diff --git a/.circleci/config.yml b/.circleci/config.yml
index e1cfa01a..d6eb8d97 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,7 +8,7 @@ jobs:
     docker:
       # specify the version you desire here
       # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
-      - image: kclem/crispresso2vpy3
+      - image: pinellolab/crispresso2:v2.3.0
 
       # Specify service dependencies here if necessary
       # CircleCI maintains a library of pre-built images
diff --git a/.github/envs/test_env.yml b/.github/envs/test_env.yml
new file mode 100644
index 00000000..581caa2b
--- /dev/null
+++ b/.github/envs/test_env.yml
@@ -0,0 +1,18 @@
+name: test_env
+channels:
+  - conda-forge
+  - bioconda
+  - defaults
+dependencies:
+  - pip
+  - fastp
+  - numpy<2
+  - cython
+  - jinja2
+  - tbb=2020.2
+  - pyparsing=2.3.1
+  - scipy
+  - matplotlib=3.8.4
+  - pandas>2
+  - pip:
+    - ydiff
diff --git a/.github/workflows/.pylintrc b/.github/workflows/.pylintrc
new file mode 100644
index 00000000..1b892797
--- /dev/null
+++ b/.github/workflows/.pylintrc
@@ -0,0 +1,7 @@
+[FORMAT]
+max-line-length=150
+max-args=15
+max-locals=40
+
+[MESSAGES CONTROL]
+disable = E0401, W0719
diff --git a/.github/workflows/aws_ecr.yml b/.github/workflows/aws_ecr.yml
new file mode 100644
index 00000000..85437885
--- /dev/null
+++ b/.github/workflows/aws_ecr.yml
@@ -0,0 +1,47 @@
+name: Push Docker image to Amazon ECR
+
+on:
+  release:
+    types:
+      - edited
+      - released
+    branches:
+      - master
+
+jobs:
+  build-and-push:
+    name: Build and Push Docker image
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v2
+
+    - id: get_version
+      name: Get version
+      uses: jannemattila/get-version-from-tag@v3
+
+    - name: Configure AWS credentials
+      uses: aws-actions/configure-aws-credentials@v1
+      with:
+        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+        aws-region: us-east-1
+
+    - name: Login to Amazon ECR
+      id: login-ecr
+      uses: aws-actions/amazon-ecr-login@v1
+
+    - name: Build, tag, and push the image to Amazon ECR
+      id: build-image
+      env:
+        AWS_ACCOUNT: ${{ secrets.AWS_ACCOUNT_ID }}
+        ECR_REPOSITORY: 'crispresso2'
+        AWS_REGION: 'us-east-1'
+        IMAGE_TAG: v${{ steps.get_version.outputs.version }}
+      run: |
+        # Build a docker container and push it to ECR 
+        docker build -t $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG .
+        echo "Pushing image to ECR..."
+        docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG
+        echo "::set-output name=image::$AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG"
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
new file mode 100644
index 00000000..64f8f516
--- /dev/null
+++ b/.github/workflows/integration_tests.yml
@@ -0,0 +1,135 @@
+name: Run Integration Tests
+
+on:
+  push:
+
+  pull_request:
+    types: [opened, reopened]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -l {0}
+    env:
+      CRISPRESSO2_DIR: ${GITHUB_WORKSPACE}/../CRISPResso2_copy
+
+    strategy:
+      fail-fast: false
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Setup Conda Env
+        uses: conda-incubator/setup-miniconda@v3
+        with:
+          activate-environment: test_env
+          channels: conda-forge,bioconda,defaults
+          auto-activate-base: false
+          use-mamba: true
+
+      - name: Get Date
+        run: echo "today=$(/bin/date -u '+%Y%m%d')" >> $GITHUB_OUTPUT
+        shell: bash
+
+      - name: Cache Conda Env
+        id: cache-env
+        uses: actions/cache@v3
+        env:
+          # Increase this number to reset the cache if envs/test_env.yml hasn't changed
+          CACHE_NUMBER: 0
+        with:
+          path: /usr/share/miniconda/envs/test_env
+          key: conda-${{ runner.os }}--${{ runner.arch }}--${{ steps.get-date.outputs.today }}-${{ env.CACHE_NUMBER }}-${{ hashFiles('.github/envs/test_env.yml') }}
+
+      - name: Update Conda Env
+        run: |
+          conda env update -n test_env -f .github/envs/test_env.yml
+        if: steps.cache-env.outputs.cache-hit != 'true'
+
+      - name: Install dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y gcc g++ bowtie2 samtools libsys-hostname-long-perl
+          conda list
+
+      - name: Create directory for files
+        run: |
+          mkdir ../CRISPResso2_copy
+          cp -r * ../CRISPResso2_copy
+
+      - name: Copy C2_tests repo
+        uses: actions/checkout@v3
+        with:
+          repository: edilytics/CRISPResso2_tests
+          # ref: '<BRANCH-NAME>' # update to specific branch
+
+      - name: Run Basic
+        run: |
+          make basic test print
+
+      - name: Run Params
+        if: success() || failure()
+        run: |
+          make params test print
+
+      - name: Run Prime Editor
+        if: success() || failure()
+        run: |
+          make prime-editor test print
+
+      - name: Run BAM Input
+        if: success() || failure()
+        run: |
+          make bam test print
+
+      - name: Run BAM Output
+        if: success() || failure()
+        run: |
+          make bam-out test print
+
+      - name: Run BAM Genome Output
+        if: success() || failure()
+        run: |
+          make bam-out-genome test print
+
+      - name: Run Batch
+        if: success() || failure()
+        run: |
+          make batch test print
+
+      - name: Run Pooled
+        if: success() || failure()
+        run: |
+          make pooled test print
+
+      - name: Run Pooled Mixed Mode
+        if: success() || failure()
+        run: |
+          make pooled-mixed-mode test print
+
+      - name: Run Pooled Mixed Mode Demux
+        if: success() || failure()
+        run: |
+          make pooled-mixed-mode-genome-demux test print
+
+      - name: Run Pooled Paired Sim
+        if: success() || failure()
+        run: |
+          make pooled-paired-sim test print
+
+      - name: Run WGS
+        if: success() || failure()
+        run: |
+          make wgs test print
+
+      - name: Run Compare
+        if: success() || failure()
+        run: |
+          make compare test print
+
+      - name: Run Aggregate
+        if: success() || failure()
+        run: |
+          make aggregate test print
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
new file mode 100644
index 00000000..564df075
--- /dev/null
+++ b/.github/workflows/pylint.yml
@@ -0,0 +1,27 @@
+name: Pylint
+
+on:
+  push:
+  
+  pull_request:
+    types: [opened, reopened]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.10"]
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install pylint
+    - name: Analysing the code with pylint
+      run: |
+        pylint --fail-under=6.2 $(git ls-files '*.py') --rcfile=/home/runner/work/CRISPResso2/CRISPResso2/.github/workflows/.pylintrc
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
new file mode 100644
index 00000000..df45334d
--- /dev/null
+++ b/.github/workflows/pytest.yml
@@ -0,0 +1,45 @@
+name: Run Pytest
+
+on:
+  push:
+
+  pull_request:
+    types: [opened, reopened]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    strategy:
+      fail-fast: false
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Conda
+      uses: conda-incubator/setup-miniconda@v3
+      with:
+          channels: conda-forge,bioconda,defaults
+          auto-activate-base: false
+          activate-environment: test_env
+          environment-file: .github/envs/test_env.yml
+
+    - name: Install apt dependencies
+      run: |
+        sudo apt-get update
+        sudo apt-get install -y gcc g++ bowtie2 samtools libsys-hostname-long-perl
+
+    - name: Install Pytest
+      run: |
+        pip install pytest pytest-cov
+
+    - name: Install CRISPResso
+      run: |
+        pip install -e .
+
+    - name: Run Pytest
+      run: |
+        pytest tests --cov CRISPResso2
diff --git a/CRISPResso.py b/CRISPResso.py
deleted file mode 100755
index df462716..00000000
--- a/CRISPResso.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPResso2/CRISPResso2Align.c b/CRISPResso2/CRISPResso2Align.c
index 3569065b..e08f9715 100644
--- a/CRISPResso2/CRISPResso2Align.c
+++ b/CRISPResso2/CRISPResso2Align.c
@@ -711,7 +711,7 @@ static CYTHON_INLINE float __PYX_NAN() {
 #include "numpy/ufuncobject.h"
 
     /* NumPy API declarations from "numpy/__init__.pxd" */
-    
+
 #include <stdlib.h>
 #include "stdlib.h"
 #include "pythread.h"
@@ -1063,7 +1063,7 @@ typedef volatile __pyx_atomic_int_type __pyx_atomic_int;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":690
  * # in Cython to enable them only on the right systems.
- * 
+ *
  * ctypedef npy_int8       int8_t             # <<<<<<<<<<<<<<
  * ctypedef npy_int16      int16_t
  * ctypedef npy_int32      int32_t
@@ -1071,7 +1071,7 @@ typedef volatile __pyx_atomic_int_type __pyx_atomic_int;
 typedef npy_int8 __pyx_t_5numpy_int8_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":691
- * 
+ *
  * ctypedef npy_int8       int8_t
  * ctypedef npy_int16      int16_t             # <<<<<<<<<<<<<<
  * ctypedef npy_int32      int32_t
@@ -1099,7 +1099,7 @@ typedef npy_int64 __pyx_t_5numpy_int64_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":697
  * #ctypedef npy_int128     int128_t
- * 
+ *
  * ctypedef npy_uint8      uint8_t             # <<<<<<<<<<<<<<
  * ctypedef npy_uint16     uint16_t
  * ctypedef npy_uint32     uint32_t
@@ -1107,7 +1107,7 @@ typedef npy_int64 __pyx_t_5numpy_int64_t;
 typedef npy_uint8 __pyx_t_5numpy_uint8_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":698
- * 
+ *
  * ctypedef npy_uint8      uint8_t
  * ctypedef npy_uint16     uint16_t             # <<<<<<<<<<<<<<
  * ctypedef npy_uint32     uint32_t
@@ -1135,7 +1135,7 @@ typedef npy_uint64 __pyx_t_5numpy_uint64_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":704
  * #ctypedef npy_uint128    uint128_t
- * 
+ *
  * ctypedef npy_float32    float32_t             # <<<<<<<<<<<<<<
  * ctypedef npy_float64    float64_t
  * #ctypedef npy_float80    float80_t
@@ -1143,7 +1143,7 @@ typedef npy_uint64 __pyx_t_5numpy_uint64_t;
 typedef npy_float32 __pyx_t_5numpy_float32_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":705
- * 
+ *
  * ctypedef npy_float32    float32_t
  * ctypedef npy_float64    float64_t             # <<<<<<<<<<<<<<
  * #ctypedef npy_float80    float80_t
@@ -1165,7 +1165,7 @@ typedef npy_long __pyx_t_5numpy_int_t;
  * ctypedef npy_long       int_t
  * ctypedef npy_longlong   long_t             # <<<<<<<<<<<<<<
  * ctypedef npy_longlong   longlong_t
- * 
+ *
  */
 typedef npy_longlong __pyx_t_5numpy_long_t;
 
@@ -1173,14 +1173,14 @@ typedef npy_longlong __pyx_t_5numpy_long_t;
  * ctypedef npy_long       int_t
  * ctypedef npy_longlong   long_t
  * ctypedef npy_longlong   longlong_t             # <<<<<<<<<<<<<<
- * 
+ *
  * ctypedef npy_ulong      uint_t
  */
 typedef npy_longlong __pyx_t_5numpy_longlong_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":718
  * ctypedef npy_longlong   longlong_t
- * 
+ *
  * ctypedef npy_ulong      uint_t             # <<<<<<<<<<<<<<
  * ctypedef npy_ulonglong  ulong_t
  * ctypedef npy_ulonglong  ulonglong_t
@@ -1188,11 +1188,11 @@ typedef npy_longlong __pyx_t_5numpy_longlong_t;
 typedef npy_ulong __pyx_t_5numpy_uint_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":719
- * 
+ *
  * ctypedef npy_ulong      uint_t
  * ctypedef npy_ulonglong  ulong_t             # <<<<<<<<<<<<<<
  * ctypedef npy_ulonglong  ulonglong_t
- * 
+ *
  */
 typedef npy_ulonglong __pyx_t_5numpy_ulong_t;
 
@@ -1200,32 +1200,32 @@ typedef npy_ulonglong __pyx_t_5numpy_ulong_t;
  * ctypedef npy_ulong      uint_t
  * ctypedef npy_ulonglong  ulong_t
  * ctypedef npy_ulonglong  ulonglong_t             # <<<<<<<<<<<<<<
- * 
+ *
  * ctypedef npy_intp       intp_t
  */
 typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":722
  * ctypedef npy_ulonglong  ulonglong_t
- * 
+ *
  * ctypedef npy_intp       intp_t             # <<<<<<<<<<<<<<
  * ctypedef npy_uintp      uintp_t
- * 
+ *
  */
 typedef npy_intp __pyx_t_5numpy_intp_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":723
- * 
+ *
  * ctypedef npy_intp       intp_t
  * ctypedef npy_uintp      uintp_t             # <<<<<<<<<<<<<<
- * 
+ *
  * ctypedef npy_double     float_t
  */
 typedef npy_uintp __pyx_t_5numpy_uintp_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":725
  * ctypedef npy_uintp      uintp_t
- * 
+ *
  * ctypedef npy_double     float_t             # <<<<<<<<<<<<<<
  * ctypedef npy_double     double_t
  * ctypedef npy_longdouble longdouble_t
@@ -1233,11 +1233,11 @@ typedef npy_uintp __pyx_t_5numpy_uintp_t;
 typedef npy_double __pyx_t_5numpy_float_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":726
- * 
+ *
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t             # <<<<<<<<<<<<<<
  * ctypedef npy_longdouble longdouble_t
- * 
+ *
  */
 typedef npy_double __pyx_t_5numpy_double_t;
 
@@ -1245,14 +1245,14 @@ typedef npy_double __pyx_t_5numpy_double_t;
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t
  * ctypedef npy_longdouble longdouble_t             # <<<<<<<<<<<<<<
- * 
+ *
  * ctypedef npy_cfloat      cfloat_t
  */
 typedef npy_longdouble __pyx_t_5numpy_longdouble_t;
 
 /* "CRISPResso2/CRISPResso2Align.pyx":20
  *     ctypedef void PyObject
- * 
+ *
  * ctypedef np.int_t DTYPE_INT             # <<<<<<<<<<<<<<
  * ctypedef np.uint_t DTYPE_UINT
  * ctypedef np.int8_t DTYPE_BOOL
@@ -1260,11 +1260,11 @@ typedef npy_longdouble __pyx_t_5numpy_longdouble_t;
 typedef __pyx_t_5numpy_int_t __pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT;
 
 /* "CRISPResso2/CRISPResso2Align.pyx":21
- * 
+ *
  * ctypedef np.int_t DTYPE_INT
  * ctypedef np.uint_t DTYPE_UINT             # <<<<<<<<<<<<<<
  * ctypedef np.int8_t DTYPE_BOOL
- * 
+ *
  */
 typedef __pyx_t_5numpy_uint_t __pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_UINT;
 
@@ -1272,7 +1272,7 @@ typedef __pyx_t_5numpy_uint_t __pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_UIN
  * ctypedef np.int_t DTYPE_INT
  * ctypedef np.uint_t DTYPE_UINT
  * ctypedef np.int8_t DTYPE_BOOL             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef size_t UP = 1, LEFT = 2, DIAG = 3, NONE = 4
  */
 typedef __pyx_t_5numpy_int8_t __pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_BOOL;
@@ -1309,7 +1309,7 @@ struct __pyx_memoryviewslice_obj;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":729
  * ctypedef npy_longdouble longdouble_t
- * 
+ *
  * ctypedef npy_cfloat      cfloat_t             # <<<<<<<<<<<<<<
  * ctypedef npy_cdouble     cdouble_t
  * ctypedef npy_clongdouble clongdouble_t
@@ -1317,11 +1317,11 @@ struct __pyx_memoryviewslice_obj;
 typedef npy_cfloat __pyx_t_5numpy_cfloat_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":730
- * 
+ *
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t             # <<<<<<<<<<<<<<
  * ctypedef npy_clongdouble clongdouble_t
- * 
+ *
  */
 typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
 
@@ -1329,25 +1329,25 @@ typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t
  * ctypedef npy_clongdouble clongdouble_t             # <<<<<<<<<<<<<<
- * 
+ *
  * ctypedef npy_cdouble     complex_t
  */
 typedef npy_clongdouble __pyx_t_5numpy_clongdouble_t;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":733
  * ctypedef npy_clongdouble clongdouble_t
- * 
+ *
  * ctypedef npy_cdouble     complex_t             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object PyArray_MultiIterNew1(a):
  */
 typedef npy_cdouble __pyx_t_5numpy_complex_t;
 
 /* "View.MemoryView":105
- * 
+ *
  * @cname("__pyx_array")
  * cdef class array:             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef:
  */
 struct __pyx_array_obj {
@@ -1369,7 +1369,7 @@ struct __pyx_array_obj {
 
 
 /* "View.MemoryView":279
- * 
+ *
  * @cname('__pyx_MemviewEnum')
  * cdef class Enum(object):             # <<<<<<<<<<<<<<
  *     cdef object name
@@ -1382,10 +1382,10 @@ struct __pyx_MemviewEnum_obj {
 
 
 /* "View.MemoryView":330
- * 
+ *
  * @cname('__pyx_memoryview')
  * cdef class memoryview(object):             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef object obj
  */
 struct __pyx_memoryview_obj {
@@ -1405,11 +1405,11 @@ struct __pyx_memoryview_obj {
 
 
 /* "View.MemoryView":965
- * 
+ *
  * @cname('__pyx_memoryviewslice')
  * cdef class _memoryviewslice(memoryview):             # <<<<<<<<<<<<<<
  *     "Internal class for passing memoryview slices to Python"
- * 
+ *
  */
 struct __pyx_memoryviewslice_obj {
   struct __pyx_memoryview_obj __pyx_base;
@@ -1422,10 +1422,10 @@ struct __pyx_memoryviewslice_obj {
 
 
 /* "View.MemoryView":105
- * 
+ *
  * @cname("__pyx_array")
  * cdef class array:             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef:
  */
 
@@ -1436,10 +1436,10 @@ static struct __pyx_vtabstruct_array *__pyx_vtabptr_array;
 
 
 /* "View.MemoryView":330
- * 
+ *
  * @cname('__pyx_memoryview')
  * cdef class memoryview(object):             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef object obj
  */
 
@@ -1456,11 +1456,11 @@ static struct __pyx_vtabstruct_memoryview *__pyx_vtabptr_memoryview;
 
 
 /* "View.MemoryView":965
- * 
+ *
  * @cname('__pyx_memoryviewslice')
  * cdef class _memoryviewslice(memoryview):             # <<<<<<<<<<<<<<
  *     "Internal class for passing memoryview slices to Python"
- * 
+ *
  */
 
 struct __pyx_vtabstruct__memoryviewslice {
@@ -2893,8 +2893,8 @@ static PyObject *__pyx_codeobj__42;
 /* Late includes */
 
 /* "CRISPResso2/CRISPResso2Align.pyx":28
- * 
- * 
+ *
+ *
  * cdef char* get_c_string_with_length(size_t length):             # <<<<<<<<<<<<<<
  *     cdef char* c_string = <char *> malloc((length + 1) * sizeof(char))
  *     if not c_string:
@@ -2911,7 +2911,7 @@ static char *__pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(s
   __Pyx_RefNannySetupContext("get_c_string_with_length", 0);
 
   /* "CRISPResso2/CRISPResso2Align.pyx":29
- * 
+ *
  * cdef char* get_c_string_with_length(size_t length):
  *     cdef char* c_string = <char *> malloc((length + 1) * sizeof(char))             # <<<<<<<<<<<<<<
  *     if not c_string:
@@ -2934,7 +2934,7 @@ static char *__pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(s
  *     if not c_string:
  *         raise MemoryError()             # <<<<<<<<<<<<<<
  *     return c_string
- * 
+ *
  */
     PyErr_NoMemory(); __PYX_ERR(0, 31, __pyx_L1_error)
 
@@ -2951,15 +2951,15 @@ static char *__pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(s
  *     if not c_string:
  *         raise MemoryError()
  *     return c_string             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = __pyx_v_c_string;
   goto __pyx_L0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":28
- * 
- * 
+ *
+ *
  * cdef char* get_c_string_with_length(size_t length):             # <<<<<<<<<<<<<<
  *     cdef char* c_string = <char *> malloc((length + 1) * sizeof(char))
  *     if not c_string:
@@ -2975,8 +2975,8 @@ static char *__pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(s
 }
 
 /* "CRISPResso2/CRISPResso2Align.pyx":35
- * 
- * 
+ *
+ *
  * def read_matrix(path):             # <<<<<<<<<<<<<<
  *     """
  *     Read a matrix in the NCBI format
@@ -3047,13 +3047,13 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
  *     cdef np.ndarray[DTYPE_INT, ndim=2] a
  *     cdef size_t ai = 0, i             # <<<<<<<<<<<<<<
  *     cdef int v, mat_size
- * 
+ *
  */
   __pyx_v_ai = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":45
  *     cdef int v, mat_size
- * 
+ *
  *     with open(path) as fh:             # <<<<<<<<<<<<<<
  *         headers = None
  *         while headers is None:
@@ -3096,7 +3096,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
           __pyx_t_4 = 0;
 
           /* "CRISPResso2/CRISPResso2Align.pyx":46
- * 
+ *
  *     with open(path) as fh:
  *         headers = None             # <<<<<<<<<<<<<<
  *         while headers is None:
@@ -3182,7 +3182,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
  *             if line[0] == '#': continue
  *             headers = [ord(x) for x in line.split(' ') if x]             # <<<<<<<<<<<<<<
  *         mat_size = max(headers) + 1
- * 
+ *
  */
             { /* enter inner scope */
               __pyx_t_4 = PyList_New(0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 50, __pyx_L18_error)
@@ -3272,7 +3272,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
  *             if line[0] == '#': continue
  *             headers = [ord(x) for x in line.split(' ') if x]
  *         mat_size = max(headers) + 1             # <<<<<<<<<<<<<<
- * 
+ *
  *         a = np.zeros((mat_size, mat_size), dtype=int)
  */
           __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_builtin_max, __pyx_v_headers); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 51, __pyx_L7_error)
@@ -3286,9 +3286,9 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
 
           /* "CRISPResso2/CRISPResso2Align.pyx":53
  *         mat_size = max(headers) + 1
- * 
+ *
  *         a = np.zeros((mat_size, mat_size), dtype=int)             # <<<<<<<<<<<<<<
- * 
+ *
  *         line = fh.readline()
  */
           __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 53, __pyx_L7_error)
@@ -3346,7 +3346,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
 
           /* "CRISPResso2/CRISPResso2Align.pyx":55
  *         a = np.zeros((mat_size, mat_size), dtype=int)
- * 
+ *
  *         line = fh.readline()             # <<<<<<<<<<<<<<
  *         while line:
  *             line_vals = [int(x) for x in line[:-1].split(' ')[1:] if x]
@@ -3372,7 +3372,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
           __pyx_t_1 = 0;
 
           /* "CRISPResso2/CRISPResso2Align.pyx":56
- * 
+ *
  *         line = fh.readline()
  *         while line:             # <<<<<<<<<<<<<<
  *             line_vals = [int(x) for x in line[:-1].split(' ')[1:] if x]
@@ -3544,11 +3544,11 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
                 }
                 #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
                 if (likely(PyTuple_CheckExact(sequence))) {
-                  __pyx_t_4 = PyTuple_GET_ITEM(sequence, 0); 
-                  __pyx_t_3 = PyTuple_GET_ITEM(sequence, 1); 
+                  __pyx_t_4 = PyTuple_GET_ITEM(sequence, 0);
+                  __pyx_t_3 = PyTuple_GET_ITEM(sequence, 1);
                 } else {
-                  __pyx_t_4 = PyList_GET_ITEM(sequence, 0); 
-                  __pyx_t_3 = PyList_GET_ITEM(sequence, 1); 
+                  __pyx_t_4 = PyList_GET_ITEM(sequence, 0);
+                  __pyx_t_3 = PyList_GET_ITEM(sequence, 1);
                 }
                 __Pyx_INCREF(__pyx_t_4);
                 __Pyx_INCREF(__pyx_t_3);
@@ -3624,7 +3624,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
  *                 a[headers[ai], ohidx] = val
  *             ai += 1             # <<<<<<<<<<<<<<
  *             line = fh.readline()
- * 
+ *
  */
             __pyx_v_ai = (__pyx_v_ai + 1);
 
@@ -3632,7 +3632,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
  *                 a[headers[ai], ohidx] = val
  *             ai += 1
  *             line = fh.readline()             # <<<<<<<<<<<<<<
- * 
+ *
  *     return a
  */
             __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_fh, __pyx_n_s_readline); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 61, __pyx_L7_error)
@@ -3658,7 +3658,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
 
           /* "CRISPResso2/CRISPResso2Align.pyx":45
  *     cdef int v, mat_size
- * 
+ *
  *     with open(path) as fh:             # <<<<<<<<<<<<<<
  *         headers = None
  *         while headers is None:
@@ -3696,7 +3696,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
             __Pyx_GIVEREF(__pyx_t_3);
             __Pyx_XGIVEREF(__pyx_t_5);
             __Pyx_ErrRestoreWithState(__pyx_t_1, __pyx_t_3, __pyx_t_5);
-            __pyx_t_1 = 0; __pyx_t_3 = 0; __pyx_t_5 = 0; 
+            __pyx_t_1 = 0; __pyx_t_3 = 0; __pyx_t_5 = 0;
             __PYX_ERR(0, 45, __pyx_L9_except_error)
           }
           __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0;
@@ -3740,9 +3740,9 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
 
   /* "CRISPResso2/CRISPResso2Align.pyx":63
  *             line = fh.readline()
- * 
+ *
  *     return a             # <<<<<<<<<<<<<<
- * 
+ *
  * def make_matrix(match_score=5, mismatch_score=-4, n_mismatch_score=-2, n_match_score=-1):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -3751,8 +3751,8 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
   goto __pyx_L0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":35
- * 
- * 
+ *
+ *
  * def read_matrix(path):             # <<<<<<<<<<<<<<
  *     """
  *     Read a matrix in the NCBI format
@@ -3793,7 +3793,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_read_matrix(CYTHON_UN
 
 /* "CRISPResso2/CRISPResso2Align.pyx":65
  *     return a
- * 
+ *
  * def make_matrix(match_score=5, mismatch_score=-4, n_mismatch_score=-2, n_match_score=-1):             # <<<<<<<<<<<<<<
  *     """
  *     Create a score matrix for matches/mismatches.
@@ -3944,13 +3944,13 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
  *     cdef np.ndarray[DTYPE_INT, ndim=2] a
  *     cdef size_t ai = 0, i             # <<<<<<<<<<<<<<
  *     cdef int v, mat_size
- * 
+ *
  */
   __pyx_v_ai = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":79
  *     cdef int v, mat_size
- * 
+ *
  *     letters = ['A','T','C','G','N']             # <<<<<<<<<<<<<<
  *     headers = [ord(x) for x in letters]
  *     mat_size = max(headers) + 1
@@ -3976,11 +3976,11 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
   __pyx_t_1 = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":80
- * 
+ *
  *     letters = ['A','T','C','G','N']
  *     headers = [ord(x) for x in letters]             # <<<<<<<<<<<<<<
  *     mat_size = max(headers) + 1
- * 
+ *
  */
   { /* enter inner scope */
     __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 80, __pyx_L5_error)
@@ -4017,7 +4017,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
  *     letters = ['A','T','C','G','N']
  *     headers = [ord(x) for x in letters]
  *     mat_size = max(headers) + 1             # <<<<<<<<<<<<<<
- * 
+ *
  *     nuc_ords = [ord(x) for x in ['A','T','C','G']]
  */
   __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_max, __pyx_v_headers); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 81, __pyx_L1_error)
@@ -4031,9 +4031,9 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":83
  *     mat_size = max(headers) + 1
- * 
+ *
  *     nuc_ords = [ord(x) for x in ['A','T','C','G']]             # <<<<<<<<<<<<<<
- * 
+ *
  *     a = np.zeros((mat_size, mat_size), dtype=int)
  */
   { /* enter inner scope */
@@ -4069,9 +4069,9 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":85
  *     nuc_ords = [ord(x) for x in ['A','T','C','G']]
- * 
+ *
  *     a = np.zeros((mat_size, mat_size), dtype=int)             # <<<<<<<<<<<<<<
- * 
+ *
  *     for nuc in nuc_ords:
  */
   __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 85, __pyx_L1_error)
@@ -4129,7 +4129,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":87
  *     a = np.zeros((mat_size, mat_size), dtype=int)
- * 
+ *
  *     for nuc in nuc_ords:             # <<<<<<<<<<<<<<
  *       for nuc2 in nuc_ords:
  *         if nuc == nuc2:
@@ -4147,7 +4147,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
     __pyx_t_7 = 0;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":88
- * 
+ *
  *     for nuc in nuc_ords:
  *       for nuc2 in nuc_ords:             # <<<<<<<<<<<<<<
  *         if nuc == nuc2:
@@ -4209,7 +4209,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
  *           a[nuc,nuc2] = match_score
  *         else:
  *           a[nuc,nuc2] = mismatch_score             # <<<<<<<<<<<<<<
- * 
+ *
  *     for nuc in nuc_ords:
  */
       /*else*/ {
@@ -4227,7 +4227,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
       __pyx_L19:;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":88
- * 
+ *
  *     for nuc in nuc_ords:
  *       for nuc2 in nuc_ords:             # <<<<<<<<<<<<<<
  *         if nuc == nuc2:
@@ -4238,7 +4238,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
     /* "CRISPResso2/CRISPResso2Align.pyx":87
  *     a = np.zeros((mat_size, mat_size), dtype=int)
- * 
+ *
  *     for nuc in nuc_ords:             # <<<<<<<<<<<<<<
  *       for nuc2 in nuc_ords:
  *         if nuc == nuc2:
@@ -4248,7 +4248,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":94
  *           a[nuc,nuc2] = mismatch_score
- * 
+ *
  *     for nuc in nuc_ords:             # <<<<<<<<<<<<<<
  *       a[nuc,ord('N')] = n_mismatch_score
  *       a[ord('N'),nuc] = n_mismatch_score
@@ -4266,11 +4266,11 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
     __pyx_t_7 = 0;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":95
- * 
+ *
  *     for nuc in nuc_ords:
  *       a[nuc,ord('N')] = n_mismatch_score             # <<<<<<<<<<<<<<
  *       a[ord('N'),nuc] = n_mismatch_score
- * 
+ *
  */
     __pyx_t_7 = PyTuple_New(2); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 95, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_7);
@@ -4287,8 +4287,8 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
  *     for nuc in nuc_ords:
  *       a[nuc,ord('N')] = n_mismatch_score
  *       a[ord('N'),nuc] = n_mismatch_score             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_t_7 = PyTuple_New(2); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 96, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_7);
@@ -4303,7 +4303,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
     /* "CRISPResso2/CRISPResso2Align.pyx":94
  *           a[nuc,nuc2] = mismatch_score
- * 
+ *
  *     for nuc in nuc_ords:             # <<<<<<<<<<<<<<
  *       a[nuc,ord('N')] = n_mismatch_score
  *       a[ord('N'),nuc] = n_mismatch_score
@@ -4312,10 +4312,10 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":99
- * 
- * 
+ *
+ *
  *     a[ord('N'),ord('N')] = n_match_score             # <<<<<<<<<<<<<<
- * 
+ *
  *     return a
  */
   __pyx_t_14 = __Pyx_PyInt_As_npy_long(__pyx_v_n_match_score); if (unlikely((__pyx_t_14 == ((npy_long)-1)) && PyErr_Occurred())) __PYX_ERR(0, 99, __pyx_L1_error)
@@ -4338,9 +4338,9 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":101
  *     a[ord('N'),ord('N')] = n_match_score
- * 
+ *
  *     return a             # <<<<<<<<<<<<<<
- * 
+ *
  * @cython.boundscheck(False)
  */
   __Pyx_XDECREF(__pyx_r);
@@ -4350,7 +4350,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_2make_matrix(CYTHON_U
 
   /* "CRISPResso2/CRISPResso2Align.pyx":65
  *     return a
- * 
+ *
  * def make_matrix(match_score=5, mismatch_score=-4, n_mismatch_score=-2, n_match_score=-1):             # <<<<<<<<<<<<<<
  *     """
  *     Create a score matrix for matches/mismatches.
@@ -4624,7 +4624,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
 
   /* "CRISPResso2/CRISPResso2Align.pyx":119
  *     """
- * 
+ *
  *     byte_seqj = pystr_seqj.encode('UTF-8')             # <<<<<<<<<<<<<<
  *     cdef char* seqj = byte_seqj
  *     byte_seqi = pystr_seqi.encode('UTF-8')
@@ -4639,7 +4639,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
   __pyx_t_1 = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":120
- * 
+ *
  *     byte_seqj = pystr_seqj.encode('UTF-8')
  *     cdef char* seqj = byte_seqj             # <<<<<<<<<<<<<<
  *     byte_seqi = pystr_seqi.encode('UTF-8')
@@ -4653,7 +4653,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     cdef char* seqj = byte_seqj
  *     byte_seqi = pystr_seqi.encode('UTF-8')             # <<<<<<<<<<<<<<
  *     cdef char* seqi = byte_seqi
- * 
+ *
  */
   if (unlikely(__pyx_v_pystr_seqi == Py_None)) {
     PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%.30s'", "encode");
@@ -4668,7 +4668,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     cdef char* seqj = byte_seqj
  *     byte_seqi = pystr_seqi.encode('UTF-8')
  *     cdef char* seqi = byte_seqi             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef size_t max_j = len(pystr_seqj)
  */
   __pyx_t_2 = __Pyx_PyObject_AsWritableString(__pyx_v_byte_seqi); if (unlikely((!__pyx_t_2) && PyErr_Occurred())) __PYX_ERR(0, 122, __pyx_L1_error)
@@ -4676,7 +4676,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
 
   /* "CRISPResso2/CRISPResso2Align.pyx":124
  *     cdef char* seqi = byte_seqi
- * 
+ *
  *     cdef size_t max_j = len(pystr_seqj)             # <<<<<<<<<<<<<<
  *     cdef size_t max_i = len(pystr_seqi)
  *     if len(gap_incentive) != max_i + 1:
@@ -4689,7 +4689,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
   __pyx_v_max_j = __pyx_t_3;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":125
- * 
+ *
  *     cdef size_t max_j = len(pystr_seqj)
  *     cdef size_t max_i = len(pystr_seqi)             # <<<<<<<<<<<<<<
  *     if len(gap_incentive) != max_i + 1:
@@ -4718,7 +4718,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     if len(gap_incentive) != max_i + 1:
  *         print('\nERROR: Mismatch in gap_incentive length (gap_incentive: ' + str(len(gap_incentive)) + ' ref: '+str(max_i+1) + ')\n')             # <<<<<<<<<<<<<<
  *         return 0
- * 
+ *
  */
     __pyx_t_3 = PyObject_Length(((PyObject *)__pyx_v_gap_incentive)); if (unlikely(__pyx_t_3 == ((Py_ssize_t)-1))) __PYX_ERR(0, 127, __pyx_L1_error)
     __pyx_t_1 = PyInt_FromSsize_t(__pyx_t_3); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 127, __pyx_L1_error)
@@ -4753,7 +4753,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     if len(gap_incentive) != max_i + 1:
  *         print('\nERROR: Mismatch in gap_incentive length (gap_incentive: ' + str(len(gap_incentive)) + ' ref: '+str(max_i+1) + ')\n')
  *         return 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     # need to initialize j for the case when it's a zero-length string.
  */
     __Pyx_XDECREF(__pyx_r);
@@ -4771,11 +4771,11 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
   }
 
   /* "CRISPResso2/CRISPResso2Align.pyx":131
- * 
+ *
  *     # need to initialize j for the case when it's a zero-length string.
  *     cdef size_t i = 0, j = 0, seqlen, align_counter = 0, p             # <<<<<<<<<<<<<<
  *     cdef int diag_score, up_score, left_score, tscore
- * 
+ *
  */
   __pyx_v_i = 0;
   __pyx_v_j = 0;
@@ -4783,7 +4783,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
 
   /* "CRISPResso2/CRISPResso2Align.pyx":144
  *     # J array - best alignment so far ending with a gap in Ref (I) (deletion in ref, insertion in read)
- * 
+ *
  *     cdef int [:,:] mScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))             # <<<<<<<<<<<<<<
  *     cdef int [:,:] iScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] jScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
@@ -4828,7 +4828,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
   __pyx_t_8.data = NULL;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":145
- * 
+ *
  *     cdef int [:,:] mScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] iScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))             # <<<<<<<<<<<<<<
  *     cdef int [:,:] jScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
@@ -4970,7 +4970,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     cdef int [:,:] mPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] iPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))             # <<<<<<<<<<<<<<
  *     cdef int [:,:] jPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
- * 
+ *
  */
   __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 148, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -5015,8 +5015,8 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     cdef int [:,:] mPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] iPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] jPointer = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 149, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -5058,8 +5058,8 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
   __pyx_t_8.data = NULL;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":152
- * 
- * 
+ *
+ *
  *     cdef int min_score = gap_open * max_j * max_i             # <<<<<<<<<<<<<<
  *     # if max_j or max_i == 0 min_score could be 0 which would lead to bad score matrix initialization
  *     if gap_open < min_score:
@@ -5071,7 +5071,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     # if max_j or max_i == 0 min_score could be 0 which would lead to bad score matrix initialization
  *     if gap_open < min_score:             # <<<<<<<<<<<<<<
  *         min_score = gap_open
- * 
+ *
  */
   __pyx_t_4 = ((__pyx_v_gap_open < __pyx_v_min_score) != 0);
   if (__pyx_t_4) {
@@ -5080,7 +5080,7 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     # if max_j or max_i == 0 min_score could be 0 which would lead to bad score matrix initialization
  *     if gap_open < min_score:
  *         min_score = gap_open             # <<<<<<<<<<<<<<
- * 
+ *
  *     #init match matrix
  */
     __pyx_v_min_score = __pyx_v_gap_open;
@@ -5090,12 +5090,12 @@ static PyObject *__pyx_pf_11CRISPResso2_16CRISPResso2Align_4global_align(CYTHON_
  *     # if max_j or max_i == 0 min_score could be 0 which would lead to bad score matrix initialization
  *     if gap_open < min_score:             # <<<<<<<<<<<<<<
  *         min_score = gap_open
- * 
+ *
  */
   }
 
   /* "CRISPResso2/CRISPResso2Align.pyx":158
- * 
+ *
  *     #init match matrix
  *     mScore[0,1:] = min_score             # <<<<<<<<<<<<<<
  *     mScore[1:,0] = min_score
@@ -5277,7 +5277,7 @@ __pyx_t_10 = -1;
  *     mPointer[0,1:] = IARRAY
  *     mPointer[1:,0] = JARRAY             # <<<<<<<<<<<<<<
  *     mPointer[0,0] = 0
- * 
+ *
  */
   __pyx_t_9.data = __pyx_v_mPointer.data;
   __pyx_t_9.memview = __pyx_v_mPointer.memview;
@@ -5331,7 +5331,7 @@ __pyx_t_10 = -1;
  *     mPointer[0,1:] = IARRAY
  *     mPointer[1:,0] = JARRAY
  *     mPointer[0,0] = 0             # <<<<<<<<<<<<<<
- * 
+ *
  * # no penalty for gaps starting at beginning
  */
   __pyx_t_12 = 0;
@@ -5372,7 +5372,7 @@ __pyx_t_10 = -1;
  * #    iScore[0,1:] = [gap_extend * np.arange(1, max_j+1, dtype=int)]
  *     iScore[0:,0] = min_score             # <<<<<<<<<<<<<<
  *     iPointer[0,1:] = IARRAY
- * 
+ *
  */
   __pyx_t_9.data = __pyx_v_iScore.data;
   __pyx_t_9.memview = __pyx_v_iScore.memview;
@@ -5426,7 +5426,7 @@ __pyx_t_10 = -1;
  * #    iScore[0,1:] = [gap_extend * np.arange(1, max_j+1, dtype=int)]
  *     iScore[0:,0] = min_score
  *     iPointer[0,1:] = IARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *     #init j matrix
  */
   __pyx_t_9.data = __pyx_v_iPointer.data;
@@ -5478,7 +5478,7 @@ __pyx_t_10 = -1;
   __pyx_t_9.data = NULL;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":177
- * 
+ *
  *     #init j matrix
  *     for i in range(1,max_i+1):             # <<<<<<<<<<<<<<
  *         jScore[i,0] = gap_extend * i + gap_incentive[0]
@@ -5509,7 +5509,7 @@ __pyx_t_10 = -1;
  *     #jScore[1:,0] = np.vectorize(gap_extend * np.arange(1, max_i+1, dtype=int))
  *     jScore[0,0:] = min_score             # <<<<<<<<<<<<<<
  *     jPointer[1:,0] = JARRAY
- * 
+ *
  */
   __pyx_t_9.data = __pyx_v_jScore.data;
   __pyx_t_9.memview = __pyx_v_jScore.memview;
@@ -5563,7 +5563,7 @@ __pyx_t_10 = -1;
  *     #jScore[1:,0] = np.vectorize(gap_extend * np.arange(1, max_i+1, dtype=int))
  *     jScore[0,0:] = min_score
  *     jPointer[1:,0] = JARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  * #    print('gap penalty is'+str(gap_incentive))
  */
   __pyx_t_9.data = __pyx_v_jPointer.data;
@@ -5615,11 +5615,11 @@ __pyx_t_10 = -1;
   __pyx_t_9.data = NULL;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":192
- * 
+ *
  *     #apply NW algorithm for inside squares (not last row or column)
  *     for i in range(1, max_i):             # <<<<<<<<<<<<<<
  *         ci = seqi[i - 1] #char in i
- * 
+ *
  */
   __pyx_t_13 = __pyx_v_max_i;
   __pyx_t_14 = __pyx_t_13;
@@ -5630,17 +5630,17 @@ __pyx_t_10 = -1;
  *     #apply NW algorithm for inside squares (not last row or column)
  *     for i in range(1, max_i):
  *         ci = seqi[i - 1] #char in i             # <<<<<<<<<<<<<<
- * 
+ *
  *         for j in range(1, max_j):
  */
     __pyx_v_ci = (__pyx_v_seqi[(__pyx_v_i - 1)]);
 
     /* "CRISPResso2/CRISPResso2Align.pyx":195
  *         ci = seqi[i - 1] #char in i
- * 
+ *
  *         for j in range(1, max_j):             # <<<<<<<<<<<<<<
  *             cj = seqj[j - 1] #char in j
- * 
+ *
  */
     __pyx_t_16 = __pyx_v_max_j;
     __pyx_t_17 = __pyx_t_16;
@@ -5648,17 +5648,17 @@ __pyx_t_10 = -1;
       __pyx_v_j = __pyx_t_18;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":196
- * 
+ *
  *         for j in range(1, max_j):
  *             cj = seqj[j - 1] #char in j             # <<<<<<<<<<<<<<
- * 
+ *
  *             iFromMVal = gap_open + mScore[i, j - 1] + gap_incentive[i]
  */
       __pyx_v_cj = (__pyx_v_seqj[(__pyx_v_j - 1)]);
 
       /* "CRISPResso2/CRISPResso2Align.pyx":198
  *             cj = seqj[j - 1] #char in j
- * 
+ *
  *             iFromMVal = gap_open + mScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *             iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]
  *             if iFromMVal > iExtendVal:
@@ -5669,7 +5669,7 @@ __pyx_t_10 = -1;
       __pyx_v_iFromMVal = ((__pyx_v_gap_open + (*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_19 * __pyx_v_mScore.strides[0]) ) + __pyx_t_20 * __pyx_v_mScore.strides[1]) )))) + (*__Pyx_BufPtrStrided1d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_gap_incentive.rcbuffer->pybuffer.buf, __pyx_t_21, __pyx_pybuffernd_gap_incentive.diminfo[0].strides)));
 
       /* "CRISPResso2/CRISPResso2Align.pyx":199
- * 
+ *
  *             iFromMVal = gap_open + mScore[i, j - 1] + gap_incentive[i]
  *             iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *             if iFromMVal > iExtendVal:
@@ -5727,7 +5727,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 iScore[i,j] = iExtendVal             # <<<<<<<<<<<<<<
  *                 iPointer[i,j] = IARRAY
- * 
+ *
  */
       /*else*/ {
         __pyx_t_19 = __pyx_v_i;
@@ -5738,7 +5738,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 iScore[i,j] = iExtendVal
  *                 iPointer[i,j] = IARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *             jFromMVal = gap_open + mScore[i - 1, j] + gap_incentive[i-1]
  */
         __pyx_t_20 = __pyx_v_i;
@@ -5749,7 +5749,7 @@ __pyx_t_10 = -1;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":207
  *                 iPointer[i,j] = IARRAY
- * 
+ *
  *             jFromMVal = gap_open + mScore[i - 1, j] + gap_incentive[i-1]             # <<<<<<<<<<<<<<
  * 	    #no gap incentive here -- J already got the gap incentive when it transitioned from M, so don't add it again if we're extending.
  *             jExtendVal = gap_extend + jScore[i - 1, j]
@@ -5817,7 +5817,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 jScore[i,j] = jExtendVal             # <<<<<<<<<<<<<<
  *                 jPointer[i,j] = JARRAY
- * 
+ *
  */
       /*else*/ {
         __pyx_t_20 = __pyx_v_i;
@@ -5828,7 +5828,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 jScore[i,j] = jExtendVal
  *                 jPointer[i,j] = JARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]
  */
         __pyx_t_21 = __pyx_v_i;
@@ -5839,7 +5839,7 @@ __pyx_t_10 = -1;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":217
  *                 jPointer[i,j] = JARRAY
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *             iVal = iScore[i - 1, j - 1] + matrix[ci,cj]
  *             jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -5853,7 +5853,7 @@ __pyx_t_10 = -1;
       __pyx_v_mVal = ((*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_20 * __pyx_v_mScore.strides[0]) ) + __pyx_t_21 * __pyx_v_mScore.strides[1]) ))) + (*__Pyx_BufPtrStrided2d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_matrix.rcbuffer->pybuffer.buf, __pyx_t_11, __pyx_pybuffernd_matrix.diminfo[0].strides, __pyx_t_12, __pyx_pybuffernd_matrix.diminfo[1].strides)));
 
       /* "CRISPResso2/CRISPResso2Align.pyx":218
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]
  *             iVal = iScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *             jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -6017,7 +6017,7 @@ __pyx_t_10 = -1;
  *                 else:
  *                     mScore[i, j] = iVal             # <<<<<<<<<<<<<<
  *                     mPointer[i, j] = IARRAY
- * 
+ *
  */
         /*else*/ {
           __pyx_t_21 = __pyx_v_i;
@@ -6028,7 +6028,7 @@ __pyx_t_10 = -1;
  *                 else:
  *                     mScore[i, j] = iVal
  *                     mPointer[i, j] = IARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  * #            print('mScore['+str(i) + ',' + str(j) +']: ' + str(mScore[i,j]) + ': max(' + str(mScore[i - 1, j - 1])+ '+ (' + str(ci)+ ',' + str(cj) + ') ' + str(matrix[ci,cj]) + ', i:'+str(iVal) + ',j:' + str(jVal))
  */
           __pyx_t_20 = __pyx_v_i;
@@ -6074,7 +6074,7 @@ __pyx_t_10 = -1;
  *     if max_j > 0:
  *         for i in range(1, max_i):             # <<<<<<<<<<<<<<
  *             ci = seqi[i-1]
- * 
+ *
  */
     __pyx_t_13 = __pyx_v_max_i;
     __pyx_t_14 = __pyx_t_13;
@@ -6085,14 +6085,14 @@ __pyx_t_10 = -1;
  *     if max_j > 0:
  *         for i in range(1, max_i):
  *             ci = seqi[i-1]             # <<<<<<<<<<<<<<
- * 
+ *
  *             iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]
  */
       __pyx_v_ci = (__pyx_v_seqi[(__pyx_v_i - 1)]);
 
       /* "CRISPResso2/CRISPResso2Align.pyx":245
  *             ci = seqi[i-1]
- * 
+ *
  *             iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *             iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]
  *             if iFromMVal > iExtendVal:
@@ -6103,7 +6103,7 @@ __pyx_t_10 = -1;
       __pyx_v_iFromMVal = ((__pyx_v_gap_extend + (*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_16 * __pyx_v_mScore.strides[0]) ) + __pyx_t_17 * __pyx_v_mScore.strides[1]) )))) + (*__Pyx_BufPtrStrided1d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_gap_incentive.rcbuffer->pybuffer.buf, __pyx_t_18, __pyx_pybuffernd_gap_incentive.diminfo[0].strides)));
 
       /* "CRISPResso2/CRISPResso2Align.pyx":246
- * 
+ *
  *             iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]
  *             iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *             if iFromMVal > iExtendVal:
@@ -6161,7 +6161,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 iScore[i,j] = iExtendVal             # <<<<<<<<<<<<<<
  *                 iPointer[i,j] = IARRAY
- * 
+ *
  */
       /*else*/ {
         __pyx_t_16 = __pyx_v_i;
@@ -6172,7 +6172,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 iScore[i,j] = iExtendVal
  *                 iPointer[i,j] = IARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *             jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]
  */
         __pyx_t_17 = __pyx_v_i;
@@ -6183,7 +6183,7 @@ __pyx_t_10 = -1;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":254
  *                 iPointer[i,j] = IARRAY
- * 
+ *
  *             jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]             # <<<<<<<<<<<<<<
  *             jExtendVal = gap_extend + jScore[i - 1, j]
  *             if jFromMVal > jExtendVal:
@@ -6194,7 +6194,7 @@ __pyx_t_10 = -1;
       __pyx_v_jFromMVal = ((__pyx_v_gap_extend + (*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_16 * __pyx_v_mScore.strides[0]) ) + __pyx_t_17 * __pyx_v_mScore.strides[1]) )))) + (*__Pyx_BufPtrStrided1d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_gap_incentive.rcbuffer->pybuffer.buf, __pyx_t_18, __pyx_pybuffernd_gap_incentive.diminfo[0].strides)));
 
       /* "CRISPResso2/CRISPResso2Align.pyx":255
- * 
+ *
  *             jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]
  *             jExtendVal = gap_extend + jScore[i - 1, j]             # <<<<<<<<<<<<<<
  *             if jFromMVal > jExtendVal:
@@ -6251,7 +6251,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 jScore[i,j] = jExtendVal             # <<<<<<<<<<<<<<
  *                 jPointer[i,j] = JARRAY
- * 
+ *
  */
       /*else*/ {
         __pyx_t_17 = __pyx_v_i;
@@ -6262,7 +6262,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 jScore[i,j] = jExtendVal
  *                 jPointer[i,j] = JARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]
  */
         __pyx_t_18 = __pyx_v_i;
@@ -6273,7 +6273,7 @@ __pyx_t_10 = -1;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":263
  *                 jPointer[i,j] = JARRAY
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *             iVal = iScore[i - 1, j - 1] + matrix[ci,cj]
  *             jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -6287,7 +6287,7 @@ __pyx_t_10 = -1;
       __pyx_v_mVal = ((*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_17 * __pyx_v_mScore.strides[0]) ) + __pyx_t_18 * __pyx_v_mScore.strides[1]) ))) + (*__Pyx_BufPtrStrided2d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_matrix.rcbuffer->pybuffer.buf, __pyx_t_12, __pyx_pybuffernd_matrix.diminfo[0].strides, __pyx_t_11, __pyx_pybuffernd_matrix.diminfo[1].strides)));
 
       /* "CRISPResso2/CRISPResso2Align.pyx":264
- * 
+ *
  *             mVal = mScore[i - 1, j - 1] + matrix[ci,cj]
  *             iVal = iScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *             jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -6463,7 +6463,7 @@ __pyx_t_10 = -1;
  *                     mScore[i, j] = iVal
  *                     mPointer[i, j] = IARRAY             # <<<<<<<<<<<<<<
  * #            print('lastCol: mScore['+str(i) + ',' + str(j) +']: ' + str(mScore[i,j]) + ': max(' + str(mScore[i - 1, j - 1])+ '+ (' + str(ci)+ ',' + str(cj) + ') ' + str(matrix[ci,cj]) + ', i:'+str(iVal) + ',j:' + str(jVal))
- * 
+ *
  */
           __pyx_t_17 = __pyx_v_i;
           __pyx_t_18 = __pyx_v_j;
@@ -6484,7 +6484,7 @@ __pyx_t_10 = -1;
   }
 
   /* "CRISPResso2/CRISPResso2Align.pyx":283
- * 
+ *
  *     #last row
  *     i = max_i             # <<<<<<<<<<<<<<
  *     ci = seqi[i - 1]
@@ -6506,7 +6506,7 @@ __pyx_t_10 = -1;
  *     ci = seqi[i - 1]
  *     for j in range(1, max_j+1):             # <<<<<<<<<<<<<<
  *         cj = seqj[j - 1]
- * 
+ *
  */
   __pyx_t_13 = (__pyx_v_max_j + 1);
   __pyx_t_14 = __pyx_t_13;
@@ -6517,14 +6517,14 @@ __pyx_t_10 = -1;
  *     ci = seqi[i - 1]
  *     for j in range(1, max_j+1):
  *         cj = seqj[j - 1]             # <<<<<<<<<<<<<<
- * 
+ *
  *         iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]
  */
     __pyx_v_cj = (__pyx_v_seqj[(__pyx_v_j - 1)]);
 
     /* "CRISPResso2/CRISPResso2Align.pyx":288
  *         cj = seqj[j - 1]
- * 
+ *
  *         iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *         iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]
  *         if iFromMVal > iExtendVal:
@@ -6535,7 +6535,7 @@ __pyx_t_10 = -1;
     __pyx_v_iFromMVal = ((__pyx_v_gap_extend + (*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_18 * __pyx_v_mScore.strides[0]) ) + __pyx_t_17 * __pyx_v_mScore.strides[1]) )))) + (*__Pyx_BufPtrStrided1d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_gap_incentive.rcbuffer->pybuffer.buf, __pyx_t_16, __pyx_pybuffernd_gap_incentive.diminfo[0].strides)));
 
     /* "CRISPResso2/CRISPResso2Align.pyx":289
- * 
+ *
  *         iFromMVal = gap_extend + mScore[i, j - 1] + gap_incentive[i]
  *         iExtendVal = gap_extend + iScore[i, j - 1] + gap_incentive[i]             # <<<<<<<<<<<<<<
  *         if iFromMVal > iExtendVal:
@@ -6593,7 +6593,7 @@ __pyx_t_10 = -1;
  *         else:
  *             iScore[i,j] = iExtendVal             # <<<<<<<<<<<<<<
  *             iPointer[i,j] = IARRAY
- * 
+ *
  */
     /*else*/ {
       __pyx_t_18 = __pyx_v_i;
@@ -6604,7 +6604,7 @@ __pyx_t_10 = -1;
  *         else:
  *             iScore[i,j] = iExtendVal
  *             iPointer[i,j] = IARRAY             # <<<<<<<<<<<<<<
- * 
+ *
  *         jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]
  */
       __pyx_t_17 = __pyx_v_i;
@@ -6615,7 +6615,7 @@ __pyx_t_10 = -1;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":297
  *             iPointer[i,j] = IARRAY
- * 
+ *
  *         jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]             # <<<<<<<<<<<<<<
  *         jExtendVal = gap_extend + jScore[i - 1, j]
  *         if jFromMVal > jExtendVal:
@@ -6626,7 +6626,7 @@ __pyx_t_10 = -1;
     __pyx_v_jFromMVal = ((__pyx_v_gap_extend + (*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_18 * __pyx_v_mScore.strides[0]) ) + __pyx_t_17 * __pyx_v_mScore.strides[1]) )))) + (*__Pyx_BufPtrStrided1d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_gap_incentive.rcbuffer->pybuffer.buf, __pyx_t_16, __pyx_pybuffernd_gap_incentive.diminfo[0].strides)));
 
     /* "CRISPResso2/CRISPResso2Align.pyx":298
- * 
+ *
  *         jFromMVal = gap_extend + mScore[i - 1, j] + gap_incentive[i-1]
  *         jExtendVal = gap_extend + jScore[i - 1, j]             # <<<<<<<<<<<<<<
  *         if jFromMVal > jExtendVal:
@@ -6683,7 +6683,7 @@ __pyx_t_10 = -1;
  *         else:
  *             jScore[i,j] = jExtendVal             # <<<<<<<<<<<<<<
  *             jPointer[i,j] = JARRAY
- * 
+ *
  */
     /*else*/ {
       __pyx_t_17 = __pyx_v_i;
@@ -6694,8 +6694,8 @@ __pyx_t_10 = -1;
  *         else:
  *             jScore[i,j] = jExtendVal
  *             jPointer[i,j] = JARRAY             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_t_16 = __pyx_v_i;
       __pyx_t_17 = __pyx_v_j;
@@ -6704,8 +6704,8 @@ __pyx_t_10 = -1;
     __pyx_L29:;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":307
- * 
- * 
+ *
+ *
  *         mVal = mScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *         iVal = iScore[i - 1, j - 1] + matrix[ci,cj]
  *         jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -6719,7 +6719,7 @@ __pyx_t_10 = -1;
     __pyx_v_mVal = ((*((int *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_mScore.data + __pyx_t_17 * __pyx_v_mScore.strides[0]) ) + __pyx_t_16 * __pyx_v_mScore.strides[1]) ))) + (*__Pyx_BufPtrStrided2d(__pyx_t_11CRISPResso2_16CRISPResso2Align_DTYPE_INT *, __pyx_pybuffernd_matrix.rcbuffer->pybuffer.buf, __pyx_t_11, __pyx_pybuffernd_matrix.diminfo[0].strides, __pyx_t_12, __pyx_pybuffernd_matrix.diminfo[1].strides)));
 
     /* "CRISPResso2/CRISPResso2Align.pyx":308
- * 
+ *
  *         mVal = mScore[i - 1, j - 1] + matrix[ci,cj]
  *         iVal = iScore[i - 1, j - 1] + matrix[ci,cj]             # <<<<<<<<<<<<<<
  *         jVal = jScore[i - 1, j - 1] + matrix[ci,cj]
@@ -6895,7 +6895,7 @@ __pyx_t_10 = -1;
  *                 mScore[i, j] = iVal
  *                 mPointer[i, j] = IARRAY             # <<<<<<<<<<<<<<
  * #        print('lastRow: mScore['+str(i) + ',' + str(j) +']: ' + str(mScore[i,j]) + ': max(' + str(mScore[i - 1, j - 1])+ '+ (' + str(ci)+ ',' + str(cj) + ') ' + str(matrix[ci,cj]) + ', i:'+str(iVal) + ',j:' + str(jVal))
- * 
+ *
  */
         __pyx_t_17 = __pyx_v_i;
         __pyx_t_16 = __pyx_v_j;
@@ -6908,7 +6908,7 @@ __pyx_t_10 = -1;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":344
  * #      print("\n"),
- * 
+ *
  *     seqlen = max_i + max_j             # <<<<<<<<<<<<<<
  *     cdef char* tmp_align_j = get_c_string_with_length(seqlen)
  *     cdef char* tmp_align_i = get_c_string_with_length(seqlen)
@@ -6916,11 +6916,11 @@ __pyx_t_10 = -1;
   __pyx_v_seqlen = (__pyx_v_max_i + __pyx_v_max_j);
 
   /* "CRISPResso2/CRISPResso2Align.pyx":345
- * 
+ *
  *     seqlen = max_i + max_j
  *     cdef char* tmp_align_j = get_c_string_with_length(seqlen)             # <<<<<<<<<<<<<<
  *     cdef char* tmp_align_i = get_c_string_with_length(seqlen)
- * 
+ *
  */
   __pyx_v_tmp_align_j = __pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(__pyx_v_seqlen);
 
@@ -6928,14 +6928,14 @@ __pyx_t_10 = -1;
  *     seqlen = max_i + max_j
  *     cdef char* tmp_align_j = get_c_string_with_length(seqlen)
  *     cdef char* tmp_align_i = get_c_string_with_length(seqlen)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef int matchCount = 0
  */
   __pyx_v_tmp_align_i = __pyx_f_11CRISPResso2_16CRISPResso2Align_get_c_string_with_length(__pyx_v_seqlen);
 
   /* "CRISPResso2/CRISPResso2Align.pyx":348
  *     cdef char* tmp_align_i = get_c_string_with_length(seqlen)
- * 
+ *
  *     cdef int matchCount = 0             # <<<<<<<<<<<<<<
  *     i = max_i
  *     j = max_j
@@ -6943,7 +6943,7 @@ __pyx_t_10 = -1;
   __pyx_v_matchCount = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":349
- * 
+ *
  *     cdef int matchCount = 0
  *     i = max_i             # <<<<<<<<<<<<<<
  *     j = max_j
@@ -7109,7 +7109,7 @@ __pyx_t_10 = -1;
  * #    print('seqj' + str(seqj))
  *     while i > 0 or j > 0:             # <<<<<<<<<<<<<<
  *         # print("i: " + str(i) + " j: " + str(j) + " currMatrix: " + str(currMatrix) + " match score: " + str(mScore[i,j]) + " last match: " +  str(mScore[i-1,j-1]) + " matrix[" + str(ci) + "," + str(cj) + "]: " + str(matrix[ci,cj]) + " last j " + str(jScore[i,j]) + " last i: " + str(iScore[i,j]) + " mpointer: " + str(mPointer[i,j]) + " ipointer: " + str(iPointer[i,j]) + " jpointer: " + str(jPointer[i,j]))
- * 
+ *
  */
   while (1) {
     __pyx_t_22 = ((__pyx_v_i > 0) != 0);
@@ -7125,7 +7125,7 @@ __pyx_t_10 = -1;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":370
  *         # print("i: " + str(i) + " j: " + str(j) + " currMatrix: " + str(currMatrix) + " match score: " + str(mScore[i,j]) + " last match: " +  str(mScore[i-1,j-1]) + " matrix[" + str(ci) + "," + str(cj) + "]: " + str(matrix[ci,cj]) + " last j " + str(jScore[i,j]) + " last i: " + str(iScore[i,j]) + " mpointer: " + str(mPointer[i,j]) + " ipointer: " + str(iPointer[i,j]) + " jpointer: " + str(jPointer[i,j]))
- * 
+ *
  *         currVal = mScore[i,j]             # <<<<<<<<<<<<<<
  *         currPtr = mPointer[i,j]
  *         if currMatrix == IARRAY:
@@ -7138,7 +7138,7 @@ __pyx_t_10 = -1;
     __pyx_t_1 = 0;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":371
- * 
+ *
  *         currVal = mScore[i,j]
  *         currPtr = mPointer[i,j]             # <<<<<<<<<<<<<<
  *         if currMatrix == IARRAY:
@@ -7289,7 +7289,7 @@ __pyx_t_10 = -1;
  *             tmp_align_i[align_counter] = ci
  *             if cj == ci:             # <<<<<<<<<<<<<<
  *                 matchCount += 1
- * 
+ *
  */
       __pyx_t_4 = ((__pyx_v_cj == __pyx_v_ci) != 0);
       if (__pyx_t_4) {
@@ -7298,7 +7298,7 @@ __pyx_t_10 = -1;
  *             tmp_align_i[align_counter] = ci
  *             if cj == ci:
  *                 matchCount += 1             # <<<<<<<<<<<<<<
- * 
+ *
  *             if i > 1:
  */
         __pyx_v_matchCount = (__pyx_v_matchCount + 1);
@@ -7308,13 +7308,13 @@ __pyx_t_10 = -1;
  *             tmp_align_i[align_counter] = ci
  *             if cj == ci:             # <<<<<<<<<<<<<<
  *                 matchCount += 1
- * 
+ *
  */
       }
 
       /* "CRISPResso2/CRISPResso2Align.pyx":386
  *                 matchCount += 1
- * 
+ *
  *             if i > 1:             # <<<<<<<<<<<<<<
  *                 i -= 1
  *                 ci = seqi[i - 1]
@@ -7323,7 +7323,7 @@ __pyx_t_10 = -1;
       if (__pyx_t_4) {
 
         /* "CRISPResso2/CRISPResso2Align.pyx":387
- * 
+ *
  *             if i > 1:
  *                 i -= 1             # <<<<<<<<<<<<<<
  *                 ci = seqi[i - 1]
@@ -7342,7 +7342,7 @@ __pyx_t_10 = -1;
 
         /* "CRISPResso2/CRISPResso2Align.pyx":386
  *                 matchCount += 1
- * 
+ *
  *             if i > 1:             # <<<<<<<<<<<<<<
  *                 i -= 1
  *                 ci = seqi[i - 1]
@@ -7414,7 +7414,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 j = 0             # <<<<<<<<<<<<<<
  *                 cj = seqj[j]
- * 
+ *
  */
       /*else*/ {
         __pyx_v_j = 0;
@@ -7423,7 +7423,7 @@ __pyx_t_10 = -1;
  *             else:
  *                 j = 0
  *                 cj = seqj[j]             # <<<<<<<<<<<<<<
- * 
+ *
  * #            print('in M set to ' + str(currMatrix))
  */
         __pyx_v_cj = (__pyx_v_seqj[__pyx_v_j]);
@@ -7441,7 +7441,7 @@ __pyx_t_10 = -1;
     }
 
     /* "CRISPResso2/CRISPResso2Align.pyx":400
- * 
+ *
  * #            print('in M set to ' + str(currMatrix))
  *         elif currMatrix == JARRAY: # 3             # <<<<<<<<<<<<<<
  *             currMatrix = jPointer[i,j]
@@ -7539,7 +7539,7 @@ __pyx_t_10 = -1;
       __pyx_L46:;
 
       /* "CRISPResso2/CRISPResso2Align.pyx":400
- * 
+ *
  * #            print('in M set to ' + str(currMatrix))
  *         elif currMatrix == JARRAY: # 3             # <<<<<<<<<<<<<<
  *             currMatrix = jPointer[i,j]
@@ -7746,7 +7746,7 @@ __pyx_t_10 = -1;
  *             print('seqj: ' + str(seqj) + ' seqi: ' + str(seqi))
  *             raise Exception('wtf4!:pointer: %i', i)             # <<<<<<<<<<<<<<
  * #          print('at end, currMatrix is ' + str(currMatrix))
- * 
+ *
  */
       __pyx_t_6 = __Pyx_PyInt_FromSize_t(__pyx_v_i); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 424, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_6);
@@ -7769,7 +7769,7 @@ __pyx_t_10 = -1;
 
     /* "CRISPResso2/CRISPResso2Align.pyx":427
  * #          print('at end, currMatrix is ' + str(currMatrix))
- * 
+ *
  *         align_counter += 1             # <<<<<<<<<<<<<<
  *     try:
  *         align_j = tmp_align_j[:align_counter].decode('UTF-8', 'strict')
@@ -7778,7 +7778,7 @@ __pyx_t_10 = -1;
   }
 
   /* "CRISPResso2/CRISPResso2Align.pyx":428
- * 
+ *
  *         align_counter += 1
  *     try:             # <<<<<<<<<<<<<<
  *         align_j = tmp_align_j[:align_counter].decode('UTF-8', 'strict')
@@ -7877,7 +7877,7 @@ __pyx_t_10 = -1;
  *         align_i = tmp_align_i[:align_counter].decode('UTF-8', 'strict')
  *     finally:
  *         free(tmp_align_i)             # <<<<<<<<<<<<<<
- * 
+ *
  *     # print(tounicode_with_length_and_free(alig))
  */
   /*finally:*/ {
@@ -8025,10 +8025,10 @@ __pyx_t_10 = -1;
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":735
  * ctypedef npy_cdouble     complex_t
- * 
+ *
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(1, <void*>a)
- * 
+ *
  */
 
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__pyx_v_a) {
@@ -8041,10 +8041,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":736
- * 
+ *
  * cdef inline object PyArray_MultiIterNew1(a):
  *     return PyArray_MultiIterNew(1, <void*>a)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object PyArray_MultiIterNew2(a, b):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8056,10 +8056,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":735
  * ctypedef npy_cdouble     complex_t
- * 
+ *
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(1, <void*>a)
- * 
+ *
  */
 
   /* function exit code */
@@ -8075,10 +8075,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":738
  *     return PyArray_MultiIterNew(1, <void*>a)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
- * 
+ *
  */
 
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__pyx_v_a, PyObject *__pyx_v_b) {
@@ -8091,10 +8091,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":739
- * 
+ *
  * cdef inline object PyArray_MultiIterNew2(a, b):
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object PyArray_MultiIterNew3(a, b, c):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8106,10 +8106,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":738
  *     return PyArray_MultiIterNew(1, <void*>a)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
- * 
+ *
  */
 
   /* function exit code */
@@ -8125,10 +8125,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":741
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
- * 
+ *
  */
 
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c) {
@@ -8141,10 +8141,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":742
- * 
+ *
  * cdef inline object PyArray_MultiIterNew3(a, b, c):
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8156,10 +8156,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":741
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
- * 
+ *
  */
 
   /* function exit code */
@@ -8175,10 +8175,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":744
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
- * 
+ *
  */
 
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d) {
@@ -8191,10 +8191,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":745
- * 
+ *
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8206,10 +8206,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":744
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
- * 
+ *
  */
 
   /* function exit code */
@@ -8225,10 +8225,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":747
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
- * 
+ *
  */
 
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d, PyObject *__pyx_v_e) {
@@ -8241,10 +8241,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":748
- * 
+ *
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8256,10 +8256,10 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":747
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
- * 
+ *
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
- * 
+ *
  */
 
   /* function exit code */
@@ -8275,7 +8275,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":750
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
- * 
+ *
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
  *     if PyDataType_HASSUBARRAY(d):
  *         return <tuple>d.subarray.shape
@@ -8288,7 +8288,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   __Pyx_RefNannySetupContext("PyDataType_SHAPE", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":751
- * 
+ *
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
  *         return <tuple>d.subarray.shape
@@ -8310,7 +8310,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
     goto __pyx_L0;
 
     /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":751
- * 
+ *
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
  *         return <tuple>d.subarray.shape
@@ -8322,8 +8322,8 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
  *         return <tuple>d.subarray.shape
  *     else:
  *         return ()             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   /*else*/ {
     __Pyx_XDECREF(__pyx_r);
@@ -8334,7 +8334,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":750
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
- * 
+ *
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
  *     if PyDataType_HASSUBARRAY(d):
  *         return <tuple>d.subarray.shape
@@ -8349,7 +8349,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":929
  *     int _import_umath() except -1
- * 
+ *
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
  *     Py_INCREF(base) # important to do this before stealing the reference below!
  *     PyArray_SetBaseObject(arr, base)
@@ -8360,11 +8360,11 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
   __Pyx_RefNannySetupContext("set_array_base", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":930
- * 
+ *
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!             # <<<<<<<<<<<<<<
  *     PyArray_SetBaseObject(arr, base)
- * 
+ *
  */
   Py_INCREF(__pyx_v_base);
 
@@ -8372,14 +8372,14 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!
  *     PyArray_SetBaseObject(arr, base)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline object get_array_base(ndarray arr):
  */
   (void)(PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base));
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":929
  *     int _import_umath() except -1
- * 
+ *
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
  *     Py_INCREF(base) # important to do this before stealing the reference below!
  *     PyArray_SetBaseObject(arr, base)
@@ -8391,7 +8391,7 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":933
  *     PyArray_SetBaseObject(arr, base)
- * 
+ *
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
  *     base = PyArray_BASE(arr)
  *     if base is NULL:
@@ -8405,7 +8405,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   __Pyx_RefNannySetupContext("get_array_base", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":934
- * 
+ *
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)             # <<<<<<<<<<<<<<
  *     if base is NULL:
@@ -8428,7 +8428,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  *     if base is NULL:
  *         return None             # <<<<<<<<<<<<<<
  *     return <object>base
- * 
+ *
  */
     __Pyx_XDECREF(__pyx_r);
     __pyx_r = Py_None; __Pyx_INCREF(Py_None);
@@ -8447,7 +8447,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  *     if base is NULL:
  *         return None
  *     return <object>base             # <<<<<<<<<<<<<<
- * 
+ *
  * # Versions of the import_* functions which are more suitable for
  */
   __Pyx_XDECREF(__pyx_r);
@@ -8457,7 +8457,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":933
  *     PyArray_SetBaseObject(arr, base)
- * 
+ *
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
  *     base = PyArray_BASE(arr)
  *     if base is NULL:
@@ -8538,7 +8538,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
  *         __pyx_import_array()
  *     except Exception:             # <<<<<<<<<<<<<<
  *         raise ImportError("numpy.core.multiarray failed to import")
- * 
+ *
  */
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
@@ -8552,7 +8552,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline int import_umath() except -1:
  */
       __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 945, __pyx_L5_except_error)
@@ -8604,7 +8604,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":947
  *         raise ImportError("numpy.core.multiarray failed to import")
- * 
+ *
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
  *     try:
  *         _import_umath()
@@ -8627,7 +8627,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
   __Pyx_RefNannySetupContext("import_umath", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":948
- * 
+ *
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8652,7 +8652,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
       __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 949, __pyx_L3_error)
 
       /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":948
- * 
+ *
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8670,7 +8670,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
  *         raise ImportError("numpy.core.umath failed to import")
- * 
+ *
  */
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
@@ -8684,7 +8684,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline int import_ufunc() except -1:
  */
       __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__11, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 951, __pyx_L5_except_error)
@@ -8697,7 +8697,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __pyx_L5_except_error:;
 
     /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":948
- * 
+ *
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8713,7 +8713,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":947
  *         raise ImportError("numpy.core.multiarray failed to import")
- * 
+ *
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
  *     try:
  *         _import_umath()
@@ -8736,7 +8736,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":953
  *         raise ImportError("numpy.core.umath failed to import")
- * 
+ *
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
  *     try:
  *         _import_umath()
@@ -8759,7 +8759,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
   __Pyx_RefNannySetupContext("import_ufunc", 0);
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":954
- * 
+ *
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8784,7 +8784,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
       __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 955, __pyx_L3_error)
 
       /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":954
- * 
+ *
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8802,7 +8802,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
  *         raise ImportError("numpy.core.umath failed to import")
- * 
+ *
  */
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
@@ -8816,7 +8816,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef extern from *:
  */
       __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__11, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 957, __pyx_L5_except_error)
@@ -8829,7 +8829,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __pyx_L5_except_error:;
 
     /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":954
- * 
+ *
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
  *         _import_umath()
@@ -8845,7 +8845,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":953
  *         raise ImportError("numpy.core.umath failed to import")
- * 
+ *
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
  *     try:
  *         _import_umath()
@@ -8867,8 +8867,8 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
 }
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":967
- * 
- * 
+ *
+ *
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
  *     """
  *     Cython equivalent of `isinstance(obj, np.timedelta64)`
@@ -8883,15 +8883,15 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyTimedeltaArrType_Type)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyTimedeltaArrType_Type));
   goto __pyx_L0;
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":967
- * 
- * 
+ *
+ *
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
  *     """
  *     Cython equivalent of `isinstance(obj, np.timedelta64)`
@@ -8904,8 +8904,8 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
 }
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":982
- * 
- * 
+ *
+ *
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
  *     """
  *     Cython equivalent of `isinstance(obj, np.datetime64)`
@@ -8920,15 +8920,15 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyDatetimeArrType_Type)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyDatetimeArrType_Type));
   goto __pyx_L0;
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":982
- * 
- * 
+ *
+ *
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
  *     """
  *     Cython equivalent of `isinstance(obj, np.datetime64)`
@@ -8941,8 +8941,8 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
 }
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":997
- * 
- * 
+ *
+ *
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the int64 value underlying scalar numpy datetime64 object
@@ -8955,15 +8955,15 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
  *     also needed.  That can be found using `get_datetime64_unit`.
  *     """
  *     return (<PyDatetimeScalarObject*>obj).obval             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = ((PyDatetimeScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":997
- * 
- * 
+ *
+ *
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the int64 value underlying scalar numpy datetime64 object
@@ -8975,8 +8975,8 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
 }
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":1007
- * 
- * 
+ *
+ *
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the int64 value underlying scalar numpy timedelta64 object
@@ -8989,15 +8989,15 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
  *     returns the int64 value underlying scalar numpy timedelta64 object
  *     """
  *     return (<PyTimedeltaScalarObject*>obj).obval             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = ((PyTimedeltaScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":1007
- * 
- * 
+ *
+ *
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the int64 value underlying scalar numpy timedelta64 object
@@ -9009,8 +9009,8 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
 }
 
 /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":1014
- * 
- * 
+ *
+ *
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the unit part of the dtype for a numpy datetime64 object.
@@ -9028,8 +9028,8 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
   goto __pyx_L0;
 
   /* "../../../../home/kenclem1/anaconda3/envs/crispresso3_dev/lib/python3.8/site-packages/numpy/__init__.pxd":1014
- * 
- * 
+ *
+ *
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
  *     """
  *     returns the unit part of the dtype for a numpy datetime64 object.
@@ -9042,10 +9042,10 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
 
 /* "View.MemoryView":122
  *         cdef bint dtype_is_object
- * 
+ *
  *     def __cinit__(array self, tuple shape, Py_ssize_t itemsize, format not None,             # <<<<<<<<<<<<<<
  *                   mode="c", bint allocate_buffer=True):
- * 
+ *
  */
 
 /* Python wrapper */
@@ -9138,10 +9138,10 @@ static int __pyx_array___cinit__(PyObject *__pyx_v_self, PyObject *__pyx_args, P
     } else {
 
       /* "View.MemoryView":123
- * 
+ *
  *     def __cinit__(array self, tuple shape, Py_ssize_t itemsize, format not None,
  *                   mode="c", bint allocate_buffer=True):             # <<<<<<<<<<<<<<
- * 
+ *
  *         cdef int idx
  */
       __pyx_v_allocate_buffer = ((int)1);
@@ -9163,10 +9163,10 @@ static int __pyx_array___cinit__(PyObject *__pyx_v_self, PyObject *__pyx_args, P
 
   /* "View.MemoryView":122
  *         cdef bint dtype_is_object
- * 
+ *
  *     def __cinit__(array self, tuple shape, Py_ssize_t itemsize, format not None,             # <<<<<<<<<<<<<<
  *                   mode="c", bint allocate_buffer=True):
- * 
+ *
  */
 
   /* function exit code */
@@ -9205,10 +9205,10 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
   /* "View.MemoryView":129
  *         cdef PyObject **p
- * 
+ *
  *         self.ndim = <int> len(shape)             # <<<<<<<<<<<<<<
  *         self.itemsize = itemsize
- * 
+ *
  */
   if (unlikely(__pyx_v_shape == Py_None)) {
     PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
@@ -9218,29 +9218,29 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
   __pyx_v_self->ndim = ((int)__pyx_t_1);
 
   /* "View.MemoryView":130
- * 
+ *
  *         self.ndim = <int> len(shape)
  *         self.itemsize = itemsize             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not self.ndim:
  */
   __pyx_v_self->itemsize = __pyx_v_itemsize;
 
   /* "View.MemoryView":132
  *         self.itemsize = itemsize
- * 
+ *
  *         if not self.ndim:             # <<<<<<<<<<<<<<
  *             raise ValueError("Empty shape tuple for cython.array")
- * 
+ *
  */
   __pyx_t_2 = ((!(__pyx_v_self->ndim != 0)) != 0);
   if (unlikely(__pyx_t_2)) {
 
     /* "View.MemoryView":133
- * 
+ *
  *         if not self.ndim:
  *             raise ValueError("Empty shape tuple for cython.array")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if itemsize <= 0:
  */
     __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__12, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 133, __pyx_L1_error)
@@ -9251,28 +9251,28 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
     /* "View.MemoryView":132
  *         self.itemsize = itemsize
- * 
+ *
  *         if not self.ndim:             # <<<<<<<<<<<<<<
  *             raise ValueError("Empty shape tuple for cython.array")
- * 
+ *
  */
   }
 
   /* "View.MemoryView":135
  *             raise ValueError("Empty shape tuple for cython.array")
- * 
+ *
  *         if itemsize <= 0:             # <<<<<<<<<<<<<<
  *             raise ValueError("itemsize <= 0 for cython.array")
- * 
+ *
  */
   __pyx_t_2 = ((__pyx_v_itemsize <= 0) != 0);
   if (unlikely(__pyx_t_2)) {
 
     /* "View.MemoryView":136
- * 
+ *
  *         if itemsize <= 0:
  *             raise ValueError("itemsize <= 0 for cython.array")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not isinstance(format, bytes):
  */
     __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__13, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 136, __pyx_L1_error)
@@ -9283,26 +9283,26 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
     /* "View.MemoryView":135
  *             raise ValueError("Empty shape tuple for cython.array")
- * 
+ *
  *         if itemsize <= 0:             # <<<<<<<<<<<<<<
  *             raise ValueError("itemsize <= 0 for cython.array")
- * 
+ *
  */
   }
 
   /* "View.MemoryView":138
  *             raise ValueError("itemsize <= 0 for cython.array")
- * 
+ *
  *         if not isinstance(format, bytes):             # <<<<<<<<<<<<<<
  *             format = format.encode('ASCII')
  *         self._format = format  # keep a reference to the byte string
  */
-  __pyx_t_2 = PyBytes_Check(__pyx_v_format); 
+  __pyx_t_2 = PyBytes_Check(__pyx_v_format);
   __pyx_t_4 = ((!(__pyx_t_2 != 0)) != 0);
   if (__pyx_t_4) {
 
     /* "View.MemoryView":139
- * 
+ *
  *         if not isinstance(format, bytes):
  *             format = format.encode('ASCII')             # <<<<<<<<<<<<<<
  *         self._format = format  # keep a reference to the byte string
@@ -9330,7 +9330,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
     /* "View.MemoryView":138
  *             raise ValueError("itemsize <= 0 for cython.array")
- * 
+ *
  *         if not isinstance(format, bytes):             # <<<<<<<<<<<<<<
  *             format = format.encode('ASCII')
  *         self._format = format  # keep a reference to the byte string
@@ -9342,7 +9342,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             format = format.encode('ASCII')
  *         self._format = format  # keep a reference to the byte string             # <<<<<<<<<<<<<<
  *         self.format = self._format
- * 
+ *
  */
   if (!(likely(PyBytes_CheckExact(__pyx_v_format))||((__pyx_v_format) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_v_format)->tp_name), 0))) __PYX_ERR(2, 140, __pyx_L1_error)
   __pyx_t_3 = __pyx_v_format;
@@ -9357,8 +9357,8 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             format = format.encode('ASCII')
  *         self._format = format  # keep a reference to the byte string
  *         self.format = self._format             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   if (unlikely(__pyx_v_self->_format == Py_None)) {
     PyErr_SetString(PyExc_TypeError, "expected bytes, NoneType found");
@@ -9368,39 +9368,39 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
   __pyx_v_self->format = __pyx_t_7;
 
   /* "View.MemoryView":144
- * 
- * 
+ *
+ *
  *         self._shape = <Py_ssize_t *> PyObject_Malloc(sizeof(Py_ssize_t)*self.ndim*2)             # <<<<<<<<<<<<<<
  *         self._strides = self._shape + self.ndim
- * 
+ *
  */
   __pyx_v_self->_shape = ((Py_ssize_t *)PyObject_Malloc((((sizeof(Py_ssize_t)) * __pyx_v_self->ndim) * 2)));
 
   /* "View.MemoryView":145
- * 
+ *
  *         self._shape = <Py_ssize_t *> PyObject_Malloc(sizeof(Py_ssize_t)*self.ndim*2)
  *         self._strides = self._shape + self.ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not self._shape:
  */
   __pyx_v_self->_strides = (__pyx_v_self->_shape + __pyx_v_self->ndim);
 
   /* "View.MemoryView":147
  *         self._strides = self._shape + self.ndim
- * 
+ *
  *         if not self._shape:             # <<<<<<<<<<<<<<
  *             raise MemoryError("unable to allocate shape and strides.")
- * 
+ *
  */
   __pyx_t_4 = ((!(__pyx_v_self->_shape != 0)) != 0);
   if (unlikely(__pyx_t_4)) {
 
     /* "View.MemoryView":148
- * 
+ *
  *         if not self._shape:
  *             raise MemoryError("unable to allocate shape and strides.")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_MemoryError, __pyx_tuple__14, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 148, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
@@ -9410,16 +9410,16 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
     /* "View.MemoryView":147
  *         self._strides = self._shape + self.ndim
- * 
+ *
  *         if not self._shape:             # <<<<<<<<<<<<<<
  *             raise MemoryError("unable to allocate shape and strides.")
- * 
+ *
  */
   }
 
   /* "View.MemoryView":151
- * 
- * 
+ *
+ *
  *         for idx, dim in enumerate(shape):             # <<<<<<<<<<<<<<
  *             if dim <= 0:
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))
@@ -9441,7 +9441,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
     __pyx_t_8 = (__pyx_t_8 + 1);
 
     /* "View.MemoryView":152
- * 
+ *
  *         for idx, dim in enumerate(shape):
  *             if dim <= 0:             # <<<<<<<<<<<<<<
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))
@@ -9455,7 +9455,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             if dim <= 0:
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))             # <<<<<<<<<<<<<<
  *             self._shape[idx] = dim
- * 
+ *
  */
       __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_5)) __PYX_ERR(2, 153, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
@@ -9480,7 +9480,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
       __PYX_ERR(2, 153, __pyx_L1_error)
 
       /* "View.MemoryView":152
- * 
+ *
  *         for idx, dim in enumerate(shape):
  *             if dim <= 0:             # <<<<<<<<<<<<<<
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))
@@ -9492,14 +9492,14 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             if dim <= 0:
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))
  *             self._shape[idx] = dim             # <<<<<<<<<<<<<<
- * 
+ *
  *         cdef char order
  */
     (__pyx_v_self->_shape[__pyx_v_idx]) = __pyx_v_dim;
 
     /* "View.MemoryView":151
- * 
- * 
+ *
+ *
  *         for idx, dim in enumerate(shape):             # <<<<<<<<<<<<<<
  *             if dim <= 0:
  *                 raise ValueError("Invalid shape in axis %d: %d." % (idx, dim))
@@ -9508,7 +9508,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
 
   /* "View.MemoryView":157
- * 
+ *
  *         cdef char order
  *         if mode == 'fortran':             # <<<<<<<<<<<<<<
  *             order = b'F'
@@ -9540,7 +9540,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
     __pyx_v_self->mode = __pyx_n_u_fortran;
 
     /* "View.MemoryView":157
- * 
+ *
  *         cdef char order
  *         if mode == 'fortran':             # <<<<<<<<<<<<<<
  *             order = b'F'
@@ -9595,7 +9595,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             self.mode = u'c'
  *         else:
  *             raise ValueError("Invalid mode, expected 'c' or 'fortran', got %s" % mode)             # <<<<<<<<<<<<<<
- * 
+ *
  *         self.len = fill_contig_strides_array(self._shape, self._strides,
  */
   /*else*/ {
@@ -9612,16 +9612,16 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
   /* "View.MemoryView":166
  *             raise ValueError("Invalid mode, expected 'c' or 'fortran', got %s" % mode)
- * 
+ *
  *         self.len = fill_contig_strides_array(self._shape, self._strides,             # <<<<<<<<<<<<<<
  *                                              itemsize, self.ndim, order)
- * 
+ *
  */
   __pyx_v_self->len = __pyx_fill_contig_strides_array(__pyx_v_self->_shape, __pyx_v_self->_strides, __pyx_v_itemsize, __pyx_v_self->ndim, __pyx_v_order);
 
   /* "View.MemoryView":169
  *                                              itemsize, self.ndim, order)
- * 
+ *
  *         self.free_data = allocate_buffer             # <<<<<<<<<<<<<<
  *         self.dtype_is_object = format == b'O'
  *         if allocate_buffer:
@@ -9629,11 +9629,11 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
   __pyx_v_self->free_data = __pyx_v_allocate_buffer;
 
   /* "View.MemoryView":170
- * 
+ *
  *         self.free_data = allocate_buffer
  *         self.dtype_is_object = format == b'O'             # <<<<<<<<<<<<<<
  *         if allocate_buffer:
- * 
+ *
  */
   __pyx_t_10 = PyObject_RichCompare(__pyx_v_format, __pyx_n_b_O, Py_EQ); __Pyx_XGOTREF(__pyx_t_10); if (unlikely(!__pyx_t_10)) __PYX_ERR(2, 170, __pyx_L1_error)
   __pyx_t_4 = __Pyx_PyObject_IsTrue(__pyx_t_10); if (unlikely((__pyx_t_4 == (int)-1) && PyErr_Occurred())) __PYX_ERR(2, 170, __pyx_L1_error)
@@ -9644,15 +9644,15 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *         self.free_data = allocate_buffer
  *         self.dtype_is_object = format == b'O'
  *         if allocate_buffer:             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_4 = (__pyx_v_allocate_buffer != 0);
   if (__pyx_t_4) {
 
     /* "View.MemoryView":174
- * 
- * 
+ *
+ *
  *             self.data = <char *>malloc(self.len)             # <<<<<<<<<<<<<<
  *             if not self.data:
  *                 raise MemoryError("unable to allocate array data.")
@@ -9660,11 +9660,11 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
     __pyx_v_self->data = ((char *)malloc(__pyx_v_self->len));
 
     /* "View.MemoryView":175
- * 
+ *
  *             self.data = <char *>malloc(self.len)
  *             if not self.data:             # <<<<<<<<<<<<<<
  *                 raise MemoryError("unable to allocate array data.")
- * 
+ *
  */
     __pyx_t_4 = ((!(__pyx_v_self->data != 0)) != 0);
     if (unlikely(__pyx_t_4)) {
@@ -9673,7 +9673,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *             self.data = <char *>malloc(self.len)
  *             if not self.data:
  *                 raise MemoryError("unable to allocate array data.")             # <<<<<<<<<<<<<<
- * 
+ *
  *             if self.dtype_is_object:
  */
       __pyx_t_10 = __Pyx_PyObject_Call(__pyx_builtin_MemoryError, __pyx_tuple__15, NULL); if (unlikely(!__pyx_t_10)) __PYX_ERR(2, 176, __pyx_L1_error)
@@ -9683,17 +9683,17 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
       __PYX_ERR(2, 176, __pyx_L1_error)
 
       /* "View.MemoryView":175
- * 
+ *
  *             self.data = <char *>malloc(self.len)
  *             if not self.data:             # <<<<<<<<<<<<<<
  *                 raise MemoryError("unable to allocate array data.")
- * 
+ *
  */
     }
 
     /* "View.MemoryView":178
  *                 raise MemoryError("unable to allocate array data.")
- * 
+ *
  *             if self.dtype_is_object:             # <<<<<<<<<<<<<<
  *                 p = <PyObject **> self.data
  *                 for i in range(self.len / itemsize):
@@ -9702,7 +9702,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
     if (__pyx_t_4) {
 
       /* "View.MemoryView":179
- * 
+ *
  *             if self.dtype_is_object:
  *                 p = <PyObject **> self.data             # <<<<<<<<<<<<<<
  *                 for i in range(self.len / itemsize):
@@ -9735,7 +9735,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *                 for i in range(self.len / itemsize):
  *                     p[i] = Py_None             # <<<<<<<<<<<<<<
  *                     Py_INCREF(Py_None)
- * 
+ *
  */
         (__pyx_v_p[__pyx_v_i]) = Py_None;
 
@@ -9743,7 +9743,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *                 for i in range(self.len / itemsize):
  *                     p[i] = Py_None
  *                     Py_INCREF(Py_None)             # <<<<<<<<<<<<<<
- * 
+ *
  *     @cname('getbuffer')
  */
         Py_INCREF(Py_None);
@@ -9751,7 +9751,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 
       /* "View.MemoryView":178
  *                 raise MemoryError("unable to allocate array data.")
- * 
+ *
  *             if self.dtype_is_object:             # <<<<<<<<<<<<<<
  *                 p = <PyObject **> self.data
  *                 for i in range(self.len / itemsize):
@@ -9762,17 +9762,17 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
  *         self.free_data = allocate_buffer
  *         self.dtype_is_object = format == b'O'
  *         if allocate_buffer:             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   }
 
   /* "View.MemoryView":122
  *         cdef bint dtype_is_object
- * 
+ *
  *     def __cinit__(array self, tuple shape, Py_ssize_t itemsize, format not None,             # <<<<<<<<<<<<<<
  *                   mode="c", bint allocate_buffer=True):
- * 
+ *
  */
 
   /* function exit code */
@@ -9792,7 +9792,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __
 }
 
 /* "View.MemoryView":185
- * 
+ *
  *     @cname('getbuffer')
  *     def __getbuffer__(self, Py_buffer *info, int flags):             # <<<<<<<<<<<<<<
  *         cdef int bufmode = -1
@@ -9999,7 +9999,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
  *         info.suboffsets = NULL
  *         info.itemsize = self.itemsize             # <<<<<<<<<<<<<<
  *         info.readonly = 0
- * 
+ *
  */
   __pyx_t_5 = __pyx_v_self->itemsize;
   __pyx_v_info->itemsize = __pyx_t_5;
@@ -10008,14 +10008,14 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
  *         info.suboffsets = NULL
  *         info.itemsize = self.itemsize
  *         info.readonly = 0             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  */
   __pyx_v_info->readonly = 0;
 
   /* "View.MemoryView":202
  *         info.readonly = 0
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             info.format = self.format
  *         else:
@@ -10024,7 +10024,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
   if (__pyx_t_1) {
 
     /* "View.MemoryView":203
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  *             info.format = self.format             # <<<<<<<<<<<<<<
  *         else:
@@ -10035,7 +10035,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
 
     /* "View.MemoryView":202
  *         info.readonly = 0
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             info.format = self.format
  *         else:
@@ -10047,7 +10047,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
  *             info.format = self.format
  *         else:
  *             info.format = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *         info.obj = self
  */
   /*else*/ {
@@ -10057,9 +10057,9 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
 
   /* "View.MemoryView":207
  *             info.format = NULL
- * 
+ *
  *         info.obj = self             # <<<<<<<<<<<<<<
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_array_getbuffer, "getbuffer(obj, view, flags)")
  */
   __Pyx_INCREF(((PyObject *)__pyx_v_self));
@@ -10069,7 +10069,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
   __pyx_v_info->obj = ((PyObject *)__pyx_v_self);
 
   /* "View.MemoryView":185
- * 
+ *
  *     @cname('getbuffer')
  *     def __getbuffer__(self, Py_buffer *info, int flags):             # <<<<<<<<<<<<<<
  *         cdef int bufmode = -1
@@ -10100,7 +10100,7 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(stru
 
 /* "View.MemoryView":211
  *     __pyx_getbuffer = capsule(<void *> &__pyx_array_getbuffer, "getbuffer(obj, view, flags)")
- * 
+ *
  *     def __dealloc__(array self):             # <<<<<<<<<<<<<<
  *         if self.callback_free_data != NULL:
  *             self.callback_free_data(self.data)
@@ -10123,7 +10123,7 @@ static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struc
   __Pyx_RefNannySetupContext("__dealloc__", 0);
 
   /* "View.MemoryView":212
- * 
+ *
  *     def __dealloc__(array self):
  *         if self.callback_free_data != NULL:             # <<<<<<<<<<<<<<
  *             self.callback_free_data(self.data)
@@ -10142,7 +10142,7 @@ static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struc
     __pyx_v_self->callback_free_data(__pyx_v_self->data);
 
     /* "View.MemoryView":212
- * 
+ *
  *     def __dealloc__(array self):
  *         if self.callback_free_data != NULL:             # <<<<<<<<<<<<<<
  *             self.callback_free_data(self.data)
@@ -10194,7 +10194,7 @@ static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struc
  *                                           self._strides, self.ndim, False)
  *             free(self.data)             # <<<<<<<<<<<<<<
  *         PyObject_Free(self._shape)
- * 
+ *
  */
     free(__pyx_v_self->data);
 
@@ -10212,14 +10212,14 @@ static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struc
  *                                           self._strides, self.ndim, False)
  *             free(self.data)
  *         PyObject_Free(self._shape)             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   PyObject_Free(__pyx_v_self->_shape);
 
   /* "View.MemoryView":211
  *     __pyx_getbuffer = capsule(<void *> &__pyx_array_getbuffer, "getbuffer(obj, view, flags)")
- * 
+ *
  *     def __dealloc__(array self):             # <<<<<<<<<<<<<<
  *         if self.callback_free_data != NULL:
  *             self.callback_free_data(self.data)
@@ -10230,11 +10230,11 @@ static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struc
 }
 
 /* "View.MemoryView":222
- * 
+ *
  *     @property
  *     def memview(self):             # <<<<<<<<<<<<<<
  *         return self.get_memview()
- * 
+ *
  */
 
 /* Python wrapper */
@@ -10263,7 +10263,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_5array_7memview___get__(struct _
  *     @property
  *     def memview(self):
  *         return self.get_memview()             # <<<<<<<<<<<<<<
- * 
+ *
  *     @cname('get_memview')
  */
   __Pyx_XDECREF(__pyx_r);
@@ -10274,11 +10274,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_5array_7memview___get__(struct _
   goto __pyx_L0;
 
   /* "View.MemoryView":222
- * 
+ *
  *     @property
  *     def memview(self):             # <<<<<<<<<<<<<<
  *         return self.get_memview()
- * 
+ *
  */
 
   /* function exit code */
@@ -10293,7 +10293,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_5array_7memview___get__(struct _
 }
 
 /* "View.MemoryView":226
- * 
+ *
  *     @cname('get_memview')
  *     cdef get_memview(self):             # <<<<<<<<<<<<<<
  *         flags =  PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT|PyBUF_WRITABLE
@@ -10317,7 +10317,7 @@ static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *__pyx_v_self) {
  *     cdef get_memview(self):
  *         flags =  PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT|PyBUF_WRITABLE             # <<<<<<<<<<<<<<
  *         return  memoryview(self, flags, self.dtype_is_object)
- * 
+ *
  */
   __pyx_v_flags = ((PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) | PyBUF_WRITABLE);
 
@@ -10325,7 +10325,7 @@ static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *__pyx_v_self) {
  *     cdef get_memview(self):
  *         flags =  PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT|PyBUF_WRITABLE
  *         return  memoryview(self, flags, self.dtype_is_object)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __len__(self):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -10352,7 +10352,7 @@ static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *__pyx_v_self) {
   goto __pyx_L0;
 
   /* "View.MemoryView":226
- * 
+ *
  *     @cname('get_memview')
  *     cdef get_memview(self):             # <<<<<<<<<<<<<<
  *         flags =  PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT|PyBUF_WRITABLE
@@ -10374,10 +10374,10 @@ static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *__pyx_v_self) {
 
 /* "View.MemoryView":230
  *         return  memoryview(self, flags, self.dtype_is_object)
- * 
+ *
  *     def __len__(self):             # <<<<<<<<<<<<<<
  *         return self._shape[0]
- * 
+ *
  */
 
 /* Python wrapper */
@@ -10399,10 +10399,10 @@ static Py_ssize_t __pyx_array___pyx_pf_15View_dot_MemoryView_5array_6__len__(str
   __Pyx_RefNannySetupContext("__len__", 0);
 
   /* "View.MemoryView":231
- * 
+ *
  *     def __len__(self):
  *         return self._shape[0]             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __getattr__(self, attr):
  */
   __pyx_r = (__pyx_v_self->_shape[0]);
@@ -10410,10 +10410,10 @@ static Py_ssize_t __pyx_array___pyx_pf_15View_dot_MemoryView_5array_6__len__(str
 
   /* "View.MemoryView":230
  *         return  memoryview(self, flags, self.dtype_is_object)
- * 
+ *
  *     def __len__(self):             # <<<<<<<<<<<<<<
  *         return self._shape[0]
- * 
+ *
  */
 
   /* function exit code */
@@ -10424,10 +10424,10 @@ static Py_ssize_t __pyx_array___pyx_pf_15View_dot_MemoryView_5array_6__len__(str
 
 /* "View.MemoryView":233
  *         return self._shape[0]
- * 
+ *
  *     def __getattr__(self, attr):             # <<<<<<<<<<<<<<
  *         return getattr(self.memview, attr)
- * 
+ *
  */
 
 /* Python wrapper */
@@ -10454,10 +10454,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_8__getattr__(
   __Pyx_RefNannySetupContext("__getattr__", 0);
 
   /* "View.MemoryView":234
- * 
+ *
  *     def __getattr__(self, attr):
  *         return getattr(self.memview, attr)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __getitem__(self, item):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -10472,10 +10472,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_8__getattr__(
 
   /* "View.MemoryView":233
  *         return self._shape[0]
- * 
+ *
  *     def __getattr__(self, attr):             # <<<<<<<<<<<<<<
  *         return getattr(self.memview, attr)
- * 
+ *
  */
 
   /* function exit code */
@@ -10492,10 +10492,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_8__getattr__(
 
 /* "View.MemoryView":236
  *         return getattr(self.memview, attr)
- * 
+ *
  *     def __getitem__(self, item):             # <<<<<<<<<<<<<<
  *         return self.memview[item]
- * 
+ *
  */
 
 /* Python wrapper */
@@ -10522,10 +10522,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_10__getitem__
   __Pyx_RefNannySetupContext("__getitem__", 0);
 
   /* "View.MemoryView":237
- * 
+ *
  *     def __getitem__(self, item):
  *         return self.memview[item]             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __setitem__(self, item, value):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -10540,10 +10540,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_10__getitem__
 
   /* "View.MemoryView":236
  *         return getattr(self.memview, attr)
- * 
+ *
  *     def __getitem__(self, item):             # <<<<<<<<<<<<<<
  *         return self.memview[item]
- * 
+ *
  */
 
   /* function exit code */
@@ -10560,10 +10560,10 @@ static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_10__getitem__
 
 /* "View.MemoryView":239
  *         return self.memview[item]
- * 
+ *
  *     def __setitem__(self, item, value):             # <<<<<<<<<<<<<<
  *         self.memview[item] = value
- * 
+ *
  */
 
 /* Python wrapper */
@@ -10589,11 +10589,11 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_12__setitem__(struc
   __Pyx_RefNannySetupContext("__setitem__", 0);
 
   /* "View.MemoryView":240
- * 
+ *
  *     def __setitem__(self, item, value):
  *         self.memview[item] = value             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_1 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_memview); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 240, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -10602,10 +10602,10 @@ static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_12__setitem__(struc
 
   /* "View.MemoryView":239
  *         return self.memview[item]
- * 
+ *
  *     def __setitem__(self, item, value):             # <<<<<<<<<<<<<<
  *         self.memview[item] = value
- * 
+ *
  */
 
   /* function exit code */
@@ -10734,7 +10734,7 @@ static PyObject *__pyx_pf___pyx_array_2__setstate_cython__(CYTHON_UNUSED struct
 }
 
 /* "View.MemoryView":244
- * 
+ *
  * @cname("__pyx_array_new")
  * cdef array array_cwrapper(tuple shape, Py_ssize_t itemsize, char *format,             # <<<<<<<<<<<<<<
  *                           char *mode, char *buf):
@@ -10757,7 +10757,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
 
   /* "View.MemoryView":248
  *     cdef array result
- * 
+ *
  *     if buf == NULL:             # <<<<<<<<<<<<<<
  *         result = array(shape, itemsize, format, mode.decode('ASCII'))
  *     else:
@@ -10766,7 +10766,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
   if (__pyx_t_1) {
 
     /* "View.MemoryView":249
- * 
+ *
  *     if buf == NULL:
  *         result = array(shape, itemsize, format, mode.decode('ASCII'))             # <<<<<<<<<<<<<<
  *     else:
@@ -10800,7 +10800,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
 
     /* "View.MemoryView":248
  *     cdef array result
- * 
+ *
  *     if buf == NULL:             # <<<<<<<<<<<<<<
  *         result = array(shape, itemsize, format, mode.decode('ASCII'))
  *     else:
@@ -10842,7 +10842,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
  *         result = array(shape, itemsize, format, mode.decode('ASCII'),
  *                        allocate_buffer=False)             # <<<<<<<<<<<<<<
  *         result.data = buf
- * 
+ *
  */
     __pyx_t_3 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 252, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
@@ -10866,7 +10866,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
  *         result = array(shape, itemsize, format, mode.decode('ASCII'),
  *                        allocate_buffer=False)
  *         result.data = buf             # <<<<<<<<<<<<<<
- * 
+ *
  *     return result
  */
     __pyx_v_result->data = __pyx_v_buf;
@@ -10875,10 +10875,10 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
 
   /* "View.MemoryView":255
  *         result.data = buf
- * 
+ *
  *     return result             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __Pyx_XDECREF(((PyObject *)__pyx_r));
   __Pyx_INCREF(((PyObject *)__pyx_v_result));
@@ -10886,7 +10886,7 @@ static struct __pyx_array_obj *__pyx_array_new(PyObject *__pyx_v_shape, Py_ssize
   goto __pyx_L0;
 
   /* "View.MemoryView":244
- * 
+ *
  * @cname("__pyx_array_new")
  * cdef array array_cwrapper(tuple shape, Py_ssize_t itemsize, char *format,             # <<<<<<<<<<<<<<
  *                           char *mode, char *buf):
@@ -11006,7 +11006,7 @@ static int __pyx_MemviewEnum___pyx_pf_15View_dot_MemoryView_4Enum___init__(struc
  *         self.name = name
  *     def __repr__(self):             # <<<<<<<<<<<<<<
  *         return self.name
- * 
+ *
  */
 
 /* Python wrapper */
@@ -11031,7 +11031,7 @@ static PyObject *__pyx_MemviewEnum___pyx_pf_15View_dot_MemoryView_4Enum_2__repr_
  *         self.name = name
  *     def __repr__(self):
  *         return self.name             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef generic = Enum("<strided and direct or indirect>")
  */
   __Pyx_XDECREF(__pyx_r);
@@ -11044,7 +11044,7 @@ static PyObject *__pyx_MemviewEnum___pyx_pf_15View_dot_MemoryView_4Enum_2__repr_
  *         self.name = name
  *     def __repr__(self):             # <<<<<<<<<<<<<<
  *         return self.name
- * 
+ *
  */
 
   /* function exit code */
@@ -11348,7 +11348,7 @@ static PyObject *__pyx_pf___pyx_MemviewEnum_2__setstate_cython__(struct __pyx_Me
 }
 
 /* "View.MemoryView":298
- * 
+ *
  * @cname('__pyx_align_pointer')
  * cdef void *align_pointer(void *memory, size_t alignment) nogil:             # <<<<<<<<<<<<<<
  *     "Align pointer memory on a given boundary"
@@ -11366,59 +11366,59 @@ static void *__pyx_align_pointer(void *__pyx_v_memory, size_t __pyx_v_alignment)
  *     "Align pointer memory on a given boundary"
  *     cdef Py_intptr_t aligned_p = <Py_intptr_t> memory             # <<<<<<<<<<<<<<
  *     cdef size_t offset
- * 
+ *
  */
   __pyx_v_aligned_p = ((Py_intptr_t)__pyx_v_memory);
 
   /* "View.MemoryView":304
- * 
+ *
  *     with cython.cdivision(True):
  *         offset = aligned_p % alignment             # <<<<<<<<<<<<<<
- * 
+ *
  *     if offset > 0:
  */
   __pyx_v_offset = (__pyx_v_aligned_p % __pyx_v_alignment);
 
   /* "View.MemoryView":306
  *         offset = aligned_p % alignment
- * 
+ *
  *     if offset > 0:             # <<<<<<<<<<<<<<
  *         aligned_p += alignment - offset
- * 
+ *
  */
   __pyx_t_1 = ((__pyx_v_offset > 0) != 0);
   if (__pyx_t_1) {
 
     /* "View.MemoryView":307
- * 
+ *
  *     if offset > 0:
  *         aligned_p += alignment - offset             # <<<<<<<<<<<<<<
- * 
+ *
  *     return <void *> aligned_p
  */
     __pyx_v_aligned_p = (__pyx_v_aligned_p + (__pyx_v_alignment - __pyx_v_offset));
 
     /* "View.MemoryView":306
  *         offset = aligned_p % alignment
- * 
+ *
  *     if offset > 0:             # <<<<<<<<<<<<<<
  *         aligned_p += alignment - offset
- * 
+ *
  */
   }
 
   /* "View.MemoryView":309
  *         aligned_p += alignment - offset
- * 
+ *
  *     return <void *> aligned_p             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = ((void *)__pyx_v_aligned_p);
   goto __pyx_L0;
 
   /* "View.MemoryView":298
- * 
+ *
  * @cname('__pyx_align_pointer')
  * cdef void *align_pointer(void *memory, size_t alignment) nogil:             # <<<<<<<<<<<<<<
  *     "Align pointer memory on a given boundary"
@@ -11432,7 +11432,7 @@ static void *__pyx_align_pointer(void *__pyx_v_memory, size_t __pyx_v_alignment)
 
 /* "View.MemoryView":345
  *     cdef __Pyx_TypeInfo *typeinfo
- * 
+ *
  *     def __cinit__(memoryview self, object obj, int flags, bint dtype_is_object=False):             # <<<<<<<<<<<<<<
  *         self.obj = obj
  *         self.flags = flags
@@ -11533,7 +11533,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
   __Pyx_RefNannySetupContext("__cinit__", 0);
 
   /* "View.MemoryView":346
- * 
+ *
  *     def __cinit__(memoryview self, object obj, int flags, bint dtype_is_object=False):
  *         self.obj = obj             # <<<<<<<<<<<<<<
  *         self.flags = flags
@@ -11598,7 +11598,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             if <PyObject *> self.view.obj == NULL:
  *                 (<__pyx_buffer *> &self.view).obj = Py_None             # <<<<<<<<<<<<<<
  *                 Py_INCREF(Py_None)
- * 
+ *
  */
       ((Py_buffer *)(&__pyx_v_self->view))->obj = Py_None;
 
@@ -11606,7 +11606,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             if <PyObject *> self.view.obj == NULL:
  *                 (<__pyx_buffer *> &self.view).obj = Py_None
  *                 Py_INCREF(Py_None)             # <<<<<<<<<<<<<<
- * 
+ *
  *         global __pyx_memoryview_thread_locks_used
  */
       Py_INCREF(Py_None);
@@ -11630,7 +11630,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
   }
 
   /* "View.MemoryView":355
- * 
+ *
  *         global __pyx_memoryview_thread_locks_used
  *         if __pyx_memoryview_thread_locks_used < THREAD_LOCKS_PREALLOCATED:             # <<<<<<<<<<<<<<
  *             self.lock = __pyx_memoryview_thread_locks[__pyx_memoryview_thread_locks_used]
@@ -11658,7 +11658,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
     __pyx_memoryview_thread_locks_used = (__pyx_memoryview_thread_locks_used + 1);
 
     /* "View.MemoryView":355
- * 
+ *
  *         global __pyx_memoryview_thread_locks_used
  *         if __pyx_memoryview_thread_locks_used < THREAD_LOCKS_PREALLOCATED:             # <<<<<<<<<<<<<<
  *             self.lock = __pyx_memoryview_thread_locks[__pyx_memoryview_thread_locks_used]
@@ -11690,7 +11690,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             self.lock = PyThread_allocate_lock()
  *             if self.lock is NULL:             # <<<<<<<<<<<<<<
  *                 raise MemoryError
- * 
+ *
  */
     __pyx_t_1 = ((__pyx_v_self->lock == NULL) != 0);
     if (unlikely(__pyx_t_1)) {
@@ -11699,7 +11699,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             self.lock = PyThread_allocate_lock()
  *             if self.lock is NULL:
  *                 raise MemoryError             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  */
       PyErr_NoMemory(); __PYX_ERR(2, 361, __pyx_L1_error)
@@ -11709,7 +11709,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             self.lock = PyThread_allocate_lock()
  *             if self.lock is NULL:             # <<<<<<<<<<<<<<
  *                 raise MemoryError
- * 
+ *
  */
     }
 
@@ -11724,7 +11724,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
 
   /* "View.MemoryView":363
  *                 raise MemoryError
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             self.dtype_is_object = (self.view.format[0] == b'O' and self.view.format[1] == b'\0')
  *         else:
@@ -11733,7 +11733,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
   if (__pyx_t_1) {
 
     /* "View.MemoryView":364
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  *             self.dtype_is_object = (self.view.format[0] == b'O' and self.view.format[1] == b'\0')             # <<<<<<<<<<<<<<
  *         else:
@@ -11752,7 +11752,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
 
     /* "View.MemoryView":363
  *                 raise MemoryError
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             self.dtype_is_object = (self.view.format[0] == b'O' and self.view.format[1] == b'\0')
  *         else:
@@ -11764,7 +11764,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *             self.dtype_is_object = (self.view.format[0] == b'O' and self.view.format[1] == b'\0')
  *         else:
  *             self.dtype_is_object = dtype_is_object             # <<<<<<<<<<<<<<
- * 
+ *
  *         self.acquisition_count_aligned_p = <__pyx_atomic_int *> align_pointer(
  */
   /*else*/ {
@@ -11774,7 +11774,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
 
   /* "View.MemoryView":368
  *             self.dtype_is_object = dtype_is_object
- * 
+ *
  *         self.acquisition_count_aligned_p = <__pyx_atomic_int *> align_pointer(             # <<<<<<<<<<<<<<
  *                   <void *> &self.acquisition_count[0], sizeof(__pyx_atomic_int))
  *         self.typeinfo = NULL
@@ -11785,14 +11785,14 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
  *         self.acquisition_count_aligned_p = <__pyx_atomic_int *> align_pointer(
  *                   <void *> &self.acquisition_count[0], sizeof(__pyx_atomic_int))
  *         self.typeinfo = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __dealloc__(memoryview self):
  */
   __pyx_v_self->typeinfo = NULL;
 
   /* "View.MemoryView":345
  *     cdef __Pyx_TypeInfo *typeinfo
- * 
+ *
  *     def __cinit__(memoryview self, object obj, int flags, bint dtype_is_object=False):             # <<<<<<<<<<<<<<
  *         self.obj = obj
  *         self.flags = flags
@@ -11811,7 +11811,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit_
 
 /* "View.MemoryView":372
  *         self.typeinfo = NULL
- * 
+ *
  *     def __dealloc__(memoryview self):             # <<<<<<<<<<<<<<
  *         if self.obj is not None:
  *             __Pyx_ReleaseBuffer(&self.view)
@@ -11841,7 +11841,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
   __Pyx_RefNannySetupContext("__dealloc__", 0);
 
   /* "View.MemoryView":373
- * 
+ *
  *     def __dealloc__(memoryview self):
  *         if self.obj is not None:             # <<<<<<<<<<<<<<
  *             __Pyx_ReleaseBuffer(&self.view)
@@ -11856,12 +11856,12 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
  *         if self.obj is not None:
  *             __Pyx_ReleaseBuffer(&self.view)             # <<<<<<<<<<<<<<
  *         elif (<__pyx_buffer *> &self.view).obj == Py_None:
- * 
+ *
  */
     __Pyx_ReleaseBuffer((&__pyx_v_self->view));
 
     /* "View.MemoryView":373
- * 
+ *
  *     def __dealloc__(memoryview self):
  *         if self.obj is not None:             # <<<<<<<<<<<<<<
  *             __Pyx_ReleaseBuffer(&self.view)
@@ -11874,7 +11874,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
  *         if self.obj is not None:
  *             __Pyx_ReleaseBuffer(&self.view)
  *         elif (<__pyx_buffer *> &self.view).obj == Py_None:             # <<<<<<<<<<<<<<
- * 
+ *
  *             (<__pyx_buffer *> &self.view).obj = NULL
  */
   __pyx_t_2 = ((((Py_buffer *)(&__pyx_v_self->view))->obj == Py_None) != 0);
@@ -11882,18 +11882,18 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
 
     /* "View.MemoryView":377
  *         elif (<__pyx_buffer *> &self.view).obj == Py_None:
- * 
+ *
  *             (<__pyx_buffer *> &self.view).obj = NULL             # <<<<<<<<<<<<<<
  *             Py_DECREF(Py_None)
- * 
+ *
  */
     ((Py_buffer *)(&__pyx_v_self->view))->obj = NULL;
 
     /* "View.MemoryView":378
- * 
+ *
  *             (<__pyx_buffer *> &self.view).obj = NULL
  *             Py_DECREF(Py_None)             # <<<<<<<<<<<<<<
- * 
+ *
  *         cdef int i
  */
     Py_DECREF(Py_None);
@@ -11902,7 +11902,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
  *         if self.obj is not None:
  *             __Pyx_ReleaseBuffer(&self.view)
  *         elif (<__pyx_buffer *> &self.view).obj == Py_None:             # <<<<<<<<<<<<<<
- * 
+ *
  *             (<__pyx_buffer *> &self.view).obj = NULL
  */
   }
@@ -12012,7 +12012,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
  *                     break
  *             else:
  *                 PyThread_free_lock(self.lock)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef char *get_item_pointer(memoryview self, object index) except NULL:
  */
       PyThread_free_lock(__pyx_v_self->lock);
@@ -12030,7 +12030,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
 
   /* "View.MemoryView":372
  *         self.typeinfo = NULL
- * 
+ *
  *     def __dealloc__(memoryview self):             # <<<<<<<<<<<<<<
  *         if self.obj is not None:
  *             __Pyx_ReleaseBuffer(&self.view)
@@ -12042,7 +12042,7 @@ static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__deal
 
 /* "View.MemoryView":393
  *                 PyThread_free_lock(self.lock)
- * 
+ *
  *     cdef char *get_item_pointer(memoryview self, object index) except NULL:             # <<<<<<<<<<<<<<
  *         cdef Py_ssize_t dim
  *         cdef char *itemp = <char *> self.view.buf
@@ -12070,17 +12070,17 @@ static char *__pyx_memoryview_get_item_pointer(struct __pyx_memoryview_obj *__py
  *     cdef char *get_item_pointer(memoryview self, object index) except NULL:
  *         cdef Py_ssize_t dim
  *         cdef char *itemp = <char *> self.view.buf             # <<<<<<<<<<<<<<
- * 
+ *
  *         for dim, idx in enumerate(index):
  */
   __pyx_v_itemp = ((char *)__pyx_v_self->view.buf);
 
   /* "View.MemoryView":397
  *         cdef char *itemp = <char *> self.view.buf
- * 
+ *
  *         for dim, idx in enumerate(index):             # <<<<<<<<<<<<<<
  *             itemp = pybuffer_index(&self.view, itemp, idx, dim)
- * 
+ *
  */
   __pyx_t_1 = 0;
   if (likely(PyList_CheckExact(__pyx_v_index)) || PyTuple_CheckExact(__pyx_v_index)) {
@@ -12128,10 +12128,10 @@ static char *__pyx_memoryview_get_item_pointer(struct __pyx_memoryview_obj *__py
     __pyx_t_1 = (__pyx_t_1 + 1);
 
     /* "View.MemoryView":398
- * 
+ *
  *         for dim, idx in enumerate(index):
  *             itemp = pybuffer_index(&self.view, itemp, idx, dim)             # <<<<<<<<<<<<<<
- * 
+ *
  *         return itemp
  */
     __pyx_t_6 = __Pyx_PyIndex_AsSsize_t(__pyx_v_idx); if (unlikely((__pyx_t_6 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(2, 398, __pyx_L1_error)
@@ -12140,27 +12140,27 @@ static char *__pyx_memoryview_get_item_pointer(struct __pyx_memoryview_obj *__py
 
     /* "View.MemoryView":397
  *         cdef char *itemp = <char *> self.view.buf
- * 
+ *
  *         for dim, idx in enumerate(index):             # <<<<<<<<<<<<<<
  *             itemp = pybuffer_index(&self.view, itemp, idx, dim)
- * 
+ *
  */
   }
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
   /* "View.MemoryView":400
  *             itemp = pybuffer_index(&self.view, itemp, idx, dim)
- * 
+ *
  *         return itemp             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = __pyx_v_itemp;
   goto __pyx_L0;
 
   /* "View.MemoryView":393
  *                 PyThread_free_lock(self.lock)
- * 
+ *
  *     cdef char *get_item_pointer(memoryview self, object index) except NULL:             # <<<<<<<<<<<<<<
  *         cdef Py_ssize_t dim
  *         cdef char *itemp = <char *> self.view.buf
@@ -12179,8 +12179,8 @@ static char *__pyx_memoryview_get_item_pointer(struct __pyx_memoryview_obj *__py
 }
 
 /* "View.MemoryView":403
- * 
- * 
+ *
+ *
  *     def __getitem__(memoryview self, object index):             # <<<<<<<<<<<<<<
  *         if index is Ellipsis:
  *             return self
@@ -12217,11 +12217,11 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
   __Pyx_RefNannySetupContext("__getitem__", 0);
 
   /* "View.MemoryView":404
- * 
+ *
  *     def __getitem__(memoryview self, object index):
  *         if index is Ellipsis:             # <<<<<<<<<<<<<<
  *             return self
- * 
+ *
  */
   __pyx_t_1 = (__pyx_v_index == __pyx_builtin_Ellipsis);
   __pyx_t_2 = (__pyx_t_1 != 0);
@@ -12231,7 +12231,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
  *     def __getitem__(memoryview self, object index):
  *         if index is Ellipsis:
  *             return self             # <<<<<<<<<<<<<<
- * 
+ *
  *         have_slices, indices = _unellipsify(index, self.view.ndim)
  */
     __Pyx_XDECREF(__pyx_r);
@@ -12240,19 +12240,19 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
     goto __pyx_L0;
 
     /* "View.MemoryView":404
- * 
+ *
  *     def __getitem__(memoryview self, object index):
  *         if index is Ellipsis:             # <<<<<<<<<<<<<<
  *             return self
- * 
+ *
  */
   }
 
   /* "View.MemoryView":407
  *             return self
- * 
+ *
  *         have_slices, indices = _unellipsify(index, self.view.ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *         cdef char *itemp
  */
   __pyx_t_3 = _unellipsify(__pyx_v_index, __pyx_v_self->view.ndim); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 407, __pyx_L1_error)
@@ -12266,8 +12266,8 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
       __PYX_ERR(2, 407, __pyx_L1_error)
     }
     #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-    __pyx_t_4 = PyTuple_GET_ITEM(sequence, 0); 
-    __pyx_t_5 = PyTuple_GET_ITEM(sequence, 1); 
+    __pyx_t_4 = PyTuple_GET_ITEM(sequence, 0);
+    __pyx_t_5 = PyTuple_GET_ITEM(sequence, 1);
     __Pyx_INCREF(__pyx_t_4);
     __Pyx_INCREF(__pyx_t_5);
     #else
@@ -12286,7 +12286,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
   __pyx_t_5 = 0;
 
   /* "View.MemoryView":410
- * 
+ *
  *         cdef char *itemp
  *         if have_slices:             # <<<<<<<<<<<<<<
  *             return memview_slice(self, indices)
@@ -12310,7 +12310,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
     goto __pyx_L0;
 
     /* "View.MemoryView":410
- * 
+ *
  *         cdef char *itemp
  *         if have_slices:             # <<<<<<<<<<<<<<
  *             return memview_slice(self, indices)
@@ -12323,7 +12323,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
  *         else:
  *             itemp = self.get_item_pointer(indices)             # <<<<<<<<<<<<<<
  *             return self.convert_item_to_object(itemp)
- * 
+ *
  */
   /*else*/ {
     __pyx_t_6 = ((struct __pyx_vtabstruct_memoryview *)__pyx_v_self->__pyx_vtab)->get_item_pointer(__pyx_v_self, __pyx_v_indices); if (unlikely(__pyx_t_6 == ((char *)NULL))) __PYX_ERR(2, 413, __pyx_L1_error)
@@ -12333,7 +12333,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
  *         else:
  *             itemp = self.get_item_pointer(indices)
  *             return self.convert_item_to_object(itemp)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __setitem__(memoryview self, object index, object value):
  */
     __Pyx_XDECREF(__pyx_r);
@@ -12345,8 +12345,8 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
   }
 
   /* "View.MemoryView":403
- * 
- * 
+ *
+ *
  *     def __getitem__(memoryview self, object index):             # <<<<<<<<<<<<<<
  *         if index is Ellipsis:
  *             return self
@@ -12369,7 +12369,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4_
 
 /* "View.MemoryView":416
  *             return self.convert_item_to_object(itemp)
- * 
+ *
  *     def __setitem__(memoryview self, object index, object value):             # <<<<<<<<<<<<<<
  *         if self.view.readonly:
  *             raise TypeError("Cannot assign to read-only memoryview")
@@ -12404,11 +12404,11 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
   __Pyx_INCREF(__pyx_v_index);
 
   /* "View.MemoryView":417
- * 
+ *
  *     def __setitem__(memoryview self, object index, object value):
  *         if self.view.readonly:             # <<<<<<<<<<<<<<
  *             raise TypeError("Cannot assign to read-only memoryview")
- * 
+ *
  */
   __pyx_t_1 = (__pyx_v_self->view.readonly != 0);
   if (unlikely(__pyx_t_1)) {
@@ -12417,7 +12417,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
  *     def __setitem__(memoryview self, object index, object value):
  *         if self.view.readonly:
  *             raise TypeError("Cannot assign to read-only memoryview")             # <<<<<<<<<<<<<<
- * 
+ *
  *         have_slices, index = _unellipsify(index, self.view.ndim)
  */
     __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__19, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 418, __pyx_L1_error)
@@ -12427,19 +12427,19 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
     __PYX_ERR(2, 418, __pyx_L1_error)
 
     /* "View.MemoryView":417
- * 
+ *
  *     def __setitem__(memoryview self, object index, object value):
  *         if self.view.readonly:             # <<<<<<<<<<<<<<
  *             raise TypeError("Cannot assign to read-only memoryview")
- * 
+ *
  */
   }
 
   /* "View.MemoryView":420
  *             raise TypeError("Cannot assign to read-only memoryview")
- * 
+ *
  *         have_slices, index = _unellipsify(index, self.view.ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *         if have_slices:
  */
   __pyx_t_2 = _unellipsify(__pyx_v_index, __pyx_v_self->view.ndim); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 420, __pyx_L1_error)
@@ -12453,8 +12453,8 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
       __PYX_ERR(2, 420, __pyx_L1_error)
     }
     #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-    __pyx_t_3 = PyTuple_GET_ITEM(sequence, 0); 
-    __pyx_t_4 = PyTuple_GET_ITEM(sequence, 1); 
+    __pyx_t_3 = PyTuple_GET_ITEM(sequence, 0);
+    __pyx_t_4 = PyTuple_GET_ITEM(sequence, 1);
     __Pyx_INCREF(__pyx_t_3);
     __Pyx_INCREF(__pyx_t_4);
     #else
@@ -12474,7 +12474,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
 
   /* "View.MemoryView":422
  *         have_slices, index = _unellipsify(index, self.view.ndim)
- * 
+ *
  *         if have_slices:             # <<<<<<<<<<<<<<
  *             obj = self.is_slice(value)
  *             if obj:
@@ -12483,7 +12483,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
   if (__pyx_t_1) {
 
     /* "View.MemoryView":423
- * 
+ *
  *         if have_slices:
  *             obj = self.is_slice(value)             # <<<<<<<<<<<<<<
  *             if obj:
@@ -12548,7 +12548,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
 
     /* "View.MemoryView":422
  *         have_slices, index = _unellipsify(index, self.view.ndim)
- * 
+ *
  *         if have_slices:             # <<<<<<<<<<<<<<
  *             obj = self.is_slice(value)
  *             if obj:
@@ -12560,7 +12560,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
  *                 self.setitem_slice_assign_scalar(self[index], value)
  *         else:
  *             self.setitem_indexed(index, value)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef is_slice(self, obj):
  */
   /*else*/ {
@@ -12572,7 +12572,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
 
   /* "View.MemoryView":416
  *             return self.convert_item_to_object(itemp)
- * 
+ *
  *     def __setitem__(memoryview self, object index, object value):             # <<<<<<<<<<<<<<
  *         if self.view.readonly:
  *             raise TypeError("Cannot assign to read-only memoryview")
@@ -12597,7 +12597,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setit
 
 /* "View.MemoryView":431
  *             self.setitem_indexed(index, value)
- * 
+ *
  *     cdef is_slice(self, obj):             # <<<<<<<<<<<<<<
  *         if not isinstance(obj, memoryview):
  *             try:
@@ -12622,13 +12622,13 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
   __Pyx_INCREF(__pyx_v_obj);
 
   /* "View.MemoryView":432
- * 
+ *
  *     cdef is_slice(self, obj):
  *         if not isinstance(obj, memoryview):             # <<<<<<<<<<<<<<
  *             try:
  *                 obj = memoryview(obj, self.flags & ~PyBUF_WRITABLE | PyBUF_ANY_CONTIGUOUS,
  */
-  __pyx_t_1 = __Pyx_TypeCheck(__pyx_v_obj, __pyx_memoryview_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(__pyx_v_obj, __pyx_memoryview_type);
   __pyx_t_2 = ((!(__pyx_t_1 != 0)) != 0);
   if (__pyx_t_2) {
 
@@ -12714,7 +12714,7 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
  *                                  self.dtype_is_object)
  *             except TypeError:             # <<<<<<<<<<<<<<
  *                 return None
- * 
+ *
  */
       __pyx_t_9 = __Pyx_PyErr_ExceptionMatches(__pyx_builtin_TypeError);
       if (__pyx_t_9) {
@@ -12728,7 +12728,7 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
  *                                  self.dtype_is_object)
  *             except TypeError:
  *                 return None             # <<<<<<<<<<<<<<
- * 
+ *
  *         return obj
  */
         __Pyx_XDECREF(__pyx_r);
@@ -12763,7 +12763,7 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
     }
 
     /* "View.MemoryView":432
- * 
+ *
  *     cdef is_slice(self, obj):
  *         if not isinstance(obj, memoryview):             # <<<<<<<<<<<<<<
  *             try:
@@ -12773,9 +12773,9 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
 
   /* "View.MemoryView":439
  *                 return None
- * 
+ *
  *         return obj             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef setitem_slice_assignment(self, dst, src):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -12785,7 +12785,7 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
 
   /* "View.MemoryView":431
  *             self.setitem_indexed(index, value)
- * 
+ *
  *     cdef is_slice(self, obj):             # <<<<<<<<<<<<<<
  *         if not isinstance(obj, memoryview):
  *             try:
@@ -12807,7 +12807,7 @@ static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_
 
 /* "View.MemoryView":441
  *         return obj
- * 
+ *
  *     cdef setitem_slice_assignment(self, dst, src):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice dst_slice
  *         cdef __Pyx_memviewslice src_slice
@@ -12831,7 +12831,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
 
   /* "View.MemoryView":445
  *         cdef __Pyx_memviewslice src_slice
- * 
+ *
  *         memoryview_copy_contents(get_slice_from_memview(src, &src_slice)[0],             # <<<<<<<<<<<<<<
  *                                  get_slice_from_memview(dst, &dst_slice)[0],
  *                                  src.ndim, dst.ndim, self.dtype_is_object)
@@ -12840,11 +12840,11 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
   __pyx_t_1 = __pyx_memoryview_get_slice_from_memoryview(((struct __pyx_memoryview_obj *)__pyx_v_src), (&__pyx_v_src_slice)); if (unlikely(__pyx_t_1 == ((__Pyx_memviewslice *)NULL))) __PYX_ERR(2, 445, __pyx_L1_error)
 
   /* "View.MemoryView":446
- * 
+ *
  *         memoryview_copy_contents(get_slice_from_memview(src, &src_slice)[0],
  *                                  get_slice_from_memview(dst, &dst_slice)[0],             # <<<<<<<<<<<<<<
  *                                  src.ndim, dst.ndim, self.dtype_is_object)
- * 
+ *
  */
   if (!(likely(((__pyx_v_dst) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_dst, __pyx_memoryview_type))))) __PYX_ERR(2, 446, __pyx_L1_error)
   __pyx_t_2 = __pyx_memoryview_get_slice_from_memoryview(((struct __pyx_memoryview_obj *)__pyx_v_dst), (&__pyx_v_dst_slice)); if (unlikely(__pyx_t_2 == ((__Pyx_memviewslice *)NULL))) __PYX_ERR(2, 446, __pyx_L1_error)
@@ -12853,7 +12853,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
  *         memoryview_copy_contents(get_slice_from_memview(src, &src_slice)[0],
  *                                  get_slice_from_memview(dst, &dst_slice)[0],
  *                                  src.ndim, dst.ndim, self.dtype_is_object)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef setitem_slice_assign_scalar(self, memoryview dst, value):
  */
   __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_src, __pyx_n_s_ndim); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 447, __pyx_L1_error)
@@ -12867,7 +12867,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
 
   /* "View.MemoryView":445
  *         cdef __Pyx_memviewslice src_slice
- * 
+ *
  *         memoryview_copy_contents(get_slice_from_memview(src, &src_slice)[0],             # <<<<<<<<<<<<<<
  *                                  get_slice_from_memview(dst, &dst_slice)[0],
  *                                  src.ndim, dst.ndim, self.dtype_is_object)
@@ -12876,7 +12876,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
 
   /* "View.MemoryView":441
  *         return obj
- * 
+ *
  *     cdef setitem_slice_assignment(self, dst, src):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice dst_slice
  *         cdef __Pyx_memviewslice src_slice
@@ -12897,7 +12897,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryvi
 
 /* "View.MemoryView":449
  *                                  src.ndim, dst.ndim, self.dtype_is_object)
- * 
+ *
  *     cdef setitem_slice_assign_scalar(self, memoryview dst, value):             # <<<<<<<<<<<<<<
  *         cdef int array[128]
  *         cdef void *tmp = NULL
@@ -12933,7 +12933,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
  *         cdef int array[128]
  *         cdef void *tmp = NULL             # <<<<<<<<<<<<<<
  *         cdef void *item
- * 
+ *
  */
   __pyx_v_tmp = NULL;
 
@@ -12941,7 +12941,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
  *         cdef __Pyx_memviewslice *dst_slice
  *         cdef __Pyx_memviewslice tmp_slice
  *         dst_slice = get_slice_from_memview(dst, &tmp_slice)             # <<<<<<<<<<<<<<
- * 
+ *
  *         if <size_t>self.view.itemsize > sizeof(array):
  */
   __pyx_t_1 = __pyx_memoryview_get_slice_from_memoryview(__pyx_v_dst, (&__pyx_v_tmp_slice)); if (unlikely(__pyx_t_1 == ((__Pyx_memviewslice *)NULL))) __PYX_ERR(2, 456, __pyx_L1_error)
@@ -12949,7 +12949,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
 
   /* "View.MemoryView":458
  *         dst_slice = get_slice_from_memview(dst, &tmp_slice)
- * 
+ *
  *         if <size_t>self.view.itemsize > sizeof(array):             # <<<<<<<<<<<<<<
  *             tmp = PyMem_Malloc(self.view.itemsize)
  *             if tmp == NULL:
@@ -12958,7 +12958,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
   if (__pyx_t_2) {
 
     /* "View.MemoryView":459
- * 
+ *
  *         if <size_t>self.view.itemsize > sizeof(array):
  *             tmp = PyMem_Malloc(self.view.itemsize)             # <<<<<<<<<<<<<<
  *             if tmp == NULL:
@@ -13005,7 +13005,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
 
     /* "View.MemoryView":458
  *         dst_slice = get_slice_from_memview(dst, &tmp_slice)
- * 
+ *
  *         if <size_t>self.view.itemsize > sizeof(array):             # <<<<<<<<<<<<<<
  *             tmp = PyMem_Malloc(self.view.itemsize)
  *             if tmp == NULL:
@@ -13017,7 +13017,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
  *             item = tmp
  *         else:
  *             item = <void *> array             # <<<<<<<<<<<<<<
- * 
+ *
  *         try:
  */
   /*else*/ {
@@ -13027,7 +13027,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
 
   /* "View.MemoryView":466
  *             item = <void *> array
- * 
+ *
  *         try:             # <<<<<<<<<<<<<<
  *             if self.dtype_is_object:
  *                 (<PyObject **> item)[0] = <PyObject *> value
@@ -13035,7 +13035,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
   /*try:*/ {
 
     /* "View.MemoryView":467
- * 
+ *
  *         try:
  *             if self.dtype_is_object:             # <<<<<<<<<<<<<<
  *                 (<PyObject **> item)[0] = <PyObject *> value
@@ -13054,7 +13054,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
       (((PyObject **)__pyx_v_item)[0]) = ((PyObject *)__pyx_v_value);
 
       /* "View.MemoryView":467
- * 
+ *
  *         try:
  *             if self.dtype_is_object:             # <<<<<<<<<<<<<<
  *                 (<PyObject **> item)[0] = <PyObject *> value
@@ -13067,8 +13067,8 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
  *                 (<PyObject **> item)[0] = <PyObject *> value
  *             else:
  *                 self.assign_item_from_object(<char *> item, value)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     /*else*/ {
       __pyx_t_3 = ((struct __pyx_vtabstruct_memoryview *)__pyx_v_self->__pyx_vtab)->assign_item_from_object(__pyx_v_self, ((char *)__pyx_v_item), __pyx_v_value); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 470, __pyx_L6_error)
@@ -13078,8 +13078,8 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
     __pyx_L8:;
 
     /* "View.MemoryView":474
- * 
- * 
+ *
+ *
  *             if self.view.suboffsets != NULL:             # <<<<<<<<<<<<<<
  *                 assert_direct_dimensions(self.view.suboffsets, self.view.ndim)
  *             slice_assign_scalar(dst_slice, dst.view.ndim, self.view.itemsize,
@@ -13088,7 +13088,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
     if (__pyx_t_2) {
 
       /* "View.MemoryView":475
- * 
+ *
  *             if self.view.suboffsets != NULL:
  *                 assert_direct_dimensions(self.view.suboffsets, self.view.ndim)             # <<<<<<<<<<<<<<
  *             slice_assign_scalar(dst_slice, dst.view.ndim, self.view.itemsize,
@@ -13099,8 +13099,8 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
       __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
 
       /* "View.MemoryView":474
- * 
- * 
+ *
+ *
  *             if self.view.suboffsets != NULL:             # <<<<<<<<<<<<<<
  *                 assert_direct_dimensions(self.view.suboffsets, self.view.ndim)
  *             slice_assign_scalar(dst_slice, dst.view.ndim, self.view.itemsize,
@@ -13121,7 +13121,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
  *                                 item, self.dtype_is_object)
  *         finally:
  *             PyMem_Free(tmp)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef setitem_indexed(self, index, value):
  */
   /*finally:*/ {
@@ -13166,7 +13166,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
 
   /* "View.MemoryView":449
  *                                  src.ndim, dst.ndim, self.dtype_is_object)
- * 
+ *
  *     cdef setitem_slice_assign_scalar(self, memoryview dst, value):             # <<<<<<<<<<<<<<
  *         cdef int array[128]
  *         cdef void *tmp = NULL
@@ -13187,7 +13187,7 @@ static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memor
 
 /* "View.MemoryView":481
  *             PyMem_Free(tmp)
- * 
+ *
  *     cdef setitem_indexed(self, index, value):             # <<<<<<<<<<<<<<
  *         cdef char *itemp = self.get_item_pointer(index)
  *         self.assign_item_from_object(itemp, value)
@@ -13205,11 +13205,11 @@ static PyObject *__pyx_memoryview_setitem_indexed(struct __pyx_memoryview_obj *_
   __Pyx_RefNannySetupContext("setitem_indexed", 0);
 
   /* "View.MemoryView":482
- * 
+ *
  *     cdef setitem_indexed(self, index, value):
  *         cdef char *itemp = self.get_item_pointer(index)             # <<<<<<<<<<<<<<
  *         self.assign_item_from_object(itemp, value)
- * 
+ *
  */
   __pyx_t_1 = ((struct __pyx_vtabstruct_memoryview *)__pyx_v_self->__pyx_vtab)->get_item_pointer(__pyx_v_self, __pyx_v_index); if (unlikely(__pyx_t_1 == ((char *)NULL))) __PYX_ERR(2, 482, __pyx_L1_error)
   __pyx_v_itemp = __pyx_t_1;
@@ -13218,7 +13218,7 @@ static PyObject *__pyx_memoryview_setitem_indexed(struct __pyx_memoryview_obj *_
  *     cdef setitem_indexed(self, index, value):
  *         cdef char *itemp = self.get_item_pointer(index)
  *         self.assign_item_from_object(itemp, value)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):
  */
   __pyx_t_2 = ((struct __pyx_vtabstruct_memoryview *)__pyx_v_self->__pyx_vtab)->assign_item_from_object(__pyx_v_self, __pyx_v_itemp, __pyx_v_value); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 483, __pyx_L1_error)
@@ -13227,7 +13227,7 @@ static PyObject *__pyx_memoryview_setitem_indexed(struct __pyx_memoryview_obj *_
 
   /* "View.MemoryView":481
  *             PyMem_Free(tmp)
- * 
+ *
  *     cdef setitem_indexed(self, index, value):             # <<<<<<<<<<<<<<
  *         cdef char *itemp = self.get_item_pointer(index)
  *         self.assign_item_from_object(itemp, value)
@@ -13248,7 +13248,7 @@ static PyObject *__pyx_memoryview_setitem_indexed(struct __pyx_memoryview_obj *_
 
 /* "View.MemoryView":485
  *         self.assign_item_from_object(itemp, value)
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):             # <<<<<<<<<<<<<<
  *         """Only used if instantiated manually by the user, or if Cython doesn't
  *         know how to convert the type"""
@@ -13281,7 +13281,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
  *         know how to convert the type"""
  *         import struct             # <<<<<<<<<<<<<<
  *         cdef bytes bytesitem
- * 
+ *
  */
   __pyx_t_1 = __Pyx_Import(__pyx_n_s_struct, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 488, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -13290,7 +13290,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
 
   /* "View.MemoryView":491
  *         cdef bytes bytesitem
- * 
+ *
  *         bytesitem = itemp[:self.view.itemsize]             # <<<<<<<<<<<<<<
  *         try:
  *             result = struct.unpack(self.view.format, bytesitem)
@@ -13301,7 +13301,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
   __pyx_t_1 = 0;
 
   /* "View.MemoryView":492
- * 
+ *
  *         bytesitem = itemp[:self.view.itemsize]
  *         try:             # <<<<<<<<<<<<<<
  *             result = struct.unpack(self.view.format, bytesitem)
@@ -13378,7 +13378,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
       __pyx_t_1 = 0;
 
       /* "View.MemoryView":492
- * 
+ *
  *         bytesitem = itemp[:self.view.itemsize]
  *         try:             # <<<<<<<<<<<<<<
  *             result = struct.unpack(self.view.format, bytesitem)
@@ -13394,7 +13394,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
  *             return result
  */
     /*else:*/ {
-      __pyx_t_10 = strlen(__pyx_v_self->view.format); 
+      __pyx_t_10 = strlen(__pyx_v_self->view.format);
       __pyx_t_11 = ((__pyx_t_10 == 1) != 0);
       if (__pyx_t_11) {
 
@@ -13403,7 +13403,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
  *             if len(self.view.format) == 1:
  *                 return result[0]             # <<<<<<<<<<<<<<
  *             return result
- * 
+ *
  */
         __Pyx_XDECREF(__pyx_r);
         __pyx_t_1 = __Pyx_GetItemInt(__pyx_v_result, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 498, __pyx_L5_except_error)
@@ -13425,7 +13425,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
  *             if len(self.view.format) == 1:
  *                 return result[0]
  *             return result             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):
  */
       __Pyx_XDECREF(__pyx_r);
@@ -13478,7 +13478,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
     __pyx_L5_except_error:;
 
     /* "View.MemoryView":492
- * 
+ *
  *         bytesitem = itemp[:self.view.itemsize]
  *         try:             # <<<<<<<<<<<<<<
  *             result = struct.unpack(self.view.format, bytesitem)
@@ -13499,7 +13499,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
 
   /* "View.MemoryView":485
  *         self.assign_item_from_object(itemp, value)
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):             # <<<<<<<<<<<<<<
  *         """Only used if instantiated manually by the user, or if Cython doesn't
  *         know how to convert the type"""
@@ -13525,7 +13525,7 @@ static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview
 
 /* "View.MemoryView":501
  *             return result
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):             # <<<<<<<<<<<<<<
  *         """Only used if instantiated manually by the user, or if Cython doesn't
  *         know how to convert the type"""
@@ -13571,17 +13571,17 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
 
   /* "View.MemoryView":509
  *         cdef Py_ssize_t i
- * 
+ *
  *         if isinstance(value, tuple):             # <<<<<<<<<<<<<<
  *             bytesvalue = struct.pack(self.view.format, *value)
  *         else:
  */
-  __pyx_t_2 = PyTuple_Check(__pyx_v_value); 
+  __pyx_t_2 = PyTuple_Check(__pyx_v_value);
   __pyx_t_3 = (__pyx_t_2 != 0);
   if (__pyx_t_3) {
 
     /* "View.MemoryView":510
- * 
+ *
  *         if isinstance(value, tuple):
  *             bytesvalue = struct.pack(self.view.format, *value)             # <<<<<<<<<<<<<<
  *         else:
@@ -13612,7 +13612,7 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
 
     /* "View.MemoryView":509
  *         cdef Py_ssize_t i
- * 
+ *
  *         if isinstance(value, tuple):             # <<<<<<<<<<<<<<
  *             bytesvalue = struct.pack(self.view.format, *value)
  *         else:
@@ -13624,7 +13624,7 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
  *             bytesvalue = struct.pack(self.view.format, *value)
  *         else:
  *             bytesvalue = struct.pack(self.view.format, value)             # <<<<<<<<<<<<<<
- * 
+ *
  *         for i, c in enumerate(bytesvalue):
  */
   /*else*/ {
@@ -13687,10 +13687,10 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
 
   /* "View.MemoryView":514
  *             bytesvalue = struct.pack(self.view.format, value)
- * 
+ *
  *         for i, c in enumerate(bytesvalue):             # <<<<<<<<<<<<<<
  *             itemp[i] = c
- * 
+ *
  */
   __pyx_t_9 = 0;
   if (unlikely(__pyx_v_bytesvalue == Py_None)) {
@@ -13706,28 +13706,28 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
     __pyx_v_c = (__pyx_t_11[0]);
 
     /* "View.MemoryView":515
- * 
+ *
  *         for i, c in enumerate(bytesvalue):
  *             itemp[i] = c             # <<<<<<<<<<<<<<
- * 
+ *
  *     @cname('getbuffer')
  */
     __pyx_v_i = __pyx_t_9;
 
     /* "View.MemoryView":514
  *             bytesvalue = struct.pack(self.view.format, value)
- * 
+ *
  *         for i, c in enumerate(bytesvalue):             # <<<<<<<<<<<<<<
  *             itemp[i] = c
- * 
+ *
  */
     __pyx_t_9 = (__pyx_t_9 + 1);
 
     /* "View.MemoryView":515
- * 
+ *
  *         for i, c in enumerate(bytesvalue):
  *             itemp[i] = c             # <<<<<<<<<<<<<<
- * 
+ *
  *     @cname('getbuffer')
  */
     (__pyx_v_itemp[__pyx_v_i]) = __pyx_v_c;
@@ -13736,7 +13736,7 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
 
   /* "View.MemoryView":501
  *             return result
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):             # <<<<<<<<<<<<<<
  *         """Only used if instantiated manually by the user, or if Cython doesn't
  *         know how to convert the type"""
@@ -13763,7 +13763,7 @@ static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryvie
 }
 
 /* "View.MemoryView":518
- * 
+ *
  *     @cname('getbuffer')
  *     def __getbuffer__(self, Py_buffer *info, int flags):             # <<<<<<<<<<<<<<
  *         if flags & PyBUF_WRITABLE and self.view.readonly:
@@ -13810,7 +13810,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *     def __getbuffer__(self, Py_buffer *info, int flags):
  *         if flags & PyBUF_WRITABLE and self.view.readonly:             # <<<<<<<<<<<<<<
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")
- * 
+ *
  */
   __pyx_t_2 = ((__pyx_v_flags & PyBUF_WRITABLE) != 0);
   if (__pyx_t_2) {
@@ -13827,7 +13827,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *     def __getbuffer__(self, Py_buffer *info, int flags):
  *         if flags & PyBUF_WRITABLE and self.view.readonly:
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_ND:
  */
     __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__21, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 520, __pyx_L1_error)
@@ -13841,13 +13841,13 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *     def __getbuffer__(self, Py_buffer *info, int flags):
  *         if flags & PyBUF_WRITABLE and self.view.readonly:             # <<<<<<<<<<<<<<
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")
- * 
+ *
  */
   }
 
   /* "View.MemoryView":522
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")
- * 
+ *
  *         if flags & PyBUF_ND:             # <<<<<<<<<<<<<<
  *             info.shape = self.view.shape
  *         else:
@@ -13856,7 +13856,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   if (__pyx_t_1) {
 
     /* "View.MemoryView":523
- * 
+ *
  *         if flags & PyBUF_ND:
  *             info.shape = self.view.shape             # <<<<<<<<<<<<<<
  *         else:
@@ -13867,7 +13867,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
     /* "View.MemoryView":522
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")
- * 
+ *
  *         if flags & PyBUF_ND:             # <<<<<<<<<<<<<<
  *             info.shape = self.view.shape
  *         else:
@@ -13879,7 +13879,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *             info.shape = self.view.shape
  *         else:
  *             info.shape = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_STRIDES:
  */
   /*else*/ {
@@ -13889,7 +13889,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
   /* "View.MemoryView":527
  *             info.shape = NULL
- * 
+ *
  *         if flags & PyBUF_STRIDES:             # <<<<<<<<<<<<<<
  *             info.strides = self.view.strides
  *         else:
@@ -13898,7 +13898,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   if (__pyx_t_1) {
 
     /* "View.MemoryView":528
- * 
+ *
  *         if flags & PyBUF_STRIDES:
  *             info.strides = self.view.strides             # <<<<<<<<<<<<<<
  *         else:
@@ -13909,7 +13909,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
     /* "View.MemoryView":527
  *             info.shape = NULL
- * 
+ *
  *         if flags & PyBUF_STRIDES:             # <<<<<<<<<<<<<<
  *             info.strides = self.view.strides
  *         else:
@@ -13921,7 +13921,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *             info.strides = self.view.strides
  *         else:
  *             info.strides = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_INDIRECT:
  */
   /*else*/ {
@@ -13931,7 +13931,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
   /* "View.MemoryView":532
  *             info.strides = NULL
- * 
+ *
  *         if flags & PyBUF_INDIRECT:             # <<<<<<<<<<<<<<
  *             info.suboffsets = self.view.suboffsets
  *         else:
@@ -13940,7 +13940,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   if (__pyx_t_1) {
 
     /* "View.MemoryView":533
- * 
+ *
  *         if flags & PyBUF_INDIRECT:
  *             info.suboffsets = self.view.suboffsets             # <<<<<<<<<<<<<<
  *         else:
@@ -13951,7 +13951,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
     /* "View.MemoryView":532
  *             info.strides = NULL
- * 
+ *
  *         if flags & PyBUF_INDIRECT:             # <<<<<<<<<<<<<<
  *             info.suboffsets = self.view.suboffsets
  *         else:
@@ -13963,7 +13963,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *             info.suboffsets = self.view.suboffsets
  *         else:
  *             info.suboffsets = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  */
   /*else*/ {
@@ -13973,7 +13973,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
   /* "View.MemoryView":537
  *             info.suboffsets = NULL
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             info.format = self.view.format
  *         else:
@@ -13982,7 +13982,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   if (__pyx_t_1) {
 
     /* "View.MemoryView":538
- * 
+ *
  *         if flags & PyBUF_FORMAT:
  *             info.format = self.view.format             # <<<<<<<<<<<<<<
  *         else:
@@ -13993,7 +13993,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
     /* "View.MemoryView":537
  *             info.suboffsets = NULL
- * 
+ *
  *         if flags & PyBUF_FORMAT:             # <<<<<<<<<<<<<<
  *             info.format = self.view.format
  *         else:
@@ -14005,7 +14005,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *             info.format = self.view.format
  *         else:
  *             info.format = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *         info.buf = self.view.buf
  */
   /*else*/ {
@@ -14015,7 +14015,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 
   /* "View.MemoryView":542
  *             info.format = NULL
- * 
+ *
  *         info.buf = self.view.buf             # <<<<<<<<<<<<<<
  *         info.ndim = self.view.ndim
  *         info.itemsize = self.view.itemsize
@@ -14024,7 +14024,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   __pyx_v_info->buf = __pyx_t_6;
 
   /* "View.MemoryView":543
- * 
+ *
  *         info.buf = self.view.buf
  *         info.ndim = self.view.ndim             # <<<<<<<<<<<<<<
  *         info.itemsize = self.view.itemsize
@@ -14058,7 +14058,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *         info.len = self.view.len
  *         info.readonly = self.view.readonly             # <<<<<<<<<<<<<<
  *         info.obj = self
- * 
+ *
  */
   __pyx_t_1 = __pyx_v_self->view.readonly;
   __pyx_v_info->readonly = __pyx_t_1;
@@ -14067,7 +14067,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
  *         info.len = self.view.len
  *         info.readonly = self.view.readonly
  *         info.obj = self             # <<<<<<<<<<<<<<
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)")
  */
   __Pyx_INCREF(((PyObject *)__pyx_v_self));
@@ -14077,7 +14077,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
   __pyx_v_info->obj = ((PyObject *)__pyx_v_self);
 
   /* "View.MemoryView":518
- * 
+ *
  *     @cname('getbuffer')
  *     def __getbuffer__(self, Py_buffer *info, int flags):             # <<<<<<<<<<<<<<
  *         if flags & PyBUF_WRITABLE and self.view.readonly:
@@ -14107,7 +14107,7 @@ static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbu
 }
 
 /* "View.MemoryView":553
- * 
+ *
  *     @property
  *     def T(self):             # <<<<<<<<<<<<<<
  *         cdef _memoryviewslice result = memoryview_copy(self)
@@ -14156,7 +14156,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_1T___get__(struct _
  *         cdef _memoryviewslice result = memoryview_copy(self)
  *         transpose_memslice(&result.from_slice)             # <<<<<<<<<<<<<<
  *         return result
- * 
+ *
  */
   __pyx_t_2 = __pyx_memslice_transpose((&__pyx_v_result->from_slice)); if (unlikely(__pyx_t_2 == ((int)0))) __PYX_ERR(2, 555, __pyx_L1_error)
 
@@ -14164,7 +14164,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_1T___get__(struct _
  *         cdef _memoryviewslice result = memoryview_copy(self)
  *         transpose_memslice(&result.from_slice)
  *         return result             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14173,7 +14173,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_1T___get__(struct _
   goto __pyx_L0;
 
   /* "View.MemoryView":553
- * 
+ *
  *     @property
  *     def T(self):             # <<<<<<<<<<<<<<
  *         cdef _memoryviewslice result = memoryview_copy(self)
@@ -14193,11 +14193,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_1T___get__(struct _
 }
 
 /* "View.MemoryView":559
- * 
+ *
  *     @property
  *     def base(self):             # <<<<<<<<<<<<<<
  *         return self.obj
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14222,7 +14222,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4base___get__(struc
  *     @property
  *     def base(self):
  *         return self.obj             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14231,11 +14231,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4base___get__(struc
   goto __pyx_L0;
 
   /* "View.MemoryView":559
- * 
+ *
  *     @property
  *     def base(self):             # <<<<<<<<<<<<<<
  *         return self.obj
- * 
+ *
  */
 
   /* function exit code */
@@ -14246,11 +14246,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4base___get__(struc
 }
 
 /* "View.MemoryView":563
- * 
+ *
  *     @property
  *     def shape(self):             # <<<<<<<<<<<<<<
  *         return tuple([length for length in self.view.shape[:self.view.ndim]])
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14284,7 +14284,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_5shape___get__(stru
  *     @property
  *     def shape(self):
  *         return tuple([length for length in self.view.shape[:self.view.ndim]])             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14307,11 +14307,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_5shape___get__(stru
   goto __pyx_L0;
 
   /* "View.MemoryView":563
- * 
+ *
  *     @property
  *     def shape(self):             # <<<<<<<<<<<<<<
  *         return tuple([length for length in self.view.shape[:self.view.ndim]])
- * 
+ *
  */
 
   /* function exit code */
@@ -14327,11 +14327,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_5shape___get__(stru
 }
 
 /* "View.MemoryView":567
- * 
+ *
  *     @property
  *     def strides(self):             # <<<<<<<<<<<<<<
  *         if self.view.strides == NULL:
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14366,7 +14366,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(st
  *     @property
  *     def strides(self):
  *         if self.view.strides == NULL:             # <<<<<<<<<<<<<<
- * 
+ *
  *             raise ValueError("Buffer view does not expose strides")
  */
   __pyx_t_1 = ((__pyx_v_self->view.strides == NULL) != 0);
@@ -14374,9 +14374,9 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(st
 
     /* "View.MemoryView":570
  *         if self.view.strides == NULL:
- * 
+ *
  *             raise ValueError("Buffer view does not expose strides")             # <<<<<<<<<<<<<<
- * 
+ *
  *         return tuple([stride for stride in self.view.strides[:self.view.ndim]])
  */
     __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__22, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 570, __pyx_L1_error)
@@ -14389,16 +14389,16 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(st
  *     @property
  *     def strides(self):
  *         if self.view.strides == NULL:             # <<<<<<<<<<<<<<
- * 
+ *
  *             raise ValueError("Buffer view does not expose strides")
  */
   }
 
   /* "View.MemoryView":572
  *             raise ValueError("Buffer view does not expose strides")
- * 
+ *
  *         return tuple([stride for stride in self.view.strides[:self.view.ndim]])             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14421,11 +14421,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(st
   goto __pyx_L0;
 
   /* "View.MemoryView":567
- * 
+ *
  *     @property
  *     def strides(self):             # <<<<<<<<<<<<<<
  *         if self.view.strides == NULL:
- * 
+ *
  */
 
   /* function exit code */
@@ -14441,7 +14441,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(st
 }
 
 /* "View.MemoryView":575
- * 
+ *
  *     @property
  *     def suboffsets(self):             # <<<<<<<<<<<<<<
  *         if self.view.suboffsets == NULL:
@@ -14481,7 +14481,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get_
  *     def suboffsets(self):
  *         if self.view.suboffsets == NULL:             # <<<<<<<<<<<<<<
  *             return (-1,) * self.view.ndim
- * 
+ *
  */
   __pyx_t_1 = ((__pyx_v_self->view.suboffsets == NULL) != 0);
   if (__pyx_t_1) {
@@ -14490,7 +14490,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get_
  *     def suboffsets(self):
  *         if self.view.suboffsets == NULL:
  *             return (-1,) * self.view.ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *         return tuple([suboffset for suboffset in self.view.suboffsets[:self.view.ndim]])
  */
     __Pyx_XDECREF(__pyx_r);
@@ -14508,15 +14508,15 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get_
  *     def suboffsets(self):
  *         if self.view.suboffsets == NULL:             # <<<<<<<<<<<<<<
  *             return (-1,) * self.view.ndim
- * 
+ *
  */
   }
 
   /* "View.MemoryView":579
  *             return (-1,) * self.view.ndim
- * 
+ *
  *         return tuple([suboffset for suboffset in self.view.suboffsets[:self.view.ndim]])             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14539,7 +14539,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get_
   goto __pyx_L0;
 
   /* "View.MemoryView":575
- * 
+ *
  *     @property
  *     def suboffsets(self):             # <<<<<<<<<<<<<<
  *         if self.view.suboffsets == NULL:
@@ -14559,11 +14559,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get_
 }
 
 /* "View.MemoryView":582
- * 
+ *
  *     @property
  *     def ndim(self):             # <<<<<<<<<<<<<<
  *         return self.view.ndim
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14592,7 +14592,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4ndim___get__(struc
  *     @property
  *     def ndim(self):
  *         return self.view.ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14603,11 +14603,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4ndim___get__(struc
   goto __pyx_L0;
 
   /* "View.MemoryView":582
- * 
+ *
  *     @property
  *     def ndim(self):             # <<<<<<<<<<<<<<
  *         return self.view.ndim
- * 
+ *
  */
 
   /* function exit code */
@@ -14622,11 +14622,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4ndim___get__(struc
 }
 
 /* "View.MemoryView":586
- * 
+ *
  *     @property
  *     def itemsize(self):             # <<<<<<<<<<<<<<
  *         return self.view.itemsize
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14655,7 +14655,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_8itemsize___get__(s
  *     @property
  *     def itemsize(self):
  *         return self.view.itemsize             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14666,11 +14666,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_8itemsize___get__(s
   goto __pyx_L0;
 
   /* "View.MemoryView":586
- * 
+ *
  *     @property
  *     def itemsize(self):             # <<<<<<<<<<<<<<
  *         return self.view.itemsize
- * 
+ *
  */
 
   /* function exit code */
@@ -14685,11 +14685,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_8itemsize___get__(s
 }
 
 /* "View.MemoryView":590
- * 
+ *
  *     @property
  *     def nbytes(self):             # <<<<<<<<<<<<<<
  *         return self.size * self.view.itemsize
- * 
+ *
  */
 
 /* Python wrapper */
@@ -14720,7 +14720,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_6nbytes___get__(str
  *     @property
  *     def nbytes(self):
  *         return self.size * self.view.itemsize             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14737,11 +14737,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_6nbytes___get__(str
   goto __pyx_L0;
 
   /* "View.MemoryView":590
- * 
+ *
  *     @property
  *     def nbytes(self):             # <<<<<<<<<<<<<<
  *         return self.size * self.view.itemsize
- * 
+ *
  */
 
   /* function exit code */
@@ -14758,7 +14758,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_6nbytes___get__(str
 }
 
 /* "View.MemoryView":594
- * 
+ *
  *     @property
  *     def size(self):             # <<<<<<<<<<<<<<
  *         if self._size is None:
@@ -14799,7 +14799,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
  *     def size(self):
  *         if self._size is None:             # <<<<<<<<<<<<<<
  *             result = 1
- * 
+ *
  */
   __pyx_t_1 = (__pyx_v_self->_size == Py_None);
   __pyx_t_2 = (__pyx_t_1 != 0);
@@ -14809,7 +14809,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
  *     def size(self):
  *         if self._size is None:
  *             result = 1             # <<<<<<<<<<<<<<
- * 
+ *
  *             for length in self.view.shape[:self.view.ndim]:
  */
     __Pyx_INCREF(__pyx_int_1);
@@ -14817,10 +14817,10 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
 
     /* "View.MemoryView":598
  *             result = 1
- * 
+ *
  *             for length in self.view.shape[:self.view.ndim]:             # <<<<<<<<<<<<<<
  *                 result *= length
- * 
+ *
  */
     __pyx_t_4 = (__pyx_v_self->view.shape + __pyx_v_self->view.ndim);
     for (__pyx_t_5 = __pyx_v_self->view.shape; __pyx_t_5 < __pyx_t_4; __pyx_t_5++) {
@@ -14831,10 +14831,10 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
       __pyx_t_6 = 0;
 
       /* "View.MemoryView":599
- * 
+ *
  *             for length in self.view.shape[:self.view.ndim]:
  *                 result *= length             # <<<<<<<<<<<<<<
- * 
+ *
  *             self._size = result
  */
       __pyx_t_6 = PyNumber_InPlaceMultiply(__pyx_v_result, __pyx_v_length); if (unlikely(!__pyx_t_6)) __PYX_ERR(2, 599, __pyx_L1_error)
@@ -14845,9 +14845,9 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
 
     /* "View.MemoryView":601
  *                 result *= length
- * 
+ *
  *             self._size = result             # <<<<<<<<<<<<<<
- * 
+ *
  *         return self._size
  */
     __Pyx_INCREF(__pyx_v_result);
@@ -14861,15 +14861,15 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
  *     def size(self):
  *         if self._size is None:             # <<<<<<<<<<<<<<
  *             result = 1
- * 
+ *
  */
   }
 
   /* "View.MemoryView":603
  *             self._size = result
- * 
+ *
  *         return self._size             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __len__(self):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -14878,7 +14878,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
   goto __pyx_L0;
 
   /* "View.MemoryView":594
- * 
+ *
  *     @property
  *     def size(self):             # <<<<<<<<<<<<<<
  *         if self._size is None:
@@ -14900,7 +14900,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struc
 
 /* "View.MemoryView":605
  *         return self._size
- * 
+ *
  *     def __len__(self):             # <<<<<<<<<<<<<<
  *         if self.view.ndim >= 1:
  *             return self.view.shape[0]
@@ -14926,11 +14926,11 @@ static Py_ssize_t __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_1
   __Pyx_RefNannySetupContext("__len__", 0);
 
   /* "View.MemoryView":606
- * 
+ *
  *     def __len__(self):
  *         if self.view.ndim >= 1:             # <<<<<<<<<<<<<<
  *             return self.view.shape[0]
- * 
+ *
  */
   __pyx_t_1 = ((__pyx_v_self->view.ndim >= 1) != 0);
   if (__pyx_t_1) {
@@ -14939,26 +14939,26 @@ static Py_ssize_t __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_1
  *     def __len__(self):
  *         if self.view.ndim >= 1:
  *             return self.view.shape[0]             # <<<<<<<<<<<<<<
- * 
+ *
  *         return 0
  */
     __pyx_r = (__pyx_v_self->view.shape[0]);
     goto __pyx_L0;
 
     /* "View.MemoryView":606
- * 
+ *
  *     def __len__(self):
  *         if self.view.ndim >= 1:             # <<<<<<<<<<<<<<
  *             return self.view.shape[0]
- * 
+ *
  */
   }
 
   /* "View.MemoryView":609
  *             return self.view.shape[0]
- * 
+ *
  *         return 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __repr__(self):
  */
   __pyx_r = 0;
@@ -14966,7 +14966,7 @@ static Py_ssize_t __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_1
 
   /* "View.MemoryView":605
  *         return self._size
- * 
+ *
  *     def __len__(self):             # <<<<<<<<<<<<<<
  *         if self.view.ndim >= 1:
  *             return self.view.shape[0]
@@ -14980,7 +14980,7 @@ static Py_ssize_t __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_1
 
 /* "View.MemoryView":611
  *         return 0
- * 
+ *
  *     def __repr__(self):             # <<<<<<<<<<<<<<
  *         return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__,
  *                                                id(self))
@@ -15011,11 +15011,11 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_12
   __Pyx_RefNannySetupContext("__repr__", 0);
 
   /* "View.MemoryView":612
- * 
+ *
  *     def __repr__(self):
  *         return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__,             # <<<<<<<<<<<<<<
  *                                                id(self))
- * 
+ *
  */
   __Pyx_XDECREF(__pyx_r);
   __pyx_t_1 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_base); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 612, __pyx_L1_error)
@@ -15031,18 +15031,18 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_12
  *     def __repr__(self):
  *         return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__,
  *                                                id(self))             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __str__(self):
  */
   __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_builtin_id, ((PyObject *)__pyx_v_self)); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 613, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
 
   /* "View.MemoryView":612
- * 
+ *
  *     def __repr__(self):
  *         return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__,             # <<<<<<<<<<<<<<
  *                                                id(self))
- * 
+ *
  */
   __pyx_t_3 = PyTuple_New(2); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 612, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
@@ -15061,7 +15061,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_12
 
   /* "View.MemoryView":611
  *         return 0
- * 
+ *
  *     def __repr__(self):             # <<<<<<<<<<<<<<
  *         return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__,
  *                                                id(self))
@@ -15082,10 +15082,10 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_12
 
 /* "View.MemoryView":615
  *                                                id(self))
- * 
+ *
  *     def __str__(self):             # <<<<<<<<<<<<<<
  *         return "<MemoryView of %r object>" % (self.base.__class__.__name__,)
- * 
+ *
  */
 
 /* Python wrapper */
@@ -15112,11 +15112,11 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_14
   __Pyx_RefNannySetupContext("__str__", 0);
 
   /* "View.MemoryView":616
- * 
+ *
  *     def __str__(self):
  *         return "<MemoryView of %r object>" % (self.base.__class__.__name__,)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __Pyx_XDECREF(__pyx_r);
   __pyx_t_1 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_base); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 616, __pyx_L1_error)
@@ -15141,10 +15141,10 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_14
 
   /* "View.MemoryView":615
  *                                                id(self))
- * 
+ *
  *     def __str__(self):             # <<<<<<<<<<<<<<
  *         return "<MemoryView of %r object>" % (self.base.__class__.__name__,)
- * 
+ *
  */
 
   /* function exit code */
@@ -15160,8 +15160,8 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_14
 }
 
 /* "View.MemoryView":619
- * 
- * 
+ *
+ *
  *     def is_c_contig(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice *mslice
  *         cdef __Pyx_memviewslice tmp
@@ -15197,7 +15197,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_16
  *         cdef __Pyx_memviewslice tmp
  *         mslice = get_slice_from_memview(self, &tmp)             # <<<<<<<<<<<<<<
  *         return slice_is_contig(mslice[0], 'C', self.view.ndim)
- * 
+ *
  */
   __pyx_t_1 = __pyx_memoryview_get_slice_from_memoryview(__pyx_v_self, (&__pyx_v_tmp)); if (unlikely(__pyx_t_1 == ((__Pyx_memviewslice *)NULL))) __PYX_ERR(2, 622, __pyx_L1_error)
   __pyx_v_mslice = __pyx_t_1;
@@ -15206,7 +15206,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_16
  *         cdef __Pyx_memviewslice tmp
  *         mslice = get_slice_from_memview(self, &tmp)
  *         return slice_is_contig(mslice[0], 'C', self.view.ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def is_f_contig(self):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -15217,8 +15217,8 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_16
   goto __pyx_L0;
 
   /* "View.MemoryView":619
- * 
- * 
+ *
+ *
  *     def is_c_contig(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice *mslice
  *         cdef __Pyx_memviewslice tmp
@@ -15237,7 +15237,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_16
 
 /* "View.MemoryView":625
  *         return slice_is_contig(mslice[0], 'C', self.view.ndim)
- * 
+ *
  *     def is_f_contig(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice *mslice
  *         cdef __Pyx_memviewslice tmp
@@ -15273,7 +15273,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_18
  *         cdef __Pyx_memviewslice tmp
  *         mslice = get_slice_from_memview(self, &tmp)             # <<<<<<<<<<<<<<
  *         return slice_is_contig(mslice[0], 'F', self.view.ndim)
- * 
+ *
  */
   __pyx_t_1 = __pyx_memoryview_get_slice_from_memoryview(__pyx_v_self, (&__pyx_v_tmp)); if (unlikely(__pyx_t_1 == ((__Pyx_memviewslice *)NULL))) __PYX_ERR(2, 628, __pyx_L1_error)
   __pyx_v_mslice = __pyx_t_1;
@@ -15282,7 +15282,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_18
  *         cdef __Pyx_memviewslice tmp
  *         mslice = get_slice_from_memview(self, &tmp)
  *         return slice_is_contig(mslice[0], 'F', self.view.ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def copy(self):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -15294,7 +15294,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_18
 
   /* "View.MemoryView":625
  *         return slice_is_contig(mslice[0], 'C', self.view.ndim)
- * 
+ *
  *     def is_f_contig(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice *mslice
  *         cdef __Pyx_memviewslice tmp
@@ -15313,7 +15313,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_18
 
 /* "View.MemoryView":631
  *         return slice_is_contig(mslice[0], 'F', self.view.ndim)
- * 
+ *
  *     def copy(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice mslice
  *         cdef int flags = self.flags & ~PyBUF_F_CONTIGUOUS
@@ -15348,14 +15348,14 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20
  *     def copy(self):
  *         cdef __Pyx_memviewslice mslice
  *         cdef int flags = self.flags & ~PyBUF_F_CONTIGUOUS             # <<<<<<<<<<<<<<
- * 
+ *
  *         slice_copy(self, &mslice)
  */
   __pyx_v_flags = (__pyx_v_self->flags & (~PyBUF_F_CONTIGUOUS));
 
   /* "View.MemoryView":635
  *         cdef int flags = self.flags & ~PyBUF_F_CONTIGUOUS
- * 
+ *
  *         slice_copy(self, &mslice)             # <<<<<<<<<<<<<<
  *         mslice = slice_copy_contig(&mslice, "c", self.view.ndim,
  *                                    self.view.itemsize,
@@ -15363,7 +15363,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20
   __pyx_memoryview_slice_copy(__pyx_v_self, (&__pyx_v_mslice));
 
   /* "View.MemoryView":636
- * 
+ *
  *         slice_copy(self, &mslice)
  *         mslice = slice_copy_contig(&mslice, "c", self.view.ndim,             # <<<<<<<<<<<<<<
  *                                    self.view.itemsize,
@@ -15374,9 +15374,9 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20
 
   /* "View.MemoryView":641
  *                                    self.dtype_is_object)
- * 
+ *
  *         return memoryview_copy_from_slice(self, &mslice)             # <<<<<<<<<<<<<<
- * 
+ *
  *     def copy_fortran(self):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -15388,7 +15388,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20
 
   /* "View.MemoryView":631
  *         return slice_is_contig(mslice[0], 'F', self.view.ndim)
- * 
+ *
  *     def copy(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice mslice
  *         cdef int flags = self.flags & ~PyBUF_F_CONTIGUOUS
@@ -15407,7 +15407,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20
 
 /* "View.MemoryView":643
  *         return memoryview_copy_from_slice(self, &mslice)
- * 
+ *
  *     def copy_fortran(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice src, dst
  *         cdef int flags = self.flags & ~PyBUF_C_CONTIGUOUS
@@ -15443,14 +15443,14 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_22
  *     def copy_fortran(self):
  *         cdef __Pyx_memviewslice src, dst
  *         cdef int flags = self.flags & ~PyBUF_C_CONTIGUOUS             # <<<<<<<<<<<<<<
- * 
+ *
  *         slice_copy(self, &src)
  */
   __pyx_v_flags = (__pyx_v_self->flags & (~PyBUF_C_CONTIGUOUS));
 
   /* "View.MemoryView":647
  *         cdef int flags = self.flags & ~PyBUF_C_CONTIGUOUS
- * 
+ *
  *         slice_copy(self, &src)             # <<<<<<<<<<<<<<
  *         dst = slice_copy_contig(&src, "fortran", self.view.ndim,
  *                                 self.view.itemsize,
@@ -15458,7 +15458,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_22
   __pyx_memoryview_slice_copy(__pyx_v_self, (&__pyx_v_src));
 
   /* "View.MemoryView":648
- * 
+ *
  *         slice_copy(self, &src)
  *         dst = slice_copy_contig(&src, "fortran", self.view.ndim,             # <<<<<<<<<<<<<<
  *                                 self.view.itemsize,
@@ -15469,10 +15469,10 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_22
 
   /* "View.MemoryView":653
  *                                 self.dtype_is_object)
- * 
+ *
  *         return memoryview_copy_from_slice(self, &dst)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __Pyx_XDECREF(__pyx_r);
   __pyx_t_2 = __pyx_memoryview_copy_object_from_slice(__pyx_v_self, (&__pyx_v_dst)); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 653, __pyx_L1_error)
@@ -15483,7 +15483,7 @@ static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_22
 
   /* "View.MemoryView":643
  *         return memoryview_copy_from_slice(self, &mslice)
- * 
+ *
  *     def copy_fortran(self):             # <<<<<<<<<<<<<<
  *         cdef __Pyx_memviewslice src, dst
  *         cdef int flags = self.flags & ~PyBUF_C_CONTIGUOUS
@@ -15614,7 +15614,7 @@ static PyObject *__pyx_pf___pyx_memoryview_2__setstate_cython__(CYTHON_UNUSED st
 }
 
 /* "View.MemoryView":657
- * 
+ *
  * @cname('__pyx_memoryview_new')
  * cdef memoryview_cwrapper(object o, int flags, bint dtype_is_object, __Pyx_TypeInfo *typeinfo):             # <<<<<<<<<<<<<<
  *     cdef memoryview result = memoryview(o, flags, dtype_is_object)
@@ -15666,7 +15666,7 @@ static PyObject *__pyx_memoryview_new(PyObject *__pyx_v_o, int __pyx_v_flags, in
  *     cdef memoryview result = memoryview(o, flags, dtype_is_object)
  *     result.typeinfo = typeinfo             # <<<<<<<<<<<<<<
  *     return result
- * 
+ *
  */
   __pyx_v_result->typeinfo = __pyx_v_typeinfo;
 
@@ -15674,7 +15674,7 @@ static PyObject *__pyx_memoryview_new(PyObject *__pyx_v_o, int __pyx_v_flags, in
  *     cdef memoryview result = memoryview(o, flags, dtype_is_object)
  *     result.typeinfo = typeinfo
  *     return result             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_check')
  */
   __Pyx_XDECREF(__pyx_r);
@@ -15683,7 +15683,7 @@ static PyObject *__pyx_memoryview_new(PyObject *__pyx_v_o, int __pyx_v_flags, in
   goto __pyx_L0;
 
   /* "View.MemoryView":657
- * 
+ *
  * @cname('__pyx_memoryview_new')
  * cdef memoryview_cwrapper(object o, int flags, bint dtype_is_object, __Pyx_TypeInfo *typeinfo):             # <<<<<<<<<<<<<<
  *     cdef memoryview result = memoryview(o, flags, dtype_is_object)
@@ -15705,11 +15705,11 @@ static PyObject *__pyx_memoryview_new(PyObject *__pyx_v_o, int __pyx_v_flags, in
 }
 
 /* "View.MemoryView":663
- * 
+ *
  * @cname('__pyx_memoryview_check')
  * cdef inline bint memoryview_check(object o):             # <<<<<<<<<<<<<<
  *     return isinstance(o, memoryview)
- * 
+ *
  */
 
 static CYTHON_INLINE int __pyx_memoryview_check(PyObject *__pyx_v_o) {
@@ -15722,19 +15722,19 @@ static CYTHON_INLINE int __pyx_memoryview_check(PyObject *__pyx_v_o) {
  * @cname('__pyx_memoryview_check')
  * cdef inline bint memoryview_check(object o):
  *     return isinstance(o, memoryview)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef tuple _unellipsify(object index, int ndim):
  */
-  __pyx_t_1 = __Pyx_TypeCheck(__pyx_v_o, __pyx_memoryview_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(__pyx_v_o, __pyx_memoryview_type);
   __pyx_r = __pyx_t_1;
   goto __pyx_L0;
 
   /* "View.MemoryView":663
- * 
+ *
  * @cname('__pyx_memoryview_check')
  * cdef inline bint memoryview_check(object o):             # <<<<<<<<<<<<<<
  *     return isinstance(o, memoryview)
- * 
+ *
  */
 
   /* function exit code */
@@ -15745,7 +15745,7 @@ static CYTHON_INLINE int __pyx_memoryview_check(PyObject *__pyx_v_o) {
 
 /* "View.MemoryView":666
  *     return isinstance(o, memoryview)
- * 
+ *
  * cdef tuple _unellipsify(object index, int ndim):             # <<<<<<<<<<<<<<
  *     """
  *     Replace all ellipses with full slices and fill incomplete indices with
@@ -15784,7 +15784,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *         tup = (index,)
  *     else:
  */
-  __pyx_t_1 = PyTuple_Check(__pyx_v_index); 
+  __pyx_t_1 = PyTuple_Check(__pyx_v_index);
   __pyx_t_2 = ((!(__pyx_t_1 != 0)) != 0);
   if (__pyx_t_2) {
 
@@ -15817,7 +15817,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *         tup = (index,)
  *     else:
  *         tup = index             # <<<<<<<<<<<<<<
- * 
+ *
  *     result = []
  */
   /*else*/ {
@@ -15828,7 +15828,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
 
   /* "View.MemoryView":676
  *         tup = index
- * 
+ *
  *     result = []             # <<<<<<<<<<<<<<
  *     have_slices = False
  *     seen_ellipsis = False
@@ -15839,7 +15839,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
   __pyx_t_3 = 0;
 
   /* "View.MemoryView":677
- * 
+ *
  *     result = []
  *     have_slices = False             # <<<<<<<<<<<<<<
  *     seen_ellipsis = False
@@ -16010,10 +16010,10 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *         else:
  *             if not isinstance(item, slice) and not PyIndex_Check(item):             # <<<<<<<<<<<<<<
  *                 raise TypeError("Cannot index with type '%s'" % type(item))
- * 
+ *
  */
     /*else*/ {
-      __pyx_t_2 = PySlice_Check(__pyx_v_item); 
+      __pyx_t_2 = PySlice_Check(__pyx_v_item);
       __pyx_t_10 = ((!(__pyx_t_2 != 0)) != 0);
       if (__pyx_t_10) {
       } else {
@@ -16029,7 +16029,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *         else:
  *             if not isinstance(item, slice) and not PyIndex_Check(item):
  *                 raise TypeError("Cannot index with type '%s'" % type(item))             # <<<<<<<<<<<<<<
- * 
+ *
  *             have_slices = have_slices or isinstance(item, slice)
  */
         __pyx_t_7 = __Pyx_PyString_FormatSafe(__pyx_kp_s_Cannot_index_with_type_s, ((PyObject *)Py_TYPE(__pyx_v_item))); if (unlikely(!__pyx_t_7)) __PYX_ERR(2, 689, __pyx_L1_error)
@@ -16046,16 +16046,16 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *         else:
  *             if not isinstance(item, slice) and not PyIndex_Check(item):             # <<<<<<<<<<<<<<
  *                 raise TypeError("Cannot index with type '%s'" % type(item))
- * 
+ *
  */
       }
 
       /* "View.MemoryView":691
  *                 raise TypeError("Cannot index with type '%s'" % type(item))
- * 
+ *
  *             have_slices = have_slices or isinstance(item, slice)             # <<<<<<<<<<<<<<
  *             result.append(item)
- * 
+ *
  */
       __pyx_t_10 = (__pyx_v_have_slices != 0);
       if (!__pyx_t_10) {
@@ -16063,17 +16063,17 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
         __pyx_t_1 = __pyx_t_10;
         goto __pyx_L11_bool_binop_done;
       }
-      __pyx_t_10 = PySlice_Check(__pyx_v_item); 
+      __pyx_t_10 = PySlice_Check(__pyx_v_item);
       __pyx_t_2 = (__pyx_t_10 != 0);
       __pyx_t_1 = __pyx_t_2;
       __pyx_L11_bool_binop_done:;
       __pyx_v_have_slices = __pyx_t_1;
 
       /* "View.MemoryView":692
- * 
+ *
  *             have_slices = have_slices or isinstance(item, slice)
  *             result.append(item)             # <<<<<<<<<<<<<<
- * 
+ *
  *     nslices = ndim - len(result)
  */
       __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_result, __pyx_v_item); if (unlikely(__pyx_t_9 == ((int)-1))) __PYX_ERR(2, 692, __pyx_L1_error)
@@ -16093,7 +16093,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
 
   /* "View.MemoryView":694
  *             result.append(item)
- * 
+ *
  *     nslices = ndim - len(result)             # <<<<<<<<<<<<<<
  *     if nslices:
  *         result.extend([slice(None)] * nslices)
@@ -16102,11 +16102,11 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
   __pyx_v_nslices = (__pyx_v_ndim - __pyx_t_5);
 
   /* "View.MemoryView":695
- * 
+ *
  *     nslices = ndim - len(result)
  *     if nslices:             # <<<<<<<<<<<<<<
  *         result.extend([slice(None)] * nslices)
- * 
+ *
  */
   __pyx_t_1 = (__pyx_v_nslices != 0);
   if (__pyx_t_1) {
@@ -16115,7 +16115,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
  *     nslices = ndim - len(result)
  *     if nslices:
  *         result.extend([slice(None)] * nslices)             # <<<<<<<<<<<<<<
- * 
+ *
  *     return have_slices or nslices, tuple(result)
  */
     __pyx_t_3 = PyList_New(1 * ((__pyx_v_nslices<0) ? 0:__pyx_v_nslices)); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 696, __pyx_L1_error)
@@ -16131,19 +16131,19 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
 
     /* "View.MemoryView":695
- * 
+ *
  *     nslices = ndim - len(result)
  *     if nslices:             # <<<<<<<<<<<<<<
  *         result.extend([slice(None)] * nslices)
- * 
+ *
  */
   }
 
   /* "View.MemoryView":698
  *         result.extend([slice(None)] * nslices)
- * 
+ *
  *     return have_slices or nslices, tuple(result)             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef assert_direct_dimensions(Py_ssize_t *suboffsets, int ndim):
  */
   __Pyx_XDECREF(__pyx_r);
@@ -16176,7 +16176,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
 
   /* "View.MemoryView":666
  *     return isinstance(o, memoryview)
- * 
+ *
  * cdef tuple _unellipsify(object index, int ndim):             # <<<<<<<<<<<<<<
  *     """
  *     Replace all ellipses with full slices and fill incomplete indices with
@@ -16202,7 +16202,7 @@ static PyObject *_unellipsify(PyObject *__pyx_v_index, int __pyx_v_ndim) {
 
 /* "View.MemoryView":700
  *     return have_slices or nslices, tuple(result)
- * 
+ *
  * cdef assert_direct_dimensions(Py_ssize_t *suboffsets, int ndim):             # <<<<<<<<<<<<<<
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:
@@ -16223,7 +16223,7 @@ static PyObject *assert_direct_dimensions(Py_ssize_t *__pyx_v_suboffsets, int __
   __Pyx_RefNannySetupContext("assert_direct_dimensions", 0);
 
   /* "View.MemoryView":701
- * 
+ *
  * cdef assert_direct_dimensions(Py_ssize_t *suboffsets, int ndim):
  *     for suboffset in suboffsets[:ndim]:             # <<<<<<<<<<<<<<
  *         if suboffset >= 0:
@@ -16239,7 +16239,7 @@ static PyObject *assert_direct_dimensions(Py_ssize_t *__pyx_v_suboffsets, int __
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:             # <<<<<<<<<<<<<<
  *             raise ValueError("Indirect dimensions not supported")
- * 
+ *
  */
     __pyx_t_4 = ((__pyx_v_suboffset >= 0) != 0);
     if (unlikely(__pyx_t_4)) {
@@ -16248,8 +16248,8 @@ static PyObject *assert_direct_dimensions(Py_ssize_t *__pyx_v_suboffsets, int __
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:
  *             raise ValueError("Indirect dimensions not supported")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__27, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(2, 703, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
@@ -16262,14 +16262,14 @@ static PyObject *assert_direct_dimensions(Py_ssize_t *__pyx_v_suboffsets, int __
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:             # <<<<<<<<<<<<<<
  *             raise ValueError("Indirect dimensions not supported")
- * 
+ *
  */
     }
   }
 
   /* "View.MemoryView":700
  *     return have_slices or nslices, tuple(result)
- * 
+ *
  * cdef assert_direct_dimensions(Py_ssize_t *suboffsets, int ndim):             # <<<<<<<<<<<<<<
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:
@@ -16289,7 +16289,7 @@ static PyObject *assert_direct_dimensions(Py_ssize_t *__pyx_v_suboffsets, int __
 }
 
 /* "View.MemoryView":710
- * 
+ *
  * @cname('__pyx_memview_slice')
  * cdef memoryview memview_slice(memoryview memview, object indices):             # <<<<<<<<<<<<<<
  *     cdef int new_ndim = 0, suboffset_dim = -1, dim
@@ -16343,19 +16343,19 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
   __pyx_v_suboffset_dim = -1;
 
   /* "View.MemoryView":718
- * 
- * 
+ *
+ *
  *     memset(&dst, 0, sizeof(dst))             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef _memoryviewslice memviewsliceobj
  */
   (void)(memset((&__pyx_v_dst), 0, (sizeof(__pyx_v_dst))));
 
   /* "View.MemoryView":722
  *     cdef _memoryviewslice memviewsliceobj
- * 
+ *
  *     assert memview.view.ndim > 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  */
   #ifndef CYTHON_WITHOUT_ASSERTIONS
@@ -16369,17 +16369,17 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
   /* "View.MemoryView":724
  *     assert memview.view.ndim > 0
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         memviewsliceobj = memview
  *         p_src = &memviewsliceobj.from_slice
  */
-  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type);
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
     /* "View.MemoryView":725
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  *         memviewsliceobj = memview             # <<<<<<<<<<<<<<
  *         p_src = &memviewsliceobj.from_slice
@@ -16402,7 +16402,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
     /* "View.MemoryView":724
  *     assert memview.view.ndim > 0
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         memviewsliceobj = memview
  *         p_src = &memviewsliceobj.from_slice
@@ -16415,7 +16415,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *     else:
  *         slice_copy(memview, &src)             # <<<<<<<<<<<<<<
  *         p_src = &src
- * 
+ *
  */
   /*else*/ {
     __pyx_memoryview_slice_copy(__pyx_v_memview, (&__pyx_v_src));
@@ -16424,36 +16424,36 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *     else:
  *         slice_copy(memview, &src)
  *         p_src = &src             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_v_p_src = (&__pyx_v_src);
   }
   __pyx_L3:;
 
   /* "View.MemoryView":735
- * 
- * 
+ *
+ *
  *     dst.memview = p_src.memview             # <<<<<<<<<<<<<<
  *     dst.data = p_src.data
- * 
+ *
  */
   __pyx_t_4 = __pyx_v_p_src->memview;
   __pyx_v_dst.memview = __pyx_t_4;
 
   /* "View.MemoryView":736
- * 
+ *
  *     dst.memview = p_src.memview
  *     dst.data = p_src.data             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_5 = __pyx_v_p_src->data;
   __pyx_v_dst.data = __pyx_t_5;
 
   /* "View.MemoryView":741
- * 
- * 
+ *
+ *
  *     cdef __Pyx_memviewslice *p_dst = &dst             # <<<<<<<<<<<<<<
  *     cdef int *p_suboffset_dim = &suboffset_dim
  *     cdef Py_ssize_t start, stop, step
@@ -16461,7 +16461,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
   __pyx_v_p_dst = (&__pyx_v_dst);
 
   /* "View.MemoryView":742
- * 
+ *
  *     cdef __Pyx_memviewslice *p_dst = &dst
  *     cdef int *p_suboffset_dim = &suboffset_dim             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t start, stop, step
@@ -16471,7 +16471,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
   /* "View.MemoryView":746
  *     cdef bint have_start, have_stop, have_step
- * 
+ *
  *     for dim, index in enumerate(indices):             # <<<<<<<<<<<<<<
  *         if PyIndex_Check(index):
  *             slice_memviewslice(
@@ -16522,7 +16522,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
     __pyx_t_6 = (__pyx_t_6 + 1);
 
     /* "View.MemoryView":747
- * 
+ *
  *     for dim, index in enumerate(indices):
  *         if PyIndex_Check(index):             # <<<<<<<<<<<<<<
  *             slice_memviewslice(
@@ -16550,7 +16550,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
       __pyx_t_11 = __pyx_memoryview_slice_memviewslice(__pyx_v_p_dst, (__pyx_v_p_src->shape[__pyx_v_dim]), (__pyx_v_p_src->strides[__pyx_v_dim]), (__pyx_v_p_src->suboffsets[__pyx_v_dim]), __pyx_v_dim, __pyx_v_new_ndim, __pyx_v_p_suboffset_dim, __pyx_t_10, 0, 0, 0, 0, 0, 0); if (unlikely(__pyx_t_11 == ((int)-1))) __PYX_ERR(2, 748, __pyx_L1_error)
 
       /* "View.MemoryView":747
- * 
+ *
  *     for dim, index in enumerate(indices):
  *         if PyIndex_Check(index):             # <<<<<<<<<<<<<<
  *             slice_memviewslice(
@@ -16644,7 +16644,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *             start = index.start or 0
  *             stop = index.stop or 0             # <<<<<<<<<<<<<<
  *             step = index.step or 0
- * 
+ *
  */
       __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_index, __pyx_n_s_stop); if (unlikely(!__pyx_t_9)) __PYX_ERR(2, 761, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
@@ -16665,7 +16665,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *             start = index.start or 0
  *             stop = index.stop or 0
  *             step = index.step or 0             # <<<<<<<<<<<<<<
- * 
+ *
  *             have_start = index.start is not None
  */
       __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_index, __pyx_n_s_step); if (unlikely(!__pyx_t_9)) __PYX_ERR(2, 762, __pyx_L1_error)
@@ -16685,7 +16685,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
       /* "View.MemoryView":764
  *             step = index.step or 0
- * 
+ *
  *             have_start = index.start is not None             # <<<<<<<<<<<<<<
  *             have_stop = index.stop is not None
  *             have_step = index.step is not None
@@ -16697,11 +16697,11 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
       __pyx_v_have_start = __pyx_t_1;
 
       /* "View.MemoryView":765
- * 
+ *
  *             have_start = index.start is not None
  *             have_stop = index.stop is not None             # <<<<<<<<<<<<<<
  *             have_step = index.step is not None
- * 
+ *
  */
       __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_index, __pyx_n_s_stop); if (unlikely(!__pyx_t_9)) __PYX_ERR(2, 765, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
@@ -16713,7 +16713,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *             have_start = index.start is not None
  *             have_stop = index.stop is not None
  *             have_step = index.step is not None             # <<<<<<<<<<<<<<
- * 
+ *
  *             slice_memviewslice(
  */
       __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_index, __pyx_n_s_step); if (unlikely(!__pyx_t_9)) __PYX_ERR(2, 766, __pyx_L1_error)
@@ -16724,7 +16724,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
       /* "View.MemoryView":768
  *             have_step = index.step is not None
- * 
+ *
  *             slice_memviewslice(             # <<<<<<<<<<<<<<
  *                 p_dst, p_src.shape[dim], p_src.strides[dim], p_src.suboffsets[dim],
  *                 dim, new_ndim, p_suboffset_dim,
@@ -16735,7 +16735,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *                 have_start, have_stop, have_step,
  *                 True)
  *             new_ndim += 1             # <<<<<<<<<<<<<<
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  */
       __pyx_v_new_ndim = (__pyx_v_new_ndim + 1);
@@ -16744,7 +16744,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
     /* "View.MemoryView":746
  *     cdef bint have_start, have_stop, have_step
- * 
+ *
  *     for dim, index in enumerate(indices):             # <<<<<<<<<<<<<<
  *         if PyIndex_Check(index):
  *             slice_memviewslice(
@@ -16754,17 +16754,17 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
   /* "View.MemoryView":776
  *             new_ndim += 1
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         return memoryview_fromslice(dst, new_ndim,
  *                                     memviewsliceobj.to_object_func,
  */
-  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type);
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
     /* "View.MemoryView":777
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  *         return memoryview_fromslice(dst, new_ndim,             # <<<<<<<<<<<<<<
  *                                     memviewsliceobj.to_object_func,
@@ -16791,7 +16791,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
     if (unlikely(!__pyx_v_memviewsliceobj)) { __Pyx_RaiseUnboundLocalError("memviewsliceobj"); __PYX_ERR(2, 779, __pyx_L1_error) }
 
     /* "View.MemoryView":777
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  *         return memoryview_fromslice(dst, new_ndim,             # <<<<<<<<<<<<<<
  *                                     memviewsliceobj.to_object_func,
@@ -16806,7 +16806,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 
     /* "View.MemoryView":776
  *             new_ndim += 1
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         return memoryview_fromslice(dst, new_ndim,
  *                                     memviewsliceobj.to_object_func,
@@ -16818,7 +16818,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *     else:
  *         return memoryview_fromslice(dst, new_ndim, NULL, NULL,             # <<<<<<<<<<<<<<
  *                                     memview.dtype_is_object)
- * 
+ *
  */
   /*else*/ {
     __Pyx_XDECREF(((PyObject *)__pyx_r));
@@ -16827,8 +16827,8 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *     else:
  *         return memoryview_fromslice(dst, new_ndim, NULL, NULL,
  *                                     memview.dtype_is_object)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_t_3 = __pyx_memoryview_fromslice(__pyx_v_dst, __pyx_v_new_ndim, NULL, NULL, __pyx_v_memview->dtype_is_object); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 782, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
@@ -16838,7 +16838,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
  *     else:
  *         return memoryview_fromslice(dst, new_ndim, NULL, NULL,             # <<<<<<<<<<<<<<
  *                                     memview.dtype_is_object)
- * 
+ *
  */
     if (!(likely(((__pyx_t_3) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_3, __pyx_memoryview_type))))) __PYX_ERR(2, 782, __pyx_L1_error)
     __pyx_r = ((struct __pyx_memoryview_obj *)__pyx_t_3);
@@ -16847,7 +16847,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
   }
 
   /* "View.MemoryView":710
- * 
+ *
  * @cname('__pyx_memview_slice')
  * cdef memoryview memview_slice(memoryview memview, object indices):             # <<<<<<<<<<<<<<
  *     cdef int new_ndim = 0, suboffset_dim = -1, dim
@@ -16869,7 +16869,7 @@ static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_
 }
 
 /* "View.MemoryView":807
- * 
+ *
  * @cname('__pyx_memoryview_slice_memviewslice')
  * cdef int slice_memviewslice(             # <<<<<<<<<<<<<<
  *         __Pyx_memviewslice *dst,
@@ -16889,9 +16889,9 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
   /* "View.MemoryView":827
  *     cdef bint negative_step
- * 
+ *
  *     if not is_slice:             # <<<<<<<<<<<<<<
- * 
+ *
  *         if start < 0:
  */
   __pyx_t_1 = ((!(__pyx_v_is_slice != 0)) != 0);
@@ -16899,7 +16899,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":829
  *     if not is_slice:
- * 
+ *
  *         if start < 0:             # <<<<<<<<<<<<<<
  *             start += shape
  *         if not 0 <= start < shape:
@@ -16908,7 +16908,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     if (__pyx_t_1) {
 
       /* "View.MemoryView":830
- * 
+ *
  *         if start < 0:
  *             start += shape             # <<<<<<<<<<<<<<
  *         if not 0 <= start < shape:
@@ -16918,7 +16918,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
       /* "View.MemoryView":829
  *     if not is_slice:
- * 
+ *
  *         if start < 0:             # <<<<<<<<<<<<<<
  *             start += shape
  *         if not 0 <= start < shape:
@@ -16944,7 +16944,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *         if not 0 <= start < shape:
  *             _err_dim(IndexError, "Index out of bounds (axis %d)", dim)             # <<<<<<<<<<<<<<
  *     else:
- * 
+ *
  */
       __pyx_t_3 = __pyx_memoryview_err_dim(__pyx_builtin_IndexError, ((char *)"Index out of bounds (axis %d)"), __pyx_v_dim); if (unlikely(__pyx_t_3 == ((int)-1))) __PYX_ERR(2, 832, __pyx_L1_error)
 
@@ -16959,9 +16959,9 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":827
  *     cdef bint negative_step
- * 
+ *
  *     if not is_slice:             # <<<<<<<<<<<<<<
- * 
+ *
  *         if start < 0:
  */
     goto __pyx_L3;
@@ -16969,9 +16969,9 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
   /* "View.MemoryView":835
  *     else:
- * 
+ *
  *         negative_step = have_step != 0 and step < 0             # <<<<<<<<<<<<<<
- * 
+ *
  *         if have_step and step == 0:
  */
   /*else*/ {
@@ -16988,10 +16988,10 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":837
  *         negative_step = have_step != 0 and step < 0
- * 
+ *
  *         if have_step and step == 0:             # <<<<<<<<<<<<<<
  *             _err_dim(ValueError, "Step may not be zero (axis %d)", dim)
- * 
+ *
  */
     __pyx_t_1 = (__pyx_v_have_step != 0);
     if (__pyx_t_1) {
@@ -17005,26 +17005,26 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     if (__pyx_t_2) {
 
       /* "View.MemoryView":838
- * 
+ *
  *         if have_step and step == 0:
  *             _err_dim(ValueError, "Step may not be zero (axis %d)", dim)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_t_3 = __pyx_memoryview_err_dim(__pyx_builtin_ValueError, ((char *)"Step may not be zero (axis %d)"), __pyx_v_dim); if (unlikely(__pyx_t_3 == ((int)-1))) __PYX_ERR(2, 838, __pyx_L1_error)
 
       /* "View.MemoryView":837
  *         negative_step = have_step != 0 and step < 0
- * 
+ *
  *         if have_step and step == 0:             # <<<<<<<<<<<<<<
  *             _err_dim(ValueError, "Step may not be zero (axis %d)", dim)
- * 
+ *
  */
     }
 
     /* "View.MemoryView":841
- * 
- * 
+ *
+ *
  *         if have_start:             # <<<<<<<<<<<<<<
  *             if start < 0:
  *                 start += shape
@@ -17033,7 +17033,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     if (__pyx_t_2) {
 
       /* "View.MemoryView":842
- * 
+ *
  *         if have_start:
  *             if start < 0:             # <<<<<<<<<<<<<<
  *                 start += shape
@@ -17080,7 +17080,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
         }
 
         /* "View.MemoryView":842
- * 
+ *
  *         if have_start:
  *             if start < 0:             # <<<<<<<<<<<<<<
  *                 start += shape
@@ -17151,8 +17151,8 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
       __pyx_L12:;
 
       /* "View.MemoryView":841
- * 
- * 
+ *
+ *
  *         if have_start:             # <<<<<<<<<<<<<<
  *             if start < 0:
  *                 start += shape
@@ -17194,7 +17194,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *                 start = shape - 1
  *             else:
  *                 start = 0             # <<<<<<<<<<<<<<
- * 
+ *
  *         if have_stop:
  */
       /*else*/ {
@@ -17206,7 +17206,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":857
  *                 start = 0
- * 
+ *
  *         if have_stop:             # <<<<<<<<<<<<<<
  *             if stop < 0:
  *                 stop += shape
@@ -17215,7 +17215,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     if (__pyx_t_2) {
 
       /* "View.MemoryView":858
- * 
+ *
  *         if have_stop:
  *             if stop < 0:             # <<<<<<<<<<<<<<
  *                 stop += shape
@@ -17262,7 +17262,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
         }
 
         /* "View.MemoryView":858
- * 
+ *
  *         if have_stop:
  *             if stop < 0:             # <<<<<<<<<<<<<<
  *                 stop += shape
@@ -17302,7 +17302,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
       /* "View.MemoryView":857
  *                 start = 0
- * 
+ *
  *         if have_stop:             # <<<<<<<<<<<<<<
  *             if stop < 0:
  *                 stop += shape
@@ -17344,7 +17344,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *                 stop = -1
  *             else:
  *                 stop = shape             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not have_step:
  */
       /*else*/ {
@@ -17356,100 +17356,100 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":870
  *                 stop = shape
- * 
+ *
  *         if not have_step:             # <<<<<<<<<<<<<<
  *             step = 1
- * 
+ *
  */
     __pyx_t_2 = ((!(__pyx_v_have_step != 0)) != 0);
     if (__pyx_t_2) {
 
       /* "View.MemoryView":871
- * 
+ *
  *         if not have_step:
  *             step = 1             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_v_step = 1;
 
       /* "View.MemoryView":870
  *                 stop = shape
- * 
+ *
  *         if not have_step:             # <<<<<<<<<<<<<<
  *             step = 1
- * 
+ *
  */
     }
 
     /* "View.MemoryView":875
- * 
+ *
  *         with cython.cdivision(True):
  *             new_shape = (stop - start) // step             # <<<<<<<<<<<<<<
- * 
+ *
  *             if (stop - start) - step * new_shape:
  */
     __pyx_v_new_shape = ((__pyx_v_stop - __pyx_v_start) / __pyx_v_step);
 
     /* "View.MemoryView":877
  *             new_shape = (stop - start) // step
- * 
+ *
  *             if (stop - start) - step * new_shape:             # <<<<<<<<<<<<<<
  *                 new_shape += 1
- * 
+ *
  */
     __pyx_t_2 = (((__pyx_v_stop - __pyx_v_start) - (__pyx_v_step * __pyx_v_new_shape)) != 0);
     if (__pyx_t_2) {
 
       /* "View.MemoryView":878
- * 
+ *
  *             if (stop - start) - step * new_shape:
  *                 new_shape += 1             # <<<<<<<<<<<<<<
- * 
+ *
  *         if new_shape < 0:
  */
       __pyx_v_new_shape = (__pyx_v_new_shape + 1);
 
       /* "View.MemoryView":877
  *             new_shape = (stop - start) // step
- * 
+ *
  *             if (stop - start) - step * new_shape:             # <<<<<<<<<<<<<<
  *                 new_shape += 1
- * 
+ *
  */
     }
 
     /* "View.MemoryView":880
  *                 new_shape += 1
- * 
+ *
  *         if new_shape < 0:             # <<<<<<<<<<<<<<
  *             new_shape = 0
- * 
+ *
  */
     __pyx_t_2 = ((__pyx_v_new_shape < 0) != 0);
     if (__pyx_t_2) {
 
       /* "View.MemoryView":881
- * 
+ *
  *         if new_shape < 0:
  *             new_shape = 0             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_v_new_shape = 0;
 
       /* "View.MemoryView":880
  *                 new_shape += 1
- * 
+ *
  *         if new_shape < 0:             # <<<<<<<<<<<<<<
  *             new_shape = 0
- * 
+ *
  */
     }
 
     /* "View.MemoryView":884
- * 
- * 
+ *
+ *
  *         dst.strides[new_ndim] = stride * step             # <<<<<<<<<<<<<<
  *         dst.shape[new_ndim] = new_shape
  *         dst.suboffsets[new_ndim] = suboffset
@@ -17457,11 +17457,11 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     (__pyx_v_dst->strides[__pyx_v_new_ndim]) = (__pyx_v_stride * __pyx_v_step);
 
     /* "View.MemoryView":885
- * 
+ *
  *         dst.strides[new_ndim] = stride * step
  *         dst.shape[new_ndim] = new_shape             # <<<<<<<<<<<<<<
  *         dst.suboffsets[new_ndim] = suboffset
- * 
+ *
  */
     (__pyx_v_dst->shape[__pyx_v_new_ndim]) = __pyx_v_new_shape;
 
@@ -17469,16 +17469,16 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *         dst.strides[new_ndim] = stride * step
  *         dst.shape[new_ndim] = new_shape
  *         dst.suboffsets[new_ndim] = suboffset             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     (__pyx_v_dst->suboffsets[__pyx_v_new_ndim]) = __pyx_v_suboffset;
   }
   __pyx_L3:;
 
   /* "View.MemoryView":889
- * 
- * 
+ *
+ *
  *     if suboffset_dim[0] < 0:             # <<<<<<<<<<<<<<
  *         dst.data += start * stride
  *     else:
@@ -17487,7 +17487,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
   if (__pyx_t_2) {
 
     /* "View.MemoryView":890
- * 
+ *
  *     if suboffset_dim[0] < 0:
  *         dst.data += start * stride             # <<<<<<<<<<<<<<
  *     else:
@@ -17496,8 +17496,8 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
     __pyx_v_dst->data = (__pyx_v_dst->data + (__pyx_v_start * __pyx_v_stride));
 
     /* "View.MemoryView":889
- * 
- * 
+ *
+ *
  *     if suboffset_dim[0] < 0:             # <<<<<<<<<<<<<<
  *         dst.data += start * stride
  *     else:
@@ -17509,7 +17509,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *         dst.data += start * stride
  *     else:
  *         dst.suboffsets[suboffset_dim[0]] += start * stride             # <<<<<<<<<<<<<<
- * 
+ *
  *     if suboffset >= 0:
  */
   /*else*/ {
@@ -17520,7 +17520,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
   /* "View.MemoryView":894
  *         dst.suboffsets[suboffset_dim[0]] += start * stride
- * 
+ *
  *     if suboffset >= 0:             # <<<<<<<<<<<<<<
  *         if not is_slice:
  *             if new_ndim == 0:
@@ -17529,7 +17529,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
   if (__pyx_t_2) {
 
     /* "View.MemoryView":895
- * 
+ *
  *     if suboffset >= 0:
  *         if not is_slice:             # <<<<<<<<<<<<<<
  *             if new_ndim == 0:
@@ -17588,7 +17588,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
       __pyx_L26:;
 
       /* "View.MemoryView":895
- * 
+ *
  *     if suboffset >= 0:
  *         if not is_slice:             # <<<<<<<<<<<<<<
  *             if new_ndim == 0:
@@ -17601,7 +17601,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
  *                                      "must be indexed and not sliced", dim)
  *         else:
  *             suboffset_dim[0] = new_ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *     return 0
  */
     /*else*/ {
@@ -17611,7 +17611,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
     /* "View.MemoryView":894
  *         dst.suboffsets[suboffset_dim[0]] += start * stride
- * 
+ *
  *     if suboffset >= 0:             # <<<<<<<<<<<<<<
  *         if not is_slice:
  *             if new_ndim == 0:
@@ -17620,16 +17620,16 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 
   /* "View.MemoryView":904
  *             suboffset_dim[0] = new_ndim
- * 
+ *
  *     return 0             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = 0;
   goto __pyx_L0;
 
   /* "View.MemoryView":807
- * 
+ *
  * @cname('__pyx_memoryview_slice_memviewslice')
  * cdef int slice_memviewslice(             # <<<<<<<<<<<<<<
  *         __Pyx_memviewslice *dst,
@@ -17653,7 +17653,7 @@ static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *__pyx_v_dst,
 }
 
 /* "View.MemoryView":910
- * 
+ *
  * @cname('__pyx_pybuffer_index')
  * cdef char *pybuffer_index(Py_buffer *view, char *bufp, Py_ssize_t index,             # <<<<<<<<<<<<<<
  *                           Py_ssize_t dim) except NULL:
@@ -17691,14 +17691,14 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *     cdef Py_ssize_t shape, stride, suboffset = -1
  *     cdef Py_ssize_t itemsize = view.itemsize             # <<<<<<<<<<<<<<
  *     cdef char *resultp
- * 
+ *
  */
   __pyx_t_1 = __pyx_v_view->itemsize;
   __pyx_v_itemsize = __pyx_t_1;
 
   /* "View.MemoryView":916
  *     cdef char *resultp
- * 
+ *
  *     if view.ndim == 0:             # <<<<<<<<<<<<<<
  *         shape = view.len / itemsize
  *         stride = itemsize
@@ -17707,7 +17707,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
   if (__pyx_t_2) {
 
     /* "View.MemoryView":917
- * 
+ *
  *     if view.ndim == 0:
  *         shape = view.len / itemsize             # <<<<<<<<<<<<<<
  *         stride = itemsize
@@ -17734,7 +17734,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
 
     /* "View.MemoryView":916
  *     cdef char *resultp
- * 
+ *
  *     if view.ndim == 0:             # <<<<<<<<<<<<<<
  *         shape = view.len / itemsize
  *         stride = itemsize
@@ -17766,7 +17766,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         stride = view.strides[dim]
  *         if view.suboffsets != NULL:             # <<<<<<<<<<<<<<
  *             suboffset = view.suboffsets[dim]
- * 
+ *
  */
     __pyx_t_2 = ((__pyx_v_view->suboffsets != NULL) != 0);
     if (__pyx_t_2) {
@@ -17775,7 +17775,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         stride = view.strides[dim]
  *         if view.suboffsets != NULL:
  *             suboffset = view.suboffsets[dim]             # <<<<<<<<<<<<<<
- * 
+ *
  *     if index < 0:
  */
       __pyx_v_suboffset = (__pyx_v_view->suboffsets[__pyx_v_dim]);
@@ -17785,7 +17785,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         stride = view.strides[dim]
  *         if view.suboffsets != NULL:             # <<<<<<<<<<<<<<
  *             suboffset = view.suboffsets[dim]
- * 
+ *
  */
     }
   }
@@ -17793,7 +17793,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
 
   /* "View.MemoryView":925
  *             suboffset = view.suboffsets[dim]
- * 
+ *
  *     if index < 0:             # <<<<<<<<<<<<<<
  *         index += view.shape[dim]
  *         if index < 0:
@@ -17802,7 +17802,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
   if (__pyx_t_2) {
 
     /* "View.MemoryView":926
- * 
+ *
  *     if index < 0:
  *         index += view.shape[dim]             # <<<<<<<<<<<<<<
  *         if index < 0:
@@ -17815,7 +17815,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         index += view.shape[dim]
  *         if index < 0:             # <<<<<<<<<<<<<<
  *             raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  */
     __pyx_t_2 = ((__pyx_v_index < 0) != 0);
     if (unlikely(__pyx_t_2)) {
@@ -17824,7 +17824,7 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         index += view.shape[dim]
  *         if index < 0:
  *             raise IndexError("Out of bounds on buffer access (axis %d)" % dim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     if index >= shape:
  */
       __pyx_t_3 = PyInt_FromSsize_t(__pyx_v_dim); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 928, __pyx_L1_error)
@@ -17844,13 +17844,13 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *         index += view.shape[dim]
  *         if index < 0:             # <<<<<<<<<<<<<<
  *             raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  */
     }
 
     /* "View.MemoryView":925
  *             suboffset = view.suboffsets[dim]
- * 
+ *
  *     if index < 0:             # <<<<<<<<<<<<<<
  *         index += view.shape[dim]
  *         if index < 0:
@@ -17859,19 +17859,19 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
 
   /* "View.MemoryView":930
  *             raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  *     if index >= shape:             # <<<<<<<<<<<<<<
  *         raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  */
   __pyx_t_2 = ((__pyx_v_index >= __pyx_v_shape) != 0);
   if (unlikely(__pyx_t_2)) {
 
     /* "View.MemoryView":931
- * 
+ *
  *     if index >= shape:
  *         raise IndexError("Out of bounds on buffer access (axis %d)" % dim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     resultp = bufp + index * stride
  */
     __pyx_t_3 = PyInt_FromSsize_t(__pyx_v_dim); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 931, __pyx_L1_error)
@@ -17888,16 +17888,16 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
 
     /* "View.MemoryView":930
  *             raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  *     if index >= shape:             # <<<<<<<<<<<<<<
  *         raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  */
   }
 
   /* "View.MemoryView":933
  *         raise IndexError("Out of bounds on buffer access (axis %d)" % dim)
- * 
+ *
  *     resultp = bufp + index * stride             # <<<<<<<<<<<<<<
  *     if suboffset >= 0:
  *         resultp = (<char **> resultp)[0] + suboffset
@@ -17905,11 +17905,11 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
   __pyx_v_resultp = (__pyx_v_bufp + (__pyx_v_index * __pyx_v_stride));
 
   /* "View.MemoryView":934
- * 
+ *
  *     resultp = bufp + index * stride
  *     if suboffset >= 0:             # <<<<<<<<<<<<<<
  *         resultp = (<char **> resultp)[0] + suboffset
- * 
+ *
  */
   __pyx_t_2 = ((__pyx_v_suboffset >= 0) != 0);
   if (__pyx_t_2) {
@@ -17918,32 +17918,32 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
  *     resultp = bufp + index * stride
  *     if suboffset >= 0:
  *         resultp = (<char **> resultp)[0] + suboffset             # <<<<<<<<<<<<<<
- * 
+ *
  *     return resultp
  */
     __pyx_v_resultp = ((((char **)__pyx_v_resultp)[0]) + __pyx_v_suboffset);
 
     /* "View.MemoryView":934
- * 
+ *
  *     resultp = bufp + index * stride
  *     if suboffset >= 0:             # <<<<<<<<<<<<<<
  *         resultp = (<char **> resultp)[0] + suboffset
- * 
+ *
  */
   }
 
   /* "View.MemoryView":937
  *         resultp = (<char **> resultp)[0] + suboffset
- * 
+ *
  *     return resultp             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = __pyx_v_resultp;
   goto __pyx_L0;
 
   /* "View.MemoryView":910
- * 
+ *
  * @cname('__pyx_pybuffer_index')
  * cdef char *pybuffer_index(Py_buffer *view, char *bufp, Py_ssize_t index,             # <<<<<<<<<<<<<<
  *                           Py_ssize_t dim) except NULL:
@@ -17962,11 +17962,11 @@ static char *__pyx_pybuffer_index(Py_buffer *__pyx_v_view, char *__pyx_v_bufp, P
 }
 
 /* "View.MemoryView":943
- * 
+ *
  * @cname('__pyx_memslice_transpose')
  * cdef int transpose_memslice(__Pyx_memviewslice *memslice) nogil except 0:             # <<<<<<<<<<<<<<
  *     cdef int ndim = memslice.memview.view.ndim
- * 
+ *
  */
 
 static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
@@ -17993,7 +17993,7 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
  * @cname('__pyx_memslice_transpose')
  * cdef int transpose_memslice(__Pyx_memviewslice *memslice) nogil except 0:
  *     cdef int ndim = memslice.memview.view.ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef Py_ssize_t *shape = memslice.shape
  */
   __pyx_t_1 = __pyx_v_memslice->memview->view.ndim;
@@ -18001,26 +18001,26 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
 
   /* "View.MemoryView":946
  *     cdef int ndim = memslice.memview.view.ndim
- * 
+ *
  *     cdef Py_ssize_t *shape = memslice.shape             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t *strides = memslice.strides
- * 
+ *
  */
   __pyx_t_2 = __pyx_v_memslice->shape;
   __pyx_v_shape = __pyx_t_2;
 
   /* "View.MemoryView":947
- * 
+ *
  *     cdef Py_ssize_t *shape = memslice.shape
  *     cdef Py_ssize_t *strides = memslice.strides             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_2 = __pyx_v_memslice->strides;
   __pyx_v_strides = __pyx_t_2;
 
   /* "View.MemoryView":951
- * 
+ *
  *     cdef int i, j
  *     for i in range(ndim / 2):             # <<<<<<<<<<<<<<
  *         j = ndim - 1 - i
@@ -18045,7 +18045,7 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
  *         j = ndim - 1 - i
  *         strides[i], strides[j] = strides[j], strides[i]             # <<<<<<<<<<<<<<
  *         shape[i], shape[j] = shape[j], shape[i]
- * 
+ *
  */
     __pyx_t_5 = (__pyx_v_strides[__pyx_v_j]);
     __pyx_t_6 = (__pyx_v_strides[__pyx_v_i]);
@@ -18056,7 +18056,7 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
  *         j = ndim - 1 - i
  *         strides[i], strides[j] = strides[j], strides[i]
  *         shape[i], shape[j] = shape[j], shape[i]             # <<<<<<<<<<<<<<
- * 
+ *
  *         if memslice.suboffsets[i] >= 0 or memslice.suboffsets[j] >= 0:
  */
     __pyx_t_6 = (__pyx_v_shape[__pyx_v_j]);
@@ -18066,10 +18066,10 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
 
     /* "View.MemoryView":956
  *         shape[i], shape[j] = shape[j], shape[i]
- * 
+ *
  *         if memslice.suboffsets[i] >= 0 or memslice.suboffsets[j] >= 0:             # <<<<<<<<<<<<<<
  *             _err(ValueError, "Cannot transpose memoryview with indirect dimensions")
- * 
+ *
  */
     __pyx_t_8 = (((__pyx_v_memslice->suboffsets[__pyx_v_i]) >= 0) != 0);
     if (!__pyx_t_8) {
@@ -18083,40 +18083,40 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
     if (__pyx_t_7) {
 
       /* "View.MemoryView":957
- * 
+ *
  *         if memslice.suboffsets[i] >= 0 or memslice.suboffsets[j] >= 0:
  *             _err(ValueError, "Cannot transpose memoryview with indirect dimensions")             # <<<<<<<<<<<<<<
- * 
+ *
  *     return 1
  */
       __pyx_t_9 = __pyx_memoryview_err(__pyx_builtin_ValueError, ((char *)"Cannot transpose memoryview with indirect dimensions")); if (unlikely(__pyx_t_9 == ((int)-1))) __PYX_ERR(2, 957, __pyx_L1_error)
 
       /* "View.MemoryView":956
  *         shape[i], shape[j] = shape[j], shape[i]
- * 
+ *
  *         if memslice.suboffsets[i] >= 0 or memslice.suboffsets[j] >= 0:             # <<<<<<<<<<<<<<
  *             _err(ValueError, "Cannot transpose memoryview with indirect dimensions")
- * 
+ *
  */
     }
   }
 
   /* "View.MemoryView":959
  *             _err(ValueError, "Cannot transpose memoryview with indirect dimensions")
- * 
+ *
  *     return 1             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = 1;
   goto __pyx_L0;
 
   /* "View.MemoryView":943
- * 
+ *
  * @cname('__pyx_memslice_transpose')
  * cdef int transpose_memslice(__Pyx_memviewslice *memslice) nogil except 0:             # <<<<<<<<<<<<<<
  *     cdef int ndim = memslice.memview.view.ndim
- * 
+ *
  */
 
   /* function exit code */
@@ -18137,10 +18137,10 @@ static int __pyx_memslice_transpose(__Pyx_memviewslice *__pyx_v_memslice) {
 
 /* "View.MemoryView":976
  *     cdef int (*to_dtype_func)(char *, object) except 0
- * 
+ *
  *     def __dealloc__(self):             # <<<<<<<<<<<<<<
  *         __PYX_XDEC_MEMVIEW(&self.from_slice, 1)
- * 
+ *
  */
 
 /* Python wrapper */
@@ -18159,20 +18159,20 @@ static void __pyx_memoryviewslice___pyx_pf_15View_dot_MemoryView_16_memoryviewsl
   __Pyx_RefNannySetupContext("__dealloc__", 0);
 
   /* "View.MemoryView":977
- * 
+ *
  *     def __dealloc__(self):
  *         __PYX_XDEC_MEMVIEW(&self.from_slice, 1)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):
  */
   __PYX_XDEC_MEMVIEW((&__pyx_v_self->from_slice), 1);
 
   /* "View.MemoryView":976
  *     cdef int (*to_dtype_func)(char *, object) except 0
- * 
+ *
  *     def __dealloc__(self):             # <<<<<<<<<<<<<<
  *         __PYX_XDEC_MEMVIEW(&self.from_slice, 1)
- * 
+ *
  */
 
   /* function exit code */
@@ -18181,7 +18181,7 @@ static void __pyx_memoryviewslice___pyx_pf_15View_dot_MemoryView_16_memoryviewsl
 
 /* "View.MemoryView":979
  *         __PYX_XDEC_MEMVIEW(&self.from_slice, 1)
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):             # <<<<<<<<<<<<<<
  *         if self.to_object_func != NULL:
  *             return self.to_object_func(itemp)
@@ -18198,7 +18198,7 @@ static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memor
   __Pyx_RefNannySetupContext("convert_item_to_object", 0);
 
   /* "View.MemoryView":980
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):
  *         if self.to_object_func != NULL:             # <<<<<<<<<<<<<<
  *             return self.to_object_func(itemp)
@@ -18222,7 +18222,7 @@ static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memor
     goto __pyx_L0;
 
     /* "View.MemoryView":980
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):
  *         if self.to_object_func != NULL:             # <<<<<<<<<<<<<<
  *             return self.to_object_func(itemp)
@@ -18234,7 +18234,7 @@ static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memor
  *             return self.to_object_func(itemp)
  *         else:
  *             return memoryview.convert_item_to_object(self, itemp)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):
  */
   /*else*/ {
@@ -18248,7 +18248,7 @@ static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memor
 
   /* "View.MemoryView":979
  *         __PYX_XDEC_MEMVIEW(&self.from_slice, 1)
- * 
+ *
  *     cdef convert_item_to_object(self, char *itemp):             # <<<<<<<<<<<<<<
  *         if self.to_object_func != NULL:
  *             return self.to_object_func(itemp)
@@ -18267,7 +18267,7 @@ static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memor
 
 /* "View.MemoryView":985
  *             return memoryview.convert_item_to_object(self, itemp)
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):             # <<<<<<<<<<<<<<
  *         if self.to_dtype_func != NULL:
  *             self.to_dtype_func(itemp, value)
@@ -18285,7 +18285,7 @@ static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memo
   __Pyx_RefNannySetupContext("assign_item_from_object", 0);
 
   /* "View.MemoryView":986
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):
  *         if self.to_dtype_func != NULL:             # <<<<<<<<<<<<<<
  *             self.to_dtype_func(itemp, value)
@@ -18304,7 +18304,7 @@ static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memo
     __pyx_t_2 = __pyx_v_self->to_dtype_func(__pyx_v_itemp, __pyx_v_value); if (unlikely(__pyx_t_2 == ((int)0))) __PYX_ERR(2, 987, __pyx_L1_error)
 
     /* "View.MemoryView":986
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):
  *         if self.to_dtype_func != NULL:             # <<<<<<<<<<<<<<
  *             self.to_dtype_func(itemp, value)
@@ -18317,7 +18317,7 @@ static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memo
  *             self.to_dtype_func(itemp, value)
  *         else:
  *             memoryview.assign_item_from_object(self, itemp, value)             # <<<<<<<<<<<<<<
- * 
+ *
  *     @property
  */
   /*else*/ {
@@ -18329,7 +18329,7 @@ static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memo
 
   /* "View.MemoryView":985
  *             return memoryview.convert_item_to_object(self, itemp)
- * 
+ *
  *     cdef assign_item_from_object(self, char *itemp, object value):             # <<<<<<<<<<<<<<
  *         if self.to_dtype_func != NULL:
  *             self.to_dtype_func(itemp, value)
@@ -18349,11 +18349,11 @@ static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memo
 }
 
 /* "View.MemoryView":992
- * 
+ *
  *     @property
  *     def base(self):             # <<<<<<<<<<<<<<
  *         return self.from_object
- * 
+ *
  */
 
 /* Python wrapper */
@@ -18378,7 +18378,7 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_16_memoryviewslice_4base___get__
  *     @property
  *     def base(self):
  *         return self.from_object             # <<<<<<<<<<<<<<
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)")
  */
   __Pyx_XDECREF(__pyx_r);
@@ -18387,11 +18387,11 @@ static PyObject *__pyx_pf_15View_dot_MemoryView_16_memoryviewslice_4base___get__
   goto __pyx_L0;
 
   /* "View.MemoryView":992
- * 
+ *
  *     @property
  *     def base(self):             # <<<<<<<<<<<<<<
  *         return self.from_object
- * 
+ *
  */
 
   /* function exit code */
@@ -18515,7 +18515,7 @@ static PyObject *__pyx_pf___pyx_memoryviewslice_2__setstate_cython__(CYTHON_UNUS
 }
 
 /* "View.MemoryView":999
- * 
+ *
  * @cname('__pyx_memoryview_fromslice')
  * cdef memoryview_fromslice(__Pyx_memviewslice memviewslice,             # <<<<<<<<<<<<<<
  *                           int ndim,
@@ -18544,20 +18544,20 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1007
  *     cdef _memoryviewslice result
- * 
+ *
  *     if <PyObject *> memviewslice.memview == Py_None:             # <<<<<<<<<<<<<<
  *         return None
- * 
+ *
  */
   __pyx_t_1 = ((((PyObject *)__pyx_v_memviewslice.memview) == Py_None) != 0);
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1008
- * 
+ *
  *     if <PyObject *> memviewslice.memview == Py_None:
  *         return None             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __Pyx_XDECREF(__pyx_r);
     __pyx_r = Py_None; __Pyx_INCREF(Py_None);
@@ -18565,18 +18565,18 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
     /* "View.MemoryView":1007
  *     cdef _memoryviewslice result
- * 
+ *
  *     if <PyObject *> memviewslice.memview == Py_None:             # <<<<<<<<<<<<<<
  *         return None
- * 
+ *
  */
   }
 
   /* "View.MemoryView":1013
- * 
- * 
+ *
+ *
  *     result = _memoryviewslice(None, 0, dtype_is_object)             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.from_slice = memviewslice
  */
   __pyx_t_2 = __Pyx_PyBool_FromLong(__pyx_v_dtype_is_object); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 1013, __pyx_L1_error)
@@ -18600,28 +18600,28 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1015
  *     result = _memoryviewslice(None, 0, dtype_is_object)
- * 
+ *
  *     result.from_slice = memviewslice             # <<<<<<<<<<<<<<
  *     __PYX_INC_MEMVIEW(&memviewslice, 1)
- * 
+ *
  */
   __pyx_v_result->from_slice = __pyx_v_memviewslice;
 
   /* "View.MemoryView":1016
- * 
+ *
  *     result.from_slice = memviewslice
  *     __PYX_INC_MEMVIEW(&memviewslice, 1)             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.from_object = (<memoryview> memviewslice.memview).base
  */
   __PYX_INC_MEMVIEW((&__pyx_v_memviewslice), 1);
 
   /* "View.MemoryView":1018
  *     __PYX_INC_MEMVIEW(&memviewslice, 1)
- * 
+ *
  *     result.from_object = (<memoryview> memviewslice.memview).base             # <<<<<<<<<<<<<<
  *     result.typeinfo = memviewslice.memview.typeinfo
- * 
+ *
  */
   __pyx_t_2 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_memviewslice.memview), __pyx_n_s_base); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 1018, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
@@ -18632,10 +18632,10 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   __pyx_t_2 = 0;
 
   /* "View.MemoryView":1019
- * 
+ *
  *     result.from_object = (<memoryview> memviewslice.memview).base
  *     result.typeinfo = memviewslice.memview.typeinfo             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.view = memviewslice.memview.view
  */
   __pyx_t_4 = __pyx_v_memviewslice.memview->typeinfo;
@@ -18643,7 +18643,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1021
  *     result.typeinfo = memviewslice.memview.typeinfo
- * 
+ *
  *     result.view = memviewslice.memview.view             # <<<<<<<<<<<<<<
  *     result.view.buf = <void *> memviewslice.data
  *     result.view.ndim = ndim
@@ -18652,7 +18652,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   __pyx_v_result->__pyx_base.view = __pyx_t_5;
 
   /* "View.MemoryView":1022
- * 
+ *
  *     result.view = memviewslice.memview.view
  *     result.view.buf = <void *> memviewslice.data             # <<<<<<<<<<<<<<
  *     result.view.ndim = ndim
@@ -18674,7 +18674,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *     result.view.ndim = ndim
  *     (<__pyx_buffer *> &result.view).obj = Py_None             # <<<<<<<<<<<<<<
  *     Py_INCREF(Py_None)
- * 
+ *
  */
   ((Py_buffer *)(&__pyx_v_result->__pyx_base.view))->obj = Py_None;
 
@@ -18682,14 +18682,14 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *     result.view.ndim = ndim
  *     (<__pyx_buffer *> &result.view).obj = Py_None
  *     Py_INCREF(Py_None)             # <<<<<<<<<<<<<<
- * 
+ *
  *     if (<memoryview>memviewslice.memview).flags & PyBUF_WRITABLE:
  */
   Py_INCREF(Py_None);
 
   /* "View.MemoryView":1027
  *     Py_INCREF(Py_None)
- * 
+ *
  *     if (<memoryview>memviewslice.memview).flags & PyBUF_WRITABLE:             # <<<<<<<<<<<<<<
  *         result.flags = PyBUF_RECORDS
  *     else:
@@ -18698,7 +18698,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1028
- * 
+ *
  *     if (<memoryview>memviewslice.memview).flags & PyBUF_WRITABLE:
  *         result.flags = PyBUF_RECORDS             # <<<<<<<<<<<<<<
  *     else:
@@ -18708,7 +18708,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
     /* "View.MemoryView":1027
  *     Py_INCREF(Py_None)
- * 
+ *
  *     if (<memoryview>memviewslice.memview).flags & PyBUF_WRITABLE:             # <<<<<<<<<<<<<<
  *         result.flags = PyBUF_RECORDS
  *     else:
@@ -18720,7 +18720,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *         result.flags = PyBUF_RECORDS
  *     else:
  *         result.flags = PyBUF_RECORDS_RO             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.view.shape = <Py_ssize_t *> result.from_slice.shape
  */
   /*else*/ {
@@ -18730,25 +18730,25 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1032
  *         result.flags = PyBUF_RECORDS_RO
- * 
+ *
  *     result.view.shape = <Py_ssize_t *> result.from_slice.shape             # <<<<<<<<<<<<<<
  *     result.view.strides = <Py_ssize_t *> result.from_slice.strides
- * 
+ *
  */
   __pyx_v_result->__pyx_base.view.shape = ((Py_ssize_t *)__pyx_v_result->from_slice.shape);
 
   /* "View.MemoryView":1033
- * 
+ *
  *     result.view.shape = <Py_ssize_t *> result.from_slice.shape
  *     result.view.strides = <Py_ssize_t *> result.from_slice.strides             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_v_result->__pyx_base.view.strides = ((Py_ssize_t *)__pyx_v_result->from_slice.strides);
 
   /* "View.MemoryView":1036
- * 
- * 
+ *
+ *
  *     result.view.suboffsets = NULL             # <<<<<<<<<<<<<<
  *     for suboffset in result.from_slice.suboffsets[:ndim]:
  *         if suboffset >= 0:
@@ -18756,7 +18756,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   __pyx_v_result->__pyx_base.view.suboffsets = NULL;
 
   /* "View.MemoryView":1037
- * 
+ *
  *     result.view.suboffsets = NULL
  *     for suboffset in result.from_slice.suboffsets[:ndim]:             # <<<<<<<<<<<<<<
  *         if suboffset >= 0:
@@ -18782,7 +18782,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *         if suboffset >= 0:
  *             result.view.suboffsets = <Py_ssize_t *> result.from_slice.suboffsets             # <<<<<<<<<<<<<<
  *             break
- * 
+ *
  */
       __pyx_v_result->__pyx_base.view.suboffsets = ((Py_ssize_t *)__pyx_v_result->from_slice.suboffsets);
 
@@ -18790,7 +18790,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *         if suboffset >= 0:
  *             result.view.suboffsets = <Py_ssize_t *> result.from_slice.suboffsets
  *             break             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.view.len = result.view.itemsize
  */
       goto __pyx_L6_break;
@@ -18808,7 +18808,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1042
  *             break
- * 
+ *
  *     result.view.len = result.view.itemsize             # <<<<<<<<<<<<<<
  *     for length in result.view.shape[:ndim]:
  *         result.view.len *= length
@@ -18817,11 +18817,11 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   __pyx_v_result->__pyx_base.view.len = __pyx_t_9;
 
   /* "View.MemoryView":1043
- * 
+ *
  *     result.view.len = result.view.itemsize
  *     for length in result.view.shape[:ndim]:             # <<<<<<<<<<<<<<
  *         result.view.len *= length
- * 
+ *
  */
   __pyx_t_7 = (__pyx_v_result->__pyx_base.view.shape + __pyx_v_ndim);
   for (__pyx_t_8 = __pyx_v_result->__pyx_base.view.shape; __pyx_t_8 < __pyx_t_7; __pyx_t_8++) {
@@ -18835,7 +18835,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
  *     result.view.len = result.view.itemsize
  *     for length in result.view.shape[:ndim]:
  *         result.view.len *= length             # <<<<<<<<<<<<<<
- * 
+ *
  *     result.to_object_func = to_object_func
  */
     __pyx_t_2 = PyInt_FromSsize_t(__pyx_v_result->__pyx_base.view.len); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 1044, __pyx_L1_error)
@@ -18850,27 +18850,27 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 
   /* "View.MemoryView":1046
  *         result.view.len *= length
- * 
+ *
  *     result.to_object_func = to_object_func             # <<<<<<<<<<<<<<
  *     result.to_dtype_func = to_dtype_func
- * 
+ *
  */
   __pyx_v_result->to_object_func = __pyx_v_to_object_func;
 
   /* "View.MemoryView":1047
- * 
+ *
  *     result.to_object_func = to_object_func
  *     result.to_dtype_func = to_dtype_func             # <<<<<<<<<<<<<<
- * 
+ *
  *     return result
  */
   __pyx_v_result->to_dtype_func = __pyx_v_to_dtype_func;
 
   /* "View.MemoryView":1049
  *     result.to_dtype_func = to_dtype_func
- * 
+ *
  *     return result             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_get_slice_from_memoryview')
  */
   __Pyx_XDECREF(__pyx_r);
@@ -18879,7 +18879,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
   goto __pyx_L0;
 
   /* "View.MemoryView":999
- * 
+ *
  * @cname('__pyx_memoryview_fromslice')
  * cdef memoryview_fromslice(__Pyx_memviewslice memviewslice,             # <<<<<<<<<<<<<<
  *                           int ndim,
@@ -18901,7 +18901,7 @@ static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice __pyx_v_memviewsl
 }
 
 /* "View.MemoryView":1052
- * 
+ *
  * @cname('__pyx_memoryview_get_slice_from_memoryview')
  * cdef __Pyx_memviewslice *get_slice_from_memview(memoryview memview,             # <<<<<<<<<<<<<<
  *                                                    __Pyx_memviewslice *mslice) except NULL:
@@ -18927,7 +18927,7 @@ static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __p
  *         obj = memview
  *         return &obj.from_slice
  */
-  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type);
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
@@ -18968,7 +18968,7 @@ static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __p
  *     else:
  *         slice_copy(memview, mslice)             # <<<<<<<<<<<<<<
  *         return mslice
- * 
+ *
  */
   /*else*/ {
     __pyx_memoryview_slice_copy(__pyx_v_memview, __pyx_v_mslice);
@@ -18977,7 +18977,7 @@ static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __p
  *     else:
  *         slice_copy(memview, mslice)
  *         return mslice             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_slice_copy')
  */
     __pyx_r = __pyx_v_mslice;
@@ -18985,7 +18985,7 @@ static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __p
   }
 
   /* "View.MemoryView":1052
- * 
+ *
  * @cname('__pyx_memoryview_get_slice_from_memoryview')
  * cdef __Pyx_memviewslice *get_slice_from_memview(memoryview memview,             # <<<<<<<<<<<<<<
  *                                                    __Pyx_memviewslice *mslice) except NULL:
@@ -19004,7 +19004,7 @@ static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __p
 }
 
 /* "View.MemoryView":1063
- * 
+ *
  * @cname('__pyx_memoryview_slice_copy')
  * cdef void slice_copy(memoryview memview, __Pyx_memviewslice *dst):             # <<<<<<<<<<<<<<
  *     cdef int dim
@@ -19026,7 +19026,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
 
   /* "View.MemoryView":1067
  *     cdef (Py_ssize_t*) shape, strides, suboffsets
- * 
+ *
  *     shape = memview.view.shape             # <<<<<<<<<<<<<<
  *     strides = memview.view.strides
  *     suboffsets = memview.view.suboffsets
@@ -19035,11 +19035,11 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
   __pyx_v_shape = __pyx_t_1;
 
   /* "View.MemoryView":1068
- * 
+ *
  *     shape = memview.view.shape
  *     strides = memview.view.strides             # <<<<<<<<<<<<<<
  *     suboffsets = memview.view.suboffsets
- * 
+ *
  */
   __pyx_t_1 = __pyx_v_memview->view.strides;
   __pyx_v_strides = __pyx_t_1;
@@ -19048,7 +19048,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
  *     shape = memview.view.shape
  *     strides = memview.view.strides
  *     suboffsets = memview.view.suboffsets             # <<<<<<<<<<<<<<
- * 
+ *
  *     dst.memview = <__pyx_memoryview *> memview
  */
   __pyx_t_1 = __pyx_v_memview->view.suboffsets;
@@ -19056,25 +19056,25 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
 
   /* "View.MemoryView":1071
  *     suboffsets = memview.view.suboffsets
- * 
+ *
  *     dst.memview = <__pyx_memoryview *> memview             # <<<<<<<<<<<<<<
  *     dst.data = <char *> memview.view.buf
- * 
+ *
  */
   __pyx_v_dst->memview = ((struct __pyx_memoryview_obj *)__pyx_v_memview);
 
   /* "View.MemoryView":1072
- * 
+ *
  *     dst.memview = <__pyx_memoryview *> memview
  *     dst.data = <char *> memview.view.buf             # <<<<<<<<<<<<<<
- * 
+ *
  *     for dim in range(memview.view.ndim):
  */
   __pyx_v_dst->data = ((char *)__pyx_v_memview->view.buf);
 
   /* "View.MemoryView":1074
  *     dst.data = <char *> memview.view.buf
- * 
+ *
  *     for dim in range(memview.view.ndim):             # <<<<<<<<<<<<<<
  *         dst.shape[dim] = shape[dim]
  *         dst.strides[dim] = strides[dim]
@@ -19085,7 +19085,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
     __pyx_v_dim = __pyx_t_4;
 
     /* "View.MemoryView":1075
- * 
+ *
  *     for dim in range(memview.view.ndim):
  *         dst.shape[dim] = shape[dim]             # <<<<<<<<<<<<<<
  *         dst.strides[dim] = strides[dim]
@@ -19098,7 +19098,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
  *         dst.shape[dim] = shape[dim]
  *         dst.strides[dim] = strides[dim]             # <<<<<<<<<<<<<<
  *         dst.suboffsets[dim] = suboffsets[dim] if suboffsets else -1
- * 
+ *
  */
     (__pyx_v_dst->strides[__pyx_v_dim]) = (__pyx_v_strides[__pyx_v_dim]);
 
@@ -19106,7 +19106,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
  *         dst.shape[dim] = shape[dim]
  *         dst.strides[dim] = strides[dim]
  *         dst.suboffsets[dim] = suboffsets[dim] if suboffsets else -1             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_copy_object')
  */
     if ((__pyx_v_suboffsets != 0)) {
@@ -19118,7 +19118,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
   }
 
   /* "View.MemoryView":1063
- * 
+ *
  * @cname('__pyx_memoryview_slice_copy')
  * cdef void slice_copy(memoryview memview, __Pyx_memviewslice *dst):             # <<<<<<<<<<<<<<
  *     cdef int dim
@@ -19130,7 +19130,7 @@ static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *__pyx_v_mem
 }
 
 /* "View.MemoryView":1080
- * 
+ *
  * @cname('__pyx_memoryview_copy_object')
  * cdef memoryview_copy(memoryview memview):             # <<<<<<<<<<<<<<
  *     "Create a new memoryview object"
@@ -19152,7 +19152,7 @@ static PyObject *__pyx_memoryview_copy_object(struct __pyx_memoryview_obj *__pyx
  *     cdef __Pyx_memviewslice memviewslice
  *     slice_copy(memview, &memviewslice)             # <<<<<<<<<<<<<<
  *     return memoryview_copy_from_slice(memview, &memviewslice)
- * 
+ *
  */
   __pyx_memoryview_slice_copy(__pyx_v_memview, (&__pyx_v_memviewslice));
 
@@ -19160,7 +19160,7 @@ static PyObject *__pyx_memoryview_copy_object(struct __pyx_memoryview_obj *__pyx
  *     cdef __Pyx_memviewslice memviewslice
  *     slice_copy(memview, &memviewslice)
  *     return memoryview_copy_from_slice(memview, &memviewslice)             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_copy_object_from_slice')
  */
   __Pyx_XDECREF(__pyx_r);
@@ -19171,7 +19171,7 @@ static PyObject *__pyx_memoryview_copy_object(struct __pyx_memoryview_obj *__pyx
   goto __pyx_L0;
 
   /* "View.MemoryView":1080
- * 
+ *
  * @cname('__pyx_memoryview_copy_object')
  * cdef memoryview_copy(memoryview memview):             # <<<<<<<<<<<<<<
  *     "Create a new memoryview object"
@@ -19190,7 +19190,7 @@ static PyObject *__pyx_memoryview_copy_object(struct __pyx_memoryview_obj *__pyx
 }
 
 /* "View.MemoryView":1087
- * 
+ *
  * @cname('__pyx_memoryview_copy_object_from_slice')
  * cdef memoryview_copy_from_slice(memoryview memview, __Pyx_memviewslice *memviewslice):             # <<<<<<<<<<<<<<
  *     """
@@ -19214,17 +19214,17 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
 
   /* "View.MemoryView":1094
  *     cdef int (*to_dtype_func)(char *, object) except 0
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         to_object_func = (<_memoryviewslice> memview).to_object_func
  *         to_dtype_func = (<_memoryviewslice> memview).to_dtype_func
  */
-  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type); 
+  __pyx_t_1 = __Pyx_TypeCheck(((PyObject *)__pyx_v_memview), __pyx_memoryviewslice_type);
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
     /* "View.MemoryView":1095
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):
  *         to_object_func = (<_memoryviewslice> memview).to_object_func             # <<<<<<<<<<<<<<
  *         to_dtype_func = (<_memoryviewslice> memview).to_dtype_func
@@ -19245,7 +19245,7 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
 
     /* "View.MemoryView":1094
  *     cdef int (*to_dtype_func)(char *, object) except 0
- * 
+ *
  *     if isinstance(memview, _memoryviewslice):             # <<<<<<<<<<<<<<
  *         to_object_func = (<_memoryviewslice> memview).to_object_func
  *         to_dtype_func = (<_memoryviewslice> memview).to_dtype_func
@@ -19258,7 +19258,7 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
  *     else:
  *         to_object_func = NULL             # <<<<<<<<<<<<<<
  *         to_dtype_func = NULL
- * 
+ *
  */
   /*else*/ {
     __pyx_v_to_object_func = NULL;
@@ -19267,7 +19267,7 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
  *     else:
  *         to_object_func = NULL
  *         to_dtype_func = NULL             # <<<<<<<<<<<<<<
- * 
+ *
  *     return memoryview_fromslice(memviewslice[0], memview.view.ndim,
  */
     __pyx_v_to_dtype_func = NULL;
@@ -19276,7 +19276,7 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
 
   /* "View.MemoryView":1101
  *         to_dtype_func = NULL
- * 
+ *
  *     return memoryview_fromslice(memviewslice[0], memview.view.ndim,             # <<<<<<<<<<<<<<
  *                                 to_object_func, to_dtype_func,
  *                                 memview.dtype_is_object)
@@ -19287,8 +19287,8 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
  *     return memoryview_fromslice(memviewslice[0], memview.view.ndim,
  *                                 to_object_func, to_dtype_func,
  *                                 memview.dtype_is_object)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_5 = __pyx_memoryview_fromslice((__pyx_v_memviewslice[0]), __pyx_v_memview->view.ndim, __pyx_v_to_object_func, __pyx_v_to_dtype_func, __pyx_v_memview->dtype_is_object); if (unlikely(!__pyx_t_5)) __PYX_ERR(2, 1101, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
@@ -19297,7 +19297,7 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
   goto __pyx_L0;
 
   /* "View.MemoryView":1087
- * 
+ *
  * @cname('__pyx_memoryview_copy_object_from_slice')
  * cdef memoryview_copy_from_slice(memoryview memview, __Pyx_memviewslice *memviewslice):             # <<<<<<<<<<<<<<
  *     """
@@ -19316,8 +19316,8 @@ static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview
 }
 
 /* "View.MemoryView":1109
- * 
- * 
+ *
+ *
  * cdef Py_ssize_t abs_py_ssize_t(Py_ssize_t arg) nogil:             # <<<<<<<<<<<<<<
  *     if arg < 0:
  *         return -arg
@@ -19328,7 +19328,7 @@ static Py_ssize_t abs_py_ssize_t(Py_ssize_t __pyx_v_arg) {
   int __pyx_t_1;
 
   /* "View.MemoryView":1110
- * 
+ *
  * cdef Py_ssize_t abs_py_ssize_t(Py_ssize_t arg) nogil:
  *     if arg < 0:             # <<<<<<<<<<<<<<
  *         return -arg
@@ -19348,7 +19348,7 @@ static Py_ssize_t abs_py_ssize_t(Py_ssize_t __pyx_v_arg) {
     goto __pyx_L0;
 
     /* "View.MemoryView":1110
- * 
+ *
  * cdef Py_ssize_t abs_py_ssize_t(Py_ssize_t arg) nogil:
  *     if arg < 0:             # <<<<<<<<<<<<<<
  *         return -arg
@@ -19360,7 +19360,7 @@ static Py_ssize_t abs_py_ssize_t(Py_ssize_t __pyx_v_arg) {
  *         return -arg
  *     else:
  *         return arg             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_get_best_slice_order')
  */
   /*else*/ {
@@ -19369,8 +19369,8 @@ static Py_ssize_t abs_py_ssize_t(Py_ssize_t __pyx_v_arg) {
   }
 
   /* "View.MemoryView":1109
- * 
- * 
+ *
+ *
  * cdef Py_ssize_t abs_py_ssize_t(Py_ssize_t arg) nogil:             # <<<<<<<<<<<<<<
  *     if arg < 0:
  *         return -arg
@@ -19382,7 +19382,7 @@ static Py_ssize_t abs_py_ssize_t(Py_ssize_t __pyx_v_arg) {
 }
 
 /* "View.MemoryView":1116
- * 
+ *
  * @cname('__pyx_get_best_slice_order')
  * cdef char get_best_order(__Pyx_memviewslice *mslice, int ndim) nogil:             # <<<<<<<<<<<<<<
  *     """
@@ -19404,7 +19404,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *     cdef int i
  *     cdef Py_ssize_t c_stride = 0             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t f_stride = 0
- * 
+ *
  */
   __pyx_v_c_stride = 0;
 
@@ -19412,14 +19412,14 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *     cdef int i
  *     cdef Py_ssize_t c_stride = 0
  *     cdef Py_ssize_t f_stride = 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):
  */
   __pyx_v_f_stride = 0;
 
   /* "View.MemoryView":1124
  *     cdef Py_ssize_t f_stride = 0
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):             # <<<<<<<<<<<<<<
  *         if mslice.shape[i] > 1:
  *             c_stride = mslice.strides[i]
@@ -19428,7 +19428,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
     __pyx_v_i = __pyx_t_1;
 
     /* "View.MemoryView":1125
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):
  *         if mslice.shape[i] > 1:             # <<<<<<<<<<<<<<
  *             c_stride = mslice.strides[i]
@@ -19442,7 +19442,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *         if mslice.shape[i] > 1:
  *             c_stride = mslice.strides[i]             # <<<<<<<<<<<<<<
  *             break
- * 
+ *
  */
       __pyx_v_c_stride = (__pyx_v_mslice->strides[__pyx_v_i]);
 
@@ -19450,13 +19450,13 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *         if mslice.shape[i] > 1:
  *             c_stride = mslice.strides[i]
  *             break             # <<<<<<<<<<<<<<
- * 
+ *
  *     for i in range(ndim):
  */
       goto __pyx_L4_break;
 
       /* "View.MemoryView":1125
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):
  *         if mslice.shape[i] > 1:             # <<<<<<<<<<<<<<
  *             c_stride = mslice.strides[i]
@@ -19468,7 +19468,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
 
   /* "View.MemoryView":1129
  *             break
- * 
+ *
  *     for i in range(ndim):             # <<<<<<<<<<<<<<
  *         if mslice.shape[i] > 1:
  *             f_stride = mslice.strides[i]
@@ -19479,7 +19479,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
     __pyx_v_i = __pyx_t_4;
 
     /* "View.MemoryView":1130
- * 
+ *
  *     for i in range(ndim):
  *         if mslice.shape[i] > 1:             # <<<<<<<<<<<<<<
  *             f_stride = mslice.strides[i]
@@ -19493,7 +19493,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *         if mslice.shape[i] > 1:
  *             f_stride = mslice.strides[i]             # <<<<<<<<<<<<<<
  *             break
- * 
+ *
  */
       __pyx_v_f_stride = (__pyx_v_mslice->strides[__pyx_v_i]);
 
@@ -19501,13 +19501,13 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *         if mslice.shape[i] > 1:
  *             f_stride = mslice.strides[i]
  *             break             # <<<<<<<<<<<<<<
- * 
+ *
  *     if abs_py_ssize_t(c_stride) <= abs_py_ssize_t(f_stride):
  */
       goto __pyx_L7_break;
 
       /* "View.MemoryView":1130
- * 
+ *
  *     for i in range(ndim):
  *         if mslice.shape[i] > 1:             # <<<<<<<<<<<<<<
  *             f_stride = mslice.strides[i]
@@ -19519,7 +19519,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
 
   /* "View.MemoryView":1134
  *             break
- * 
+ *
  *     if abs_py_ssize_t(c_stride) <= abs_py_ssize_t(f_stride):             # <<<<<<<<<<<<<<
  *         return 'C'
  *     else:
@@ -19528,7 +19528,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
   if (__pyx_t_2) {
 
     /* "View.MemoryView":1135
- * 
+ *
  *     if abs_py_ssize_t(c_stride) <= abs_py_ssize_t(f_stride):
  *         return 'C'             # <<<<<<<<<<<<<<
  *     else:
@@ -19539,7 +19539,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
 
     /* "View.MemoryView":1134
  *             break
- * 
+ *
  *     if abs_py_ssize_t(c_stride) <= abs_py_ssize_t(f_stride):             # <<<<<<<<<<<<<<
  *         return 'C'
  *     else:
@@ -19550,7 +19550,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
  *         return 'C'
  *     else:
  *         return 'F'             # <<<<<<<<<<<<<<
- * 
+ *
  * @cython.cdivision(True)
  */
   /*else*/ {
@@ -19559,7 +19559,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
   }
 
   /* "View.MemoryView":1116
- * 
+ *
  * @cname('__pyx_get_best_slice_order')
  * cdef char get_best_order(__Pyx_memviewslice *mslice, int ndim) nogil:             # <<<<<<<<<<<<<<
  *     """
@@ -19572,7 +19572,7 @@ static char __pyx_get_best_slice_order(__Pyx_memviewslice *__pyx_v_mslice, int _
 }
 
 /* "View.MemoryView":1140
- * 
+ *
  * @cython.cdivision(True)
  * cdef void _copy_strided_to_strided(char *src_data, Py_ssize_t *src_strides,             # <<<<<<<<<<<<<<
  *                                    char *dst_data, Py_ssize_t *dst_strides,
@@ -19593,7 +19593,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
   Py_ssize_t __pyx_t_6;
 
   /* "View.MemoryView":1147
- * 
+ *
  *     cdef Py_ssize_t i
  *     cdef Py_ssize_t src_extent = src_shape[0]             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t dst_extent = dst_shape[0]
@@ -19615,7 +19615,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
  *     cdef Py_ssize_t dst_extent = dst_shape[0]
  *     cdef Py_ssize_t src_stride = src_strides[0]             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t dst_stride = dst_strides[0]
- * 
+ *
  */
   __pyx_v_src_stride = (__pyx_v_src_strides[0]);
 
@@ -19623,14 +19623,14 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
  *     cdef Py_ssize_t dst_extent = dst_shape[0]
  *     cdef Py_ssize_t src_stride = src_strides[0]
  *     cdef Py_ssize_t dst_stride = dst_strides[0]             # <<<<<<<<<<<<<<
- * 
+ *
  *     if ndim == 1:
  */
   __pyx_v_dst_stride = (__pyx_v_dst_strides[0]);
 
   /* "View.MemoryView":1152
  *     cdef Py_ssize_t dst_stride = dst_strides[0]
- * 
+ *
  *     if ndim == 1:             # <<<<<<<<<<<<<<
  *        if (src_stride > 0 and dst_stride > 0 and
  *            <size_t> src_stride == itemsize == <size_t> dst_stride):
@@ -19639,7 +19639,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1153
- * 
+ *
  *     if ndim == 1:
  *        if (src_stride > 0 and dst_stride > 0 and             # <<<<<<<<<<<<<<
  *            <size_t> src_stride == itemsize == <size_t> dst_stride):
@@ -19674,7 +19674,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
     __pyx_L5_bool_binop_done:;
 
     /* "View.MemoryView":1153
- * 
+ *
  *     if ndim == 1:
  *        if (src_stride > 0 and dst_stride > 0 and             # <<<<<<<<<<<<<<
  *            <size_t> src_stride == itemsize == <size_t> dst_stride):
@@ -19692,7 +19692,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
       (void)(memcpy(__pyx_v_dst_data, __pyx_v_src_data, (__pyx_v_itemsize * __pyx_v_dst_extent)));
 
       /* "View.MemoryView":1153
- * 
+ *
  *     if ndim == 1:
  *        if (src_stride > 0 and dst_stride > 0 and             # <<<<<<<<<<<<<<
  *            <size_t> src_stride == itemsize == <size_t> dst_stride):
@@ -19746,7 +19746,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
 
     /* "View.MemoryView":1152
  *     cdef Py_ssize_t dst_stride = dst_strides[0]
- * 
+ *
  *     if ndim == 1:             # <<<<<<<<<<<<<<
  *        if (src_stride > 0 and dst_stride > 0 and
  *            <size_t> src_stride == itemsize == <size_t> dst_stride):
@@ -19781,7 +19781,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
  *                                      ndim - 1, itemsize)
  *             src_data += src_stride             # <<<<<<<<<<<<<<
  *             dst_data += dst_stride
- * 
+ *
  */
       __pyx_v_src_data = (__pyx_v_src_data + __pyx_v_src_stride);
 
@@ -19789,7 +19789,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
  *                                      ndim - 1, itemsize)
  *             src_data += src_stride
  *             dst_data += dst_stride             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef void copy_strided_to_strided(__Pyx_memviewslice *src,
  */
       __pyx_v_dst_data = (__pyx_v_dst_data + __pyx_v_dst_stride);
@@ -19798,7 +19798,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
   __pyx_L3:;
 
   /* "View.MemoryView":1140
- * 
+ *
  * @cython.cdivision(True)
  * cdef void _copy_strided_to_strided(char *src_data, Py_ssize_t *src_strides,             # <<<<<<<<<<<<<<
  *                                    char *dst_data, Py_ssize_t *dst_strides,
@@ -19810,7 +19810,7 @@ static void _copy_strided_to_strided(char *__pyx_v_src_data, Py_ssize_t *__pyx_v
 
 /* "View.MemoryView":1170
  *             dst_data += dst_stride
- * 
+ *
  * cdef void copy_strided_to_strided(__Pyx_memviewslice *src,             # <<<<<<<<<<<<<<
  *                                   __Pyx_memviewslice *dst,
  *                                   int ndim, size_t itemsize) nogil:
@@ -19823,13 +19823,13 @@ static void copy_strided_to_strided(__Pyx_memviewslice *__pyx_v_src, __Pyx_memvi
  *                                   int ndim, size_t itemsize) nogil:
  *     _copy_strided_to_strided(src.data, src.strides, dst.data, dst.strides,             # <<<<<<<<<<<<<<
  *                              src.shape, dst.shape, ndim, itemsize)
- * 
+ *
  */
   _copy_strided_to_strided(__pyx_v_src->data, __pyx_v_src->strides, __pyx_v_dst->data, __pyx_v_dst->strides, __pyx_v_src->shape, __pyx_v_dst->shape, __pyx_v_ndim, __pyx_v_itemsize);
 
   /* "View.MemoryView":1170
  *             dst_data += dst_stride
- * 
+ *
  * cdef void copy_strided_to_strided(__Pyx_memviewslice *src,             # <<<<<<<<<<<<<<
  *                                   __Pyx_memviewslice *dst,
  *                                   int ndim, size_t itemsize) nogil:
@@ -19839,7 +19839,7 @@ static void copy_strided_to_strided(__Pyx_memviewslice *__pyx_v_src, __Pyx_memvi
 }
 
 /* "View.MemoryView":1177
- * 
+ *
  * @cname('__pyx_memoryview_slice_get_size')
  * cdef Py_ssize_t slice_get_size(__Pyx_memviewslice *src, int ndim) nogil:             # <<<<<<<<<<<<<<
  *     "Return the size of the memory occupied by the slice in number of bytes"
@@ -19859,7 +19859,7 @@ static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *__pyx_v_sr
  * cdef Py_ssize_t slice_get_size(__Pyx_memviewslice *src, int ndim) nogil:
  *     "Return the size of the memory occupied by the slice in number of bytes"
  *     cdef Py_ssize_t shape, size = src.memview.view.itemsize             # <<<<<<<<<<<<<<
- * 
+ *
  *     for shape in src.shape[:ndim]:
  */
   __pyx_t_1 = __pyx_v_src->memview->view.itemsize;
@@ -19867,10 +19867,10 @@ static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *__pyx_v_sr
 
   /* "View.MemoryView":1181
  *     cdef Py_ssize_t shape, size = src.memview.view.itemsize
- * 
+ *
  *     for shape in src.shape[:ndim]:             # <<<<<<<<<<<<<<
  *         size *= shape
- * 
+ *
  */
   __pyx_t_3 = (__pyx_v_src->shape + __pyx_v_ndim);
   for (__pyx_t_4 = __pyx_v_src->shape; __pyx_t_4 < __pyx_t_3; __pyx_t_4++) {
@@ -19878,10 +19878,10 @@ static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *__pyx_v_sr
     __pyx_v_shape = (__pyx_t_2[0]);
 
     /* "View.MemoryView":1182
- * 
+ *
  *     for shape in src.shape[:ndim]:
  *         size *= shape             # <<<<<<<<<<<<<<
- * 
+ *
  *     return size
  */
     __pyx_v_size = (__pyx_v_size * __pyx_v_shape);
@@ -19889,16 +19889,16 @@ static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *__pyx_v_sr
 
   /* "View.MemoryView":1184
  *         size *= shape
- * 
+ *
  *     return size             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_fill_contig_strides_array')
  */
   __pyx_r = __pyx_v_size;
   goto __pyx_L0;
 
   /* "View.MemoryView":1177
- * 
+ *
  * @cname('__pyx_memoryview_slice_get_size')
  * cdef Py_ssize_t slice_get_size(__Pyx_memviewslice *src, int ndim) nogil:             # <<<<<<<<<<<<<<
  *     "Return the size of the memory occupied by the slice in number of bytes"
@@ -19911,7 +19911,7 @@ static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *__pyx_v_sr
 }
 
 /* "View.MemoryView":1187
- * 
+ *
  * @cname('__pyx_fill_contig_strides_array')
  * cdef Py_ssize_t fill_contig_strides_array(             # <<<<<<<<<<<<<<
  *                 Py_ssize_t *shape, Py_ssize_t *strides, Py_ssize_t stride,
@@ -19928,7 +19928,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
 
   /* "View.MemoryView":1196
  *     cdef int idx
- * 
+ *
  *     if order == 'F':             # <<<<<<<<<<<<<<
  *         for idx in range(ndim):
  *             strides[idx] = stride
@@ -19937,7 +19937,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1197
- * 
+ *
  *     if order == 'F':
  *         for idx in range(ndim):             # <<<<<<<<<<<<<<
  *             strides[idx] = stride
@@ -19969,7 +19969,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
 
     /* "View.MemoryView":1196
  *     cdef int idx
- * 
+ *
  *     if order == 'F':             # <<<<<<<<<<<<<<
  *         for idx in range(ndim):
  *             strides[idx] = stride
@@ -19993,7 +19993,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
  *         for idx in range(ndim - 1, -1, -1):
  *             strides[idx] = stride             # <<<<<<<<<<<<<<
  *             stride *= shape[idx]
- * 
+ *
  */
       (__pyx_v_strides[__pyx_v_idx]) = __pyx_v_stride;
 
@@ -20001,7 +20001,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
  *         for idx in range(ndim - 1, -1, -1):
  *             strides[idx] = stride
  *             stride *= shape[idx]             # <<<<<<<<<<<<<<
- * 
+ *
  *     return stride
  */
       __pyx_v_stride = (__pyx_v_stride * (__pyx_v_shape[__pyx_v_idx]));
@@ -20011,16 +20011,16 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
 
   /* "View.MemoryView":1205
  *             stride *= shape[idx]
- * 
+ *
  *     return stride             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_copy_data_to_temp')
  */
   __pyx_r = __pyx_v_stride;
   goto __pyx_L0;
 
   /* "View.MemoryView":1187
- * 
+ *
  * @cname('__pyx_fill_contig_strides_array')
  * cdef Py_ssize_t fill_contig_strides_array(             # <<<<<<<<<<<<<<
  *                 Py_ssize_t *shape, Py_ssize_t *strides, Py_ssize_t stride,
@@ -20033,7 +20033,7 @@ static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *__pyx_v_shape, Py_
 }
 
 /* "View.MemoryView":1208
- * 
+ *
  * @cname('__pyx_memoryview_copy_data_to_temp')
  * cdef void *copy_data_to_temp(__Pyx_memviewslice *src,             # <<<<<<<<<<<<<<
  *                              __Pyx_memviewslice *tmpslice,
@@ -20058,26 +20058,26 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
 
   /* "View.MemoryView":1219
  *     cdef void *result
- * 
+ *
  *     cdef size_t itemsize = src.memview.view.itemsize             # <<<<<<<<<<<<<<
  *     cdef size_t size = slice_get_size(src, ndim)
- * 
+ *
  */
   __pyx_t_1 = __pyx_v_src->memview->view.itemsize;
   __pyx_v_itemsize = __pyx_t_1;
 
   /* "View.MemoryView":1220
- * 
+ *
  *     cdef size_t itemsize = src.memview.view.itemsize
  *     cdef size_t size = slice_get_size(src, ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     result = malloc(size)
  */
   __pyx_v_size = __pyx_memoryview_slice_get_size(__pyx_v_src, __pyx_v_ndim);
 
   /* "View.MemoryView":1222
  *     cdef size_t size = slice_get_size(src, ndim)
- * 
+ *
  *     result = malloc(size)             # <<<<<<<<<<<<<<
  *     if not result:
  *         _err(MemoryError, NULL)
@@ -20085,11 +20085,11 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
   __pyx_v_result = malloc(__pyx_v_size);
 
   /* "View.MemoryView":1223
- * 
+ *
  *     result = malloc(size)
  *     if not result:             # <<<<<<<<<<<<<<
  *         _err(MemoryError, NULL)
- * 
+ *
  */
   __pyx_t_2 = ((!(__pyx_v_result != 0)) != 0);
   if (__pyx_t_2) {
@@ -20098,23 +20098,23 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
  *     result = malloc(size)
  *     if not result:
  *         _err(MemoryError, NULL)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_t_3 = __pyx_memoryview_err(__pyx_builtin_MemoryError, NULL); if (unlikely(__pyx_t_3 == ((int)-1))) __PYX_ERR(2, 1224, __pyx_L1_error)
 
     /* "View.MemoryView":1223
- * 
+ *
  *     result = malloc(size)
  *     if not result:             # <<<<<<<<<<<<<<
  *         _err(MemoryError, NULL)
- * 
+ *
  */
   }
 
   /* "View.MemoryView":1227
- * 
- * 
+ *
+ *
  *     tmpslice.data = <char *> result             # <<<<<<<<<<<<<<
  *     tmpslice.memview = src.memview
  *     for i in range(ndim):
@@ -20122,7 +20122,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
   __pyx_v_tmpslice->data = ((char *)__pyx_v_result);
 
   /* "View.MemoryView":1228
- * 
+ *
  *     tmpslice.data = <char *> result
  *     tmpslice.memview = src.memview             # <<<<<<<<<<<<<<
  *     for i in range(ndim):
@@ -20148,7 +20148,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
  *     for i in range(ndim):
  *         tmpslice.shape[i] = src.shape[i]             # <<<<<<<<<<<<<<
  *         tmpslice.suboffsets[i] = -1
- * 
+ *
  */
     (__pyx_v_tmpslice->shape[__pyx_v_i]) = (__pyx_v_src->shape[__pyx_v_i]);
 
@@ -20156,7 +20156,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
  *     for i in range(ndim):
  *         tmpslice.shape[i] = src.shape[i]
  *         tmpslice.suboffsets[i] = -1             # <<<<<<<<<<<<<<
- * 
+ *
  *     fill_contig_strides_array(&tmpslice.shape[0], &tmpslice.strides[0], itemsize,
  */
     (__pyx_v_tmpslice->suboffsets[__pyx_v_i]) = -1L;
@@ -20164,16 +20164,16 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
 
   /* "View.MemoryView":1233
  *         tmpslice.suboffsets[i] = -1
- * 
+ *
  *     fill_contig_strides_array(&tmpslice.shape[0], &tmpslice.strides[0], itemsize,             # <<<<<<<<<<<<<<
  *                               ndim, order)
- * 
+ *
  */
   (void)(__pyx_fill_contig_strides_array((&(__pyx_v_tmpslice->shape[0])), (&(__pyx_v_tmpslice->strides[0])), __pyx_v_itemsize, __pyx_v_ndim, __pyx_v_order));
 
   /* "View.MemoryView":1237
- * 
- * 
+ *
+ *
  *     for i in range(ndim):             # <<<<<<<<<<<<<<
  *         if tmpslice.shape[i] == 1:
  *             tmpslice.strides[i] = 0
@@ -20184,11 +20184,11 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
     __pyx_v_i = __pyx_t_6;
 
     /* "View.MemoryView":1238
- * 
+ *
  *     for i in range(ndim):
  *         if tmpslice.shape[i] == 1:             # <<<<<<<<<<<<<<
  *             tmpslice.strides[i] = 0
- * 
+ *
  */
     __pyx_t_2 = (((__pyx_v_tmpslice->shape[__pyx_v_i]) == 1) != 0);
     if (__pyx_t_2) {
@@ -20197,24 +20197,24 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
  *     for i in range(ndim):
  *         if tmpslice.shape[i] == 1:
  *             tmpslice.strides[i] = 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     if slice_is_contig(src[0], order, ndim):
  */
       (__pyx_v_tmpslice->strides[__pyx_v_i]) = 0;
 
       /* "View.MemoryView":1238
- * 
+ *
  *     for i in range(ndim):
  *         if tmpslice.shape[i] == 1:             # <<<<<<<<<<<<<<
  *             tmpslice.strides[i] = 0
- * 
+ *
  */
     }
   }
 
   /* "View.MemoryView":1241
  *             tmpslice.strides[i] = 0
- * 
+ *
  *     if slice_is_contig(src[0], order, ndim):             # <<<<<<<<<<<<<<
  *         memcpy(result, src.data, size)
  *     else:
@@ -20223,7 +20223,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
   if (__pyx_t_2) {
 
     /* "View.MemoryView":1242
- * 
+ *
  *     if slice_is_contig(src[0], order, ndim):
  *         memcpy(result, src.data, size)             # <<<<<<<<<<<<<<
  *     else:
@@ -20233,7 +20233,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
 
     /* "View.MemoryView":1241
  *             tmpslice.strides[i] = 0
- * 
+ *
  *     if slice_is_contig(src[0], order, ndim):             # <<<<<<<<<<<<<<
  *         memcpy(result, src.data, size)
  *     else:
@@ -20245,7 +20245,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
  *         memcpy(result, src.data, size)
  *     else:
  *         copy_strided_to_strided(src, tmpslice, ndim, itemsize)             # <<<<<<<<<<<<<<
- * 
+ *
  *     return result
  */
   /*else*/ {
@@ -20255,16 +20255,16 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
 
   /* "View.MemoryView":1246
  *         copy_strided_to_strided(src, tmpslice, ndim, itemsize)
- * 
+ *
  *     return result             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_r = __pyx_v_result;
   goto __pyx_L0;
 
   /* "View.MemoryView":1208
- * 
+ *
  * @cname('__pyx_memoryview_copy_data_to_temp')
  * cdef void *copy_data_to_temp(__Pyx_memviewslice *src,             # <<<<<<<<<<<<<<
  *                              __Pyx_memviewslice *tmpslice,
@@ -20288,7 +20288,7 @@ static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *__pyx_v_src,
 }
 
 /* "View.MemoryView":1251
- * 
+ *
  * @cname('__pyx_memoryview_err_extents')
  * cdef int _err_extents(int i, Py_ssize_t extent1,             # <<<<<<<<<<<<<<
  *                              Py_ssize_t extent2) except -1 with gil:
@@ -20314,7 +20314,7 @@ static int __pyx_memoryview_err_extents(int __pyx_v_i, Py_ssize_t __pyx_v_extent
  *                              Py_ssize_t extent2) except -1 with gil:
  *     raise ValueError("got differing extents in dimension %d (got %d and %d)" %
  *                                                         (i, extent1, extent2))             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_err_dim')
  */
   __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_i); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 1254, __pyx_L1_error)
@@ -20340,7 +20340,7 @@ static int __pyx_memoryview_err_extents(int __pyx_v_i, Py_ssize_t __pyx_v_extent
  *                              Py_ssize_t extent2) except -1 with gil:
  *     raise ValueError("got differing extents in dimension %d (got %d and %d)" %             # <<<<<<<<<<<<<<
  *                                                         (i, extent1, extent2))
- * 
+ *
  */
   __pyx_t_3 = __Pyx_PyString_Format(__pyx_kp_s_got_differing_extents_in_dimensi, __pyx_t_4); if (unlikely(!__pyx_t_3)) __PYX_ERR(2, 1253, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
@@ -20353,7 +20353,7 @@ static int __pyx_memoryview_err_extents(int __pyx_v_i, Py_ssize_t __pyx_v_extent
   __PYX_ERR(2, 1253, __pyx_L1_error)
 
   /* "View.MemoryView":1251
- * 
+ *
  * @cname('__pyx_memoryview_err_extents')
  * cdef int _err_extents(int i, Py_ssize_t extent1,             # <<<<<<<<<<<<<<
  *                              Py_ssize_t extent2) except -1 with gil:
@@ -20376,11 +20376,11 @@ static int __pyx_memoryview_err_extents(int __pyx_v_i, Py_ssize_t __pyx_v_extent
 }
 
 /* "View.MemoryView":1257
- * 
+ *
  * @cname('__pyx_memoryview_err_dim')
  * cdef int _err_dim(object error, char *msg, int dim) except -1 with gil:             # <<<<<<<<<<<<<<
  *     raise error(msg.decode('ascii') % dim)
- * 
+ *
  */
 
 static int __pyx_memoryview_err_dim(PyObject *__pyx_v_error, char *__pyx_v_msg, int __pyx_v_dim) {
@@ -20403,7 +20403,7 @@ static int __pyx_memoryview_err_dim(PyObject *__pyx_v_error, char *__pyx_v_msg,
  * @cname('__pyx_memoryview_err_dim')
  * cdef int _err_dim(object error, char *msg, int dim) except -1 with gil:
  *     raise error(msg.decode('ascii') % dim)             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_err')
  */
   __pyx_t_2 = __Pyx_decode_c_string(__pyx_v_msg, 0, strlen(__pyx_v_msg), NULL, NULL, PyUnicode_DecodeASCII); if (unlikely(!__pyx_t_2)) __PYX_ERR(2, 1258, __pyx_L1_error)
@@ -20436,11 +20436,11 @@ static int __pyx_memoryview_err_dim(PyObject *__pyx_v_error, char *__pyx_v_msg,
   __PYX_ERR(2, 1258, __pyx_L1_error)
 
   /* "View.MemoryView":1257
- * 
+ *
  * @cname('__pyx_memoryview_err_dim')
  * cdef int _err_dim(object error, char *msg, int dim) except -1 with gil:             # <<<<<<<<<<<<<<
  *     raise error(msg.decode('ascii') % dim)
- * 
+ *
  */
 
   /* function exit code */
@@ -20460,7 +20460,7 @@ static int __pyx_memoryview_err_dim(PyObject *__pyx_v_error, char *__pyx_v_msg,
 }
 
 /* "View.MemoryView":1261
- * 
+ *
  * @cname('__pyx_memoryview_err')
  * cdef int _err(object error, char *msg) except -1 with gil:             # <<<<<<<<<<<<<<
  *     if msg != NULL:
@@ -20537,7 +20537,7 @@ static int __pyx_memoryview_err(PyObject *__pyx_v_error, char *__pyx_v_msg) {
  *         raise error(msg.decode('ascii'))
  *     else:
  *         raise error             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_copy_contents')
  */
   /*else*/ {
@@ -20546,7 +20546,7 @@ static int __pyx_memoryview_err(PyObject *__pyx_v_error, char *__pyx_v_msg) {
   }
 
   /* "View.MemoryView":1261
- * 
+ *
  * @cname('__pyx_memoryview_err')
  * cdef int _err(object error, char *msg) except -1 with gil:             # <<<<<<<<<<<<<<
  *     if msg != NULL:
@@ -20570,7 +20570,7 @@ static int __pyx_memoryview_err(PyObject *__pyx_v_error, char *__pyx_v_msg) {
 }
 
 /* "View.MemoryView":1268
- * 
+ *
  * @cname('__pyx_memoryview_copy_contents')
  * cdef int memoryview_copy_contents(__Pyx_memviewslice src,             # <<<<<<<<<<<<<<
  *                                   __Pyx_memviewslice dst,
@@ -20641,13 +20641,13 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *     cdef bint broadcasting = False
  *     cdef bint direct_copy = False             # <<<<<<<<<<<<<<
  *     cdef __Pyx_memviewslice tmp
- * 
+ *
  */
   __pyx_v_direct_copy = 0;
 
   /* "View.MemoryView":1284
  *     cdef __Pyx_memviewslice tmp
- * 
+ *
  *     if src_ndim < dst_ndim:             # <<<<<<<<<<<<<<
  *         broadcast_leading(&src, src_ndim, dst_ndim)
  *     elif dst_ndim < src_ndim:
@@ -20656,7 +20656,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
   if (__pyx_t_2) {
 
     /* "View.MemoryView":1285
- * 
+ *
  *     if src_ndim < dst_ndim:
  *         broadcast_leading(&src, src_ndim, dst_ndim)             # <<<<<<<<<<<<<<
  *     elif dst_ndim < src_ndim:
@@ -20666,7 +20666,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
     /* "View.MemoryView":1284
  *     cdef __Pyx_memviewslice tmp
- * 
+ *
  *     if src_ndim < dst_ndim:             # <<<<<<<<<<<<<<
  *         broadcast_leading(&src, src_ndim, dst_ndim)
  *     elif dst_ndim < src_ndim:
@@ -20679,7 +20679,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *         broadcast_leading(&src, src_ndim, dst_ndim)
  *     elif dst_ndim < src_ndim:             # <<<<<<<<<<<<<<
  *         broadcast_leading(&dst, dst_ndim, src_ndim)
- * 
+ *
  */
   __pyx_t_2 = ((__pyx_v_dst_ndim < __pyx_v_src_ndim) != 0);
   if (__pyx_t_2) {
@@ -20688,7 +20688,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *         broadcast_leading(&src, src_ndim, dst_ndim)
  *     elif dst_ndim < src_ndim:
  *         broadcast_leading(&dst, dst_ndim, src_ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     cdef int ndim = max(src_ndim, dst_ndim)
  */
     __pyx_memoryview_broadcast_leading((&__pyx_v_dst), __pyx_v_dst_ndim, __pyx_v_src_ndim);
@@ -20698,16 +20698,16 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *         broadcast_leading(&src, src_ndim, dst_ndim)
  *     elif dst_ndim < src_ndim:             # <<<<<<<<<<<<<<
  *         broadcast_leading(&dst, dst_ndim, src_ndim)
- * 
+ *
  */
   }
   __pyx_L3:;
 
   /* "View.MemoryView":1289
  *         broadcast_leading(&dst, dst_ndim, src_ndim)
- * 
+ *
  *     cdef int ndim = max(src_ndim, dst_ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *     for i in range(ndim):
  */
   __pyx_t_3 = __pyx_v_dst_ndim;
@@ -20721,7 +20721,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
   /* "View.MemoryView":1291
  *     cdef int ndim = max(src_ndim, dst_ndim)
- * 
+ *
  *     for i in range(ndim):             # <<<<<<<<<<<<<<
  *         if src.shape[i] != dst.shape[i]:
  *             if src.shape[i] == 1:
@@ -20732,7 +20732,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
     __pyx_v_i = __pyx_t_4;
 
     /* "View.MemoryView":1292
- * 
+ *
  *     for i in range(ndim):
  *         if src.shape[i] != dst.shape[i]:             # <<<<<<<<<<<<<<
  *             if src.shape[i] == 1:
@@ -20783,7 +20783,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *                 src.strides[i] = 0
  *             else:
  *                 _err_extents(i, dst.shape[i], src.shape[i])             # <<<<<<<<<<<<<<
- * 
+ *
  *         if src.suboffsets[i] >= 0:
  */
       /*else*/ {
@@ -20792,7 +20792,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
       __pyx_L7:;
 
       /* "View.MemoryView":1292
- * 
+ *
  *     for i in range(ndim):
  *         if src.shape[i] != dst.shape[i]:             # <<<<<<<<<<<<<<
  *             if src.shape[i] == 1:
@@ -20802,38 +20802,38 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
     /* "View.MemoryView":1299
  *                 _err_extents(i, dst.shape[i], src.shape[i])
- * 
+ *
  *         if src.suboffsets[i] >= 0:             # <<<<<<<<<<<<<<
  *             _err_dim(ValueError, "Dimension %d is not direct", i)
- * 
+ *
  */
     __pyx_t_2 = (((__pyx_v_src.suboffsets[__pyx_v_i]) >= 0) != 0);
     if (__pyx_t_2) {
 
       /* "View.MemoryView":1300
- * 
+ *
  *         if src.suboffsets[i] >= 0:
  *             _err_dim(ValueError, "Dimension %d is not direct", i)             # <<<<<<<<<<<<<<
- * 
+ *
  *     if slices_overlap(&src, &dst, ndim, itemsize):
  */
       __pyx_t_6 = __pyx_memoryview_err_dim(__pyx_builtin_ValueError, ((char *)"Dimension %d is not direct"), __pyx_v_i); if (unlikely(__pyx_t_6 == ((int)-1))) __PYX_ERR(2, 1300, __pyx_L1_error)
 
       /* "View.MemoryView":1299
  *                 _err_extents(i, dst.shape[i], src.shape[i])
- * 
+ *
  *         if src.suboffsets[i] >= 0:             # <<<<<<<<<<<<<<
  *             _err_dim(ValueError, "Dimension %d is not direct", i)
- * 
+ *
  */
     }
   }
 
   /* "View.MemoryView":1302
  *             _err_dim(ValueError, "Dimension %d is not direct", i)
- * 
+ *
  *     if slices_overlap(&src, &dst, ndim, itemsize):             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not slice_is_contig(src, order, ndim):
  */
   __pyx_t_2 = (__pyx_slices_overlap((&__pyx_v_src), (&__pyx_v_dst), __pyx_v_ndim, __pyx_v_itemsize) != 0);
@@ -20841,73 +20841,73 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
     /* "View.MemoryView":1304
  *     if slices_overlap(&src, &dst, ndim, itemsize):
- * 
+ *
  *         if not slice_is_contig(src, order, ndim):             # <<<<<<<<<<<<<<
  *             order = get_best_order(&dst, ndim)
- * 
+ *
  */
     __pyx_t_2 = ((!(__pyx_memviewslice_is_contig(__pyx_v_src, __pyx_v_order, __pyx_v_ndim) != 0)) != 0);
     if (__pyx_t_2) {
 
       /* "View.MemoryView":1305
- * 
+ *
  *         if not slice_is_contig(src, order, ndim):
  *             order = get_best_order(&dst, ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *         tmpdata = copy_data_to_temp(&src, &tmp, order, ndim)
  */
       __pyx_v_order = __pyx_get_best_slice_order((&__pyx_v_dst), __pyx_v_ndim);
 
       /* "View.MemoryView":1304
  *     if slices_overlap(&src, &dst, ndim, itemsize):
- * 
+ *
  *         if not slice_is_contig(src, order, ndim):             # <<<<<<<<<<<<<<
  *             order = get_best_order(&dst, ndim)
- * 
+ *
  */
     }
 
     /* "View.MemoryView":1307
  *             order = get_best_order(&dst, ndim)
- * 
+ *
  *         tmpdata = copy_data_to_temp(&src, &tmp, order, ndim)             # <<<<<<<<<<<<<<
  *         src = tmp
- * 
+ *
  */
     __pyx_t_7 = __pyx_memoryview_copy_data_to_temp((&__pyx_v_src), (&__pyx_v_tmp), __pyx_v_order, __pyx_v_ndim); if (unlikely(__pyx_t_7 == ((void *)NULL))) __PYX_ERR(2, 1307, __pyx_L1_error)
     __pyx_v_tmpdata = __pyx_t_7;
 
     /* "View.MemoryView":1308
- * 
+ *
  *         tmpdata = copy_data_to_temp(&src, &tmp, order, ndim)
  *         src = tmp             # <<<<<<<<<<<<<<
- * 
+ *
  *     if not broadcasting:
  */
     __pyx_v_src = __pyx_v_tmp;
 
     /* "View.MemoryView":1302
  *             _err_dim(ValueError, "Dimension %d is not direct", i)
- * 
+ *
  *     if slices_overlap(&src, &dst, ndim, itemsize):             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not slice_is_contig(src, order, ndim):
  */
   }
 
   /* "View.MemoryView":1310
  *         src = tmp
- * 
+ *
  *     if not broadcasting:             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_2 = ((!(__pyx_v_broadcasting != 0)) != 0);
   if (__pyx_t_2) {
 
     /* "View.MemoryView":1313
- * 
- * 
+ *
+ *
  *         if slice_is_contig(src, 'C', ndim):             # <<<<<<<<<<<<<<
  *             direct_copy = slice_is_contig(dst, 'C', ndim)
  *         elif slice_is_contig(src, 'F', ndim):
@@ -20916,7 +20916,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
     if (__pyx_t_2) {
 
       /* "View.MemoryView":1314
- * 
+ *
  *         if slice_is_contig(src, 'C', ndim):
  *             direct_copy = slice_is_contig(dst, 'C', ndim)             # <<<<<<<<<<<<<<
  *         elif slice_is_contig(src, 'F', ndim):
@@ -20925,8 +20925,8 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
       __pyx_v_direct_copy = __pyx_memviewslice_is_contig(__pyx_v_dst, 'C', __pyx_v_ndim);
 
       /* "View.MemoryView":1313
- * 
- * 
+ *
+ *
  *         if slice_is_contig(src, 'C', ndim):             # <<<<<<<<<<<<<<
  *             direct_copy = slice_is_contig(dst, 'C', ndim)
  *         elif slice_is_contig(src, 'F', ndim):
@@ -20939,7 +20939,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *             direct_copy = slice_is_contig(dst, 'C', ndim)
  *         elif slice_is_contig(src, 'F', ndim):             # <<<<<<<<<<<<<<
  *             direct_copy = slice_is_contig(dst, 'F', ndim)
- * 
+ *
  */
     __pyx_t_2 = (__pyx_memviewslice_is_contig(__pyx_v_src, 'F', __pyx_v_ndim) != 0);
     if (__pyx_t_2) {
@@ -20948,7 +20948,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *             direct_copy = slice_is_contig(dst, 'C', ndim)
  *         elif slice_is_contig(src, 'F', ndim):
  *             direct_copy = slice_is_contig(dst, 'F', ndim)             # <<<<<<<<<<<<<<
- * 
+ *
  *         if direct_copy:
  */
       __pyx_v_direct_copy = __pyx_memviewslice_is_contig(__pyx_v_dst, 'F', __pyx_v_ndim);
@@ -20958,16 +20958,16 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *             direct_copy = slice_is_contig(dst, 'C', ndim)
  *         elif slice_is_contig(src, 'F', ndim):             # <<<<<<<<<<<<<<
  *             direct_copy = slice_is_contig(dst, 'F', ndim)
- * 
+ *
  */
     }
     __pyx_L12:;
 
     /* "View.MemoryView":1318
  *             direct_copy = slice_is_contig(dst, 'F', ndim)
- * 
+ *
  *         if direct_copy:             # <<<<<<<<<<<<<<
- * 
+ *
  *             refcount_copying(&dst, dtype_is_object, ndim, False)
  */
     __pyx_t_2 = (__pyx_v_direct_copy != 0);
@@ -20975,7 +20975,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
       /* "View.MemoryView":1320
  *         if direct_copy:
- * 
+ *
  *             refcount_copying(&dst, dtype_is_object, ndim, False)             # <<<<<<<<<<<<<<
  *             memcpy(dst.data, src.data, slice_get_size(&src, ndim))
  *             refcount_copying(&dst, dtype_is_object, ndim, True)
@@ -20983,7 +20983,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
       __pyx_memoryview_refcount_copying((&__pyx_v_dst), __pyx_v_dtype_is_object, __pyx_v_ndim, 0);
 
       /* "View.MemoryView":1321
- * 
+ *
  *             refcount_copying(&dst, dtype_is_object, ndim, False)
  *             memcpy(dst.data, src.data, slice_get_size(&src, ndim))             # <<<<<<<<<<<<<<
  *             refcount_copying(&dst, dtype_is_object, ndim, True)
@@ -21005,7 +21005,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *             refcount_copying(&dst, dtype_is_object, ndim, True)
  *             free(tmpdata)             # <<<<<<<<<<<<<<
  *             return 0
- * 
+ *
  */
       free(__pyx_v_tmpdata);
 
@@ -21013,7 +21013,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *             refcount_copying(&dst, dtype_is_object, ndim, True)
  *             free(tmpdata)
  *             return 0             # <<<<<<<<<<<<<<
- * 
+ *
  *     if order == 'F' == get_best_order(&dst, ndim):
  */
       __pyx_r = 0;
@@ -21021,28 +21021,28 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 
       /* "View.MemoryView":1318
  *             direct_copy = slice_is_contig(dst, 'F', ndim)
- * 
+ *
  *         if direct_copy:             # <<<<<<<<<<<<<<
- * 
+ *
  *             refcount_copying(&dst, dtype_is_object, ndim, False)
  */
     }
 
     /* "View.MemoryView":1310
  *         src = tmp
- * 
+ *
  *     if not broadcasting:             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   }
 
   /* "View.MemoryView":1326
  *             return 0
- * 
+ *
  *     if order == 'F' == get_best_order(&dst, ndim):             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_2 = (__pyx_v_order == 'F');
   if (__pyx_t_2) {
@@ -21052,35 +21052,35 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
   if (__pyx_t_8) {
 
     /* "View.MemoryView":1329
- * 
- * 
+ *
+ *
  *         transpose_memslice(&src)             # <<<<<<<<<<<<<<
  *         transpose_memslice(&dst)
- * 
+ *
  */
     __pyx_t_5 = __pyx_memslice_transpose((&__pyx_v_src)); if (unlikely(__pyx_t_5 == ((int)0))) __PYX_ERR(2, 1329, __pyx_L1_error)
 
     /* "View.MemoryView":1330
- * 
+ *
  *         transpose_memslice(&src)
  *         transpose_memslice(&dst)             # <<<<<<<<<<<<<<
- * 
+ *
  *     refcount_copying(&dst, dtype_is_object, ndim, False)
  */
     __pyx_t_5 = __pyx_memslice_transpose((&__pyx_v_dst)); if (unlikely(__pyx_t_5 == ((int)0))) __PYX_ERR(2, 1330, __pyx_L1_error)
 
     /* "View.MemoryView":1326
  *             return 0
- * 
+ *
  *     if order == 'F' == get_best_order(&dst, ndim):             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   }
 
   /* "View.MemoryView":1332
  *         transpose_memslice(&dst)
- * 
+ *
  *     refcount_copying(&dst, dtype_is_object, ndim, False)             # <<<<<<<<<<<<<<
  *     copy_strided_to_strided(&src, &dst, ndim, itemsize)
  *     refcount_copying(&dst, dtype_is_object, ndim, True)
@@ -21088,11 +21088,11 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
   __pyx_memoryview_refcount_copying((&__pyx_v_dst), __pyx_v_dtype_is_object, __pyx_v_ndim, 0);
 
   /* "View.MemoryView":1333
- * 
+ *
  *     refcount_copying(&dst, dtype_is_object, ndim, False)
  *     copy_strided_to_strided(&src, &dst, ndim, itemsize)             # <<<<<<<<<<<<<<
  *     refcount_copying(&dst, dtype_is_object, ndim, True)
- * 
+ *
  */
   copy_strided_to_strided((&__pyx_v_src), (&__pyx_v_dst), __pyx_v_ndim, __pyx_v_itemsize);
 
@@ -21100,32 +21100,32 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
  *     refcount_copying(&dst, dtype_is_object, ndim, False)
  *     copy_strided_to_strided(&src, &dst, ndim, itemsize)
  *     refcount_copying(&dst, dtype_is_object, ndim, True)             # <<<<<<<<<<<<<<
- * 
+ *
  *     free(tmpdata)
  */
   __pyx_memoryview_refcount_copying((&__pyx_v_dst), __pyx_v_dtype_is_object, __pyx_v_ndim, 1);
 
   /* "View.MemoryView":1336
  *     refcount_copying(&dst, dtype_is_object, ndim, True)
- * 
+ *
  *     free(tmpdata)             # <<<<<<<<<<<<<<
  *     return 0
- * 
+ *
  */
   free(__pyx_v_tmpdata);
 
   /* "View.MemoryView":1337
- * 
+ *
  *     free(tmpdata)
  *     return 0             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_broadcast_leading')
  */
   __pyx_r = 0;
   goto __pyx_L0;
 
   /* "View.MemoryView":1268
- * 
+ *
  * @cname('__pyx_memoryview_copy_contents')
  * cdef int memoryview_copy_contents(__Pyx_memviewslice src,             # <<<<<<<<<<<<<<
  *                                   __Pyx_memviewslice dst,
@@ -21149,7 +21149,7 @@ static int __pyx_memoryview_copy_contents(__Pyx_memviewslice __pyx_v_src, __Pyx_
 }
 
 /* "View.MemoryView":1340
- * 
+ *
  * @cname('__pyx_memoryview_broadcast_leading')
  * cdef void broadcast_leading(__Pyx_memviewslice *mslice,             # <<<<<<<<<<<<<<
  *                             int ndim,
@@ -21167,14 +21167,14 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
  *                             int ndim_other) nogil:
  *     cdef int i
  *     cdef int offset = ndim_other - ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):
  */
   __pyx_v_offset = (__pyx_v_ndim_other - __pyx_v_ndim);
 
   /* "View.MemoryView":1346
  *     cdef int offset = ndim_other - ndim
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):             # <<<<<<<<<<<<<<
  *         mslice.shape[i + offset] = mslice.shape[i]
  *         mslice.strides[i + offset] = mslice.strides[i]
@@ -21183,7 +21183,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
     __pyx_v_i = __pyx_t_1;
 
     /* "View.MemoryView":1347
- * 
+ *
  *     for i in range(ndim - 1, -1, -1):
  *         mslice.shape[i + offset] = mslice.shape[i]             # <<<<<<<<<<<<<<
  *         mslice.strides[i + offset] = mslice.strides[i]
@@ -21196,7 +21196,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
  *         mslice.shape[i + offset] = mslice.shape[i]
  *         mslice.strides[i + offset] = mslice.strides[i]             # <<<<<<<<<<<<<<
  *         mslice.suboffsets[i + offset] = mslice.suboffsets[i]
- * 
+ *
  */
     (__pyx_v_mslice->strides[(__pyx_v_i + __pyx_v_offset)]) = (__pyx_v_mslice->strides[__pyx_v_i]);
 
@@ -21204,7 +21204,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
  *         mslice.shape[i + offset] = mslice.shape[i]
  *         mslice.strides[i + offset] = mslice.strides[i]
  *         mslice.suboffsets[i + offset] = mslice.suboffsets[i]             # <<<<<<<<<<<<<<
- * 
+ *
  *     for i in range(offset):
  */
     (__pyx_v_mslice->suboffsets[(__pyx_v_i + __pyx_v_offset)]) = (__pyx_v_mslice->suboffsets[__pyx_v_i]);
@@ -21212,7 +21212,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
 
   /* "View.MemoryView":1351
  *         mslice.suboffsets[i + offset] = mslice.suboffsets[i]
- * 
+ *
  *     for i in range(offset):             # <<<<<<<<<<<<<<
  *         mslice.shape[i] = 1
  *         mslice.strides[i] = mslice.strides[0]
@@ -21223,7 +21223,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
     __pyx_v_i = __pyx_t_3;
 
     /* "View.MemoryView":1352
- * 
+ *
  *     for i in range(offset):
  *         mslice.shape[i] = 1             # <<<<<<<<<<<<<<
  *         mslice.strides[i] = mslice.strides[0]
@@ -21236,7 +21236,7 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
  *         mslice.shape[i] = 1
  *         mslice.strides[i] = mslice.strides[0]             # <<<<<<<<<<<<<<
  *         mslice.suboffsets[i] = -1
- * 
+ *
  */
     (__pyx_v_mslice->strides[__pyx_v_i]) = (__pyx_v_mslice->strides[0]);
 
@@ -21244,14 +21244,14 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
  *         mslice.shape[i] = 1
  *         mslice.strides[i] = mslice.strides[0]
  *         mslice.suboffsets[i] = -1             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     (__pyx_v_mslice->suboffsets[__pyx_v_i]) = -1L;
   }
 
   /* "View.MemoryView":1340
- * 
+ *
  * @cname('__pyx_memoryview_broadcast_leading')
  * cdef void broadcast_leading(__Pyx_memviewslice *mslice,             # <<<<<<<<<<<<<<
  *                             int ndim,
@@ -21262,19 +21262,19 @@ static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *__pyx_v_mslic
 }
 
 /* "View.MemoryView":1362
- * 
+ *
  * @cname('__pyx_memoryview_refcount_copying')
  * cdef void refcount_copying(__Pyx_memviewslice *dst, bint dtype_is_object,             # <<<<<<<<<<<<<<
  *                            int ndim, bint inc) nogil:
- * 
+ *
  */
 
 static void __pyx_memoryview_refcount_copying(__Pyx_memviewslice *__pyx_v_dst, int __pyx_v_dtype_is_object, int __pyx_v_ndim, int __pyx_v_inc) {
   int __pyx_t_1;
 
   /* "View.MemoryView":1366
- * 
- * 
+ *
+ *
  *     if dtype_is_object:             # <<<<<<<<<<<<<<
  *         refcount_objects_in_slice_with_gil(dst.data, dst.shape,
  *                                            dst.strides, ndim, inc)
@@ -21283,17 +21283,17 @@ static void __pyx_memoryview_refcount_copying(__Pyx_memviewslice *__pyx_v_dst, i
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1367
- * 
+ *
  *     if dtype_is_object:
  *         refcount_objects_in_slice_with_gil(dst.data, dst.shape,             # <<<<<<<<<<<<<<
  *                                            dst.strides, ndim, inc)
- * 
+ *
  */
     __pyx_memoryview_refcount_objects_in_slice_with_gil(__pyx_v_dst->data, __pyx_v_dst->shape, __pyx_v_dst->strides, __pyx_v_ndim, __pyx_v_inc);
 
     /* "View.MemoryView":1366
- * 
- * 
+ *
+ *
  *     if dtype_is_object:             # <<<<<<<<<<<<<<
  *         refcount_objects_in_slice_with_gil(dst.data, dst.shape,
  *                                            dst.strides, ndim, inc)
@@ -21301,18 +21301,18 @@ static void __pyx_memoryview_refcount_copying(__Pyx_memviewslice *__pyx_v_dst, i
   }
 
   /* "View.MemoryView":1362
- * 
+ *
  * @cname('__pyx_memoryview_refcount_copying')
  * cdef void refcount_copying(__Pyx_memviewslice *dst, bint dtype_is_object,             # <<<<<<<<<<<<<<
  *                            int ndim, bint inc) nogil:
- * 
+ *
  */
 
   /* function exit code */
 }
 
 /* "View.MemoryView":1371
- * 
+ *
  * @cname('__pyx_memoryview_refcount_objects_in_slice_with_gil')
  * cdef void refcount_objects_in_slice_with_gil(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                                              Py_ssize_t *strides, int ndim,
@@ -21330,13 +21330,13 @@ static void __pyx_memoryview_refcount_objects_in_slice_with_gil(char *__pyx_v_da
  *                                              Py_ssize_t *strides, int ndim,
  *                                              bint inc) with gil:
  *     refcount_objects_in_slice(data, shape, strides, ndim, inc)             # <<<<<<<<<<<<<<
- * 
+ *
  * @cname('__pyx_memoryview_refcount_objects_in_slice')
  */
   __pyx_memoryview_refcount_objects_in_slice(__pyx_v_data, __pyx_v_shape, __pyx_v_strides, __pyx_v_ndim, __pyx_v_inc);
 
   /* "View.MemoryView":1371
- * 
+ *
  * @cname('__pyx_memoryview_refcount_objects_in_slice_with_gil')
  * cdef void refcount_objects_in_slice_with_gil(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                                              Py_ssize_t *strides, int ndim,
@@ -21351,7 +21351,7 @@ static void __pyx_memoryview_refcount_objects_in_slice_with_gil(char *__pyx_v_da
 }
 
 /* "View.MemoryView":1377
- * 
+ *
  * @cname('__pyx_memoryview_refcount_objects_in_slice')
  * cdef void refcount_objects_in_slice(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                                     Py_ssize_t *strides, int ndim, bint inc):
@@ -21369,7 +21369,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
 
   /* "View.MemoryView":1381
  *     cdef Py_ssize_t i
- * 
+ *
  *     for i in range(shape[0]):             # <<<<<<<<<<<<<<
  *         if ndim == 1:
  *             if inc:
@@ -21380,7 +21380,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
     __pyx_v_i = __pyx_t_3;
 
     /* "View.MemoryView":1382
- * 
+ *
  *     for i in range(shape[0]):
  *         if ndim == 1:             # <<<<<<<<<<<<<<
  *             if inc:
@@ -21431,7 +21431,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
       __pyx_L6:;
 
       /* "View.MemoryView":1382
- * 
+ *
  *     for i in range(shape[0]):
  *         if ndim == 1:             # <<<<<<<<<<<<<<
  *             if inc:
@@ -21445,7 +21445,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
  *         else:
  *             refcount_objects_in_slice(data, shape + 1, strides + 1,             # <<<<<<<<<<<<<<
  *                                       ndim - 1, inc)
- * 
+ *
  */
     /*else*/ {
 
@@ -21453,7 +21453,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
  *         else:
  *             refcount_objects_in_slice(data, shape + 1, strides + 1,
  *                                       ndim - 1, inc)             # <<<<<<<<<<<<<<
- * 
+ *
  *         data += strides[0]
  */
       __pyx_memoryview_refcount_objects_in_slice(__pyx_v_data, (__pyx_v_shape + 1), (__pyx_v_strides + 1), (__pyx_v_ndim - 1), __pyx_v_inc);
@@ -21462,16 +21462,16 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
 
     /* "View.MemoryView":1391
  *                                       ndim - 1, inc)
- * 
+ *
  *         data += strides[0]             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
     __pyx_v_data = (__pyx_v_data + (__pyx_v_strides[0]));
   }
 
   /* "View.MemoryView":1377
- * 
+ *
  * @cname('__pyx_memoryview_refcount_objects_in_slice')
  * cdef void refcount_objects_in_slice(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                                     Py_ssize_t *strides, int ndim, bint inc):
@@ -21483,7 +21483,7 @@ static void __pyx_memoryview_refcount_objects_in_slice(char *__pyx_v_data, Py_ss
 }
 
 /* "View.MemoryView":1397
- * 
+ *
  * @cname('__pyx_memoryview_slice_assign_scalar')
  * cdef void slice_assign_scalar(__Pyx_memviewslice *dst, int ndim,             # <<<<<<<<<<<<<<
  *                               size_t itemsize, void *item,
@@ -21514,13 +21514,13 @@ static void __pyx_memoryview_slice_assign_scalar(__Pyx_memviewslice *__pyx_v_dst
  *     _slice_assign_scalar(dst.data, dst.shape, dst.strides, ndim,
  *                          itemsize, item)
  *     refcount_copying(dst, dtype_is_object, ndim, True)             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_memoryview_refcount_copying(__pyx_v_dst, __pyx_v_dtype_is_object, __pyx_v_ndim, 1);
 
   /* "View.MemoryView":1397
- * 
+ *
  * @cname('__pyx_memoryview_slice_assign_scalar')
  * cdef void slice_assign_scalar(__Pyx_memviewslice *dst, int ndim,             # <<<<<<<<<<<<<<
  *                               size_t itemsize, void *item,
@@ -21531,7 +21531,7 @@ static void __pyx_memoryview_slice_assign_scalar(__Pyx_memviewslice *__pyx_v_dst
 }
 
 /* "View.MemoryView":1407
- * 
+ *
  * @cname('__pyx_memoryview__slice_assign_scalar')
  * cdef void _slice_assign_scalar(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                               Py_ssize_t *strides, int ndim,
@@ -21552,7 +21552,7 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
  *     cdef Py_ssize_t i
  *     cdef Py_ssize_t stride = strides[0]             # <<<<<<<<<<<<<<
  *     cdef Py_ssize_t extent = shape[0]
- * 
+ *
  */
   __pyx_v_stride = (__pyx_v_strides[0]);
 
@@ -21560,14 +21560,14 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
  *     cdef Py_ssize_t i
  *     cdef Py_ssize_t stride = strides[0]
  *     cdef Py_ssize_t extent = shape[0]             # <<<<<<<<<<<<<<
- * 
+ *
  *     if ndim == 1:
  */
   __pyx_v_extent = (__pyx_v_shape[0]);
 
   /* "View.MemoryView":1414
  *     cdef Py_ssize_t extent = shape[0]
- * 
+ *
  *     if ndim == 1:             # <<<<<<<<<<<<<<
  *         for i in range(extent):
  *             memcpy(data, item, itemsize)
@@ -21576,7 +21576,7 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
   if (__pyx_t_1) {
 
     /* "View.MemoryView":1415
- * 
+ *
  *     if ndim == 1:
  *         for i in range(extent):             # <<<<<<<<<<<<<<
  *             memcpy(data, item, itemsize)
@@ -21608,7 +21608,7 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
 
     /* "View.MemoryView":1414
  *     cdef Py_ssize_t extent = shape[0]
- * 
+ *
  *     if ndim == 1:             # <<<<<<<<<<<<<<
  *         for i in range(extent):
  *             memcpy(data, item, itemsize)
@@ -21642,8 +21642,8 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
  *             _slice_assign_scalar(data, shape + 1, strides + 1,
  *                                 ndim - 1, itemsize, item)
  *             data += stride             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
       __pyx_v_data = (__pyx_v_data + __pyx_v_stride);
     }
@@ -21651,7 +21651,7 @@ static void __pyx_memoryview__slice_assign_scalar(char *__pyx_v_data, Py_ssize_t
   __pyx_L3:;
 
   /* "View.MemoryView":1407
- * 
+ *
  * @cname('__pyx_memoryview__slice_assign_scalar')
  * cdef void _slice_assign_scalar(char *data, Py_ssize_t *shape,             # <<<<<<<<<<<<<<
  *                               Py_ssize_t *strides, int ndim,
@@ -23068,7 +23068,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":45
  *     cdef int v, mat_size
- * 
+ *
  *     with open(path) as fh:             # <<<<<<<<<<<<<<
  *         headers = None
  *         while headers is None:
@@ -23079,9 +23079,9 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":83
  *     mat_size = max(headers) + 1
- * 
+ *
  *     nuc_ords = [ord(x) for x in ['A','T','C','G']]             # <<<<<<<<<<<<<<
- * 
+ *
  *     a = np.zeros((mat_size, mat_size), dtype=int)
  */
   __pyx_tuple__6 = PyTuple_Pack(4, __pyx_n_u_A, __pyx_n_u_T, __pyx_n_u_C, __pyx_n_u_G); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(0, 83, __pyx_L1_error)
@@ -23090,7 +23090,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":144
  *     # J array - best alignment so far ending with a gap in Ref (I) (deletion in ref, insertion in read)
- * 
+ *
  *     cdef int [:,:] mScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))             # <<<<<<<<<<<<<<
  *     cdef int [:,:] iScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
  *     cdef int [:,:] jScore = np.empty((max_i + 1, max_j + 1), dtype=np.dtype("i"))
@@ -23112,7 +23112,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline int import_umath() except -1:
  */
   __pyx_tuple__10 = PyTuple_Pack(1, __pyx_kp_u_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(1, 945, __pyx_L1_error)
@@ -23123,7 +23123,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef inline int import_ufunc() except -1:
  */
   __pyx_tuple__11 = PyTuple_Pack(1, __pyx_kp_u_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(1, 951, __pyx_L1_error)
@@ -23131,10 +23131,10 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__11);
 
   /* "View.MemoryView":133
- * 
+ *
  *         if not self.ndim:
  *             raise ValueError("Empty shape tuple for cython.array")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if itemsize <= 0:
  */
   __pyx_tuple__12 = PyTuple_Pack(1, __pyx_kp_s_Empty_shape_tuple_for_cython_arr); if (unlikely(!__pyx_tuple__12)) __PYX_ERR(2, 133, __pyx_L1_error)
@@ -23142,10 +23142,10 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__12);
 
   /* "View.MemoryView":136
- * 
+ *
  *         if itemsize <= 0:
  *             raise ValueError("itemsize <= 0 for cython.array")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if not isinstance(format, bytes):
  */
   __pyx_tuple__13 = PyTuple_Pack(1, __pyx_kp_s_itemsize_0_for_cython_array); if (unlikely(!__pyx_tuple__13)) __PYX_ERR(2, 136, __pyx_L1_error)
@@ -23153,11 +23153,11 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__13);
 
   /* "View.MemoryView":148
- * 
+ *
  *         if not self._shape:
  *             raise MemoryError("unable to allocate shape and strides.")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_tuple__14 = PyTuple_Pack(1, __pyx_kp_s_unable_to_allocate_shape_and_str); if (unlikely(!__pyx_tuple__14)) __PYX_ERR(2, 148, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__14);
@@ -23167,7 +23167,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *             self.data = <char *>malloc(self.len)
  *             if not self.data:
  *                 raise MemoryError("unable to allocate array data.")             # <<<<<<<<<<<<<<
- * 
+ *
  *             if self.dtype_is_object:
  */
   __pyx_tuple__15 = PyTuple_Pack(1, __pyx_kp_s_unable_to_allocate_array_data); if (unlikely(!__pyx_tuple__15)) __PYX_ERR(2, 176, __pyx_L1_error)
@@ -23208,7 +23208,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *     def __setitem__(memoryview self, object index, object value):
  *         if self.view.readonly:
  *             raise TypeError("Cannot assign to read-only memoryview")             # <<<<<<<<<<<<<<
- * 
+ *
  *         have_slices, index = _unellipsify(index, self.view.ndim)
  */
   __pyx_tuple__19 = PyTuple_Pack(1, __pyx_kp_s_Cannot_assign_to_read_only_memor); if (unlikely(!__pyx_tuple__19)) __PYX_ERR(2, 418, __pyx_L1_error)
@@ -23230,7 +23230,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *     def __getbuffer__(self, Py_buffer *info, int flags):
  *         if flags & PyBUF_WRITABLE and self.view.readonly:
  *             raise ValueError("Cannot create writable memory view from read-only memoryview")             # <<<<<<<<<<<<<<
- * 
+ *
  *         if flags & PyBUF_ND:
  */
   __pyx_tuple__21 = PyTuple_Pack(1, __pyx_kp_s_Cannot_create_writable_memory_vi); if (unlikely(!__pyx_tuple__21)) __PYX_ERR(2, 520, __pyx_L1_error)
@@ -23239,9 +23239,9 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "View.MemoryView":570
  *         if self.view.strides == NULL:
- * 
+ *
  *             raise ValueError("Buffer view does not expose strides")             # <<<<<<<<<<<<<<
- * 
+ *
  *         return tuple([stride for stride in self.view.strides[:self.view.ndim]])
  */
   __pyx_tuple__22 = PyTuple_Pack(1, __pyx_kp_s_Buffer_view_does_not_expose_stri); if (unlikely(!__pyx_tuple__22)) __PYX_ERR(2, 570, __pyx_L1_error)
@@ -23252,7 +23252,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *     def suboffsets(self):
  *         if self.view.suboffsets == NULL:
  *             return (-1,) * self.view.ndim             # <<<<<<<<<<<<<<
- * 
+ *
  *         return tuple([suboffset for suboffset in self.view.suboffsets[:self.view.ndim]])
  */
   __pyx_tuple__23 = PyTuple_New(1); if (unlikely(!__pyx_tuple__23)) __PYX_ERR(2, 577, __pyx_L1_error)
@@ -23296,8 +23296,8 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  *     for suboffset in suboffsets[:ndim]:
  *         if suboffset >= 0:
  *             raise ValueError("Indirect dimensions not supported")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_tuple__27 = PyTuple_Pack(1, __pyx_kp_s_Indirect_dimensions_not_supporte); if (unlikely(!__pyx_tuple__27)) __PYX_ERR(2, 703, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__27);
@@ -23323,8 +23323,8 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__29);
 
   /* "CRISPResso2/CRISPResso2Align.pyx":35
- * 
- * 
+ *
+ *
  * def read_matrix(path):             # <<<<<<<<<<<<<<
  *     """
  *     Read a matrix in the NCBI format
@@ -23336,7 +23336,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":65
  *     return a
- * 
+ *
  * def make_matrix(match_score=5, mismatch_score=-4, n_mismatch_score=-2, n_match_score=-1):             # <<<<<<<<<<<<<<
  *     """
  *     Create a score matrix for matches/mismatches.
@@ -23360,7 +23360,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
 
   /* "View.MemoryView":286
  *         return self.name
- * 
+ *
  * cdef generic = Enum("<strided and direct or indirect>")             # <<<<<<<<<<<<<<
  * cdef strided = Enum("<strided and direct>") # default
  * cdef indirect = Enum("<strided and indirect>")
@@ -23370,11 +23370,11 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__36);
 
   /* "View.MemoryView":287
- * 
+ *
  * cdef generic = Enum("<strided and direct or indirect>")
  * cdef strided = Enum("<strided and direct>") # default             # <<<<<<<<<<<<<<
  * cdef indirect = Enum("<strided and indirect>")
- * 
+ *
  */
   __pyx_tuple__37 = PyTuple_Pack(1, __pyx_kp_s_strided_and_direct); if (unlikely(!__pyx_tuple__37)) __PYX_ERR(2, 287, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__37);
@@ -23384,30 +23384,30 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
  * cdef generic = Enum("<strided and direct or indirect>")
  * cdef strided = Enum("<strided and direct>") # default
  * cdef indirect = Enum("<strided and indirect>")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_tuple__38 = PyTuple_Pack(1, __pyx_kp_s_strided_and_indirect); if (unlikely(!__pyx_tuple__38)) __PYX_ERR(2, 288, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__38);
   __Pyx_GIVEREF(__pyx_tuple__38);
 
   /* "View.MemoryView":291
- * 
- * 
+ *
+ *
  * cdef contiguous = Enum("<contiguous and direct>")             # <<<<<<<<<<<<<<
  * cdef indirect_contiguous = Enum("<contiguous and indirect>")
- * 
+ *
  */
   __pyx_tuple__39 = PyTuple_Pack(1, __pyx_kp_s_contiguous_and_direct); if (unlikely(!__pyx_tuple__39)) __PYX_ERR(2, 291, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__39);
   __Pyx_GIVEREF(__pyx_tuple__39);
 
   /* "View.MemoryView":292
- * 
+ *
  * cdef contiguous = Enum("<contiguous and direct>")
  * cdef indirect_contiguous = Enum("<contiguous and indirect>")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_tuple__40 = PyTuple_Pack(1, __pyx_kp_s_contiguous_and_indirect); if (unlikely(!__pyx_tuple__40)) __PYX_ERR(2, 292, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__40);
@@ -23557,7 +23557,7 @@ static int __Pyx_modinit_type_import_code(void) {
   /*--- Type import code ---*/
   __pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(3, 9, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_ptype_7cpython_4type_type = __Pyx_ImportType(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", 
+  __pyx_ptype_7cpython_4type_type = __Pyx_ImportType(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type",
   #if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000
   sizeof(PyTypeObject),
   #else
@@ -23825,11 +23825,11 @@ if (!__Pyx_RefNanny) {
   #endif
 
   /* "CRISPResso2/CRISPResso2Align.pyx":5
- * 
+ *
  * from cython.view cimport array as cvarray
  * import numpy as np             # <<<<<<<<<<<<<<
  * cimport numpy as np
- * 
+ *
  */
   __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23837,11 +23837,11 @@ if (!__Pyx_RefNanny) {
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":11
- * 
+ *
  * cimport cython
  * import sys             # <<<<<<<<<<<<<<
  * import os.path
- * 
+ *
  */
   __pyx_t_1 = __Pyx_Import(__pyx_n_s_sys, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 11, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23852,7 +23852,7 @@ if (!__Pyx_RefNanny) {
  * cimport cython
  * import sys
  * import os.path             # <<<<<<<<<<<<<<
- * 
+ *
  * cdef extern from "stdlib.h":
  */
   __pyx_t_1 = __Pyx_Import(__pyx_n_s_os_path, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 12, __pyx_L1_error)
@@ -23862,10 +23862,10 @@ if (!__Pyx_RefNanny) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":24
  * ctypedef np.int8_t DTYPE_BOOL
- * 
+ *
  * cdef size_t UP = 1, LEFT = 2, DIAG = 3, NONE = 4             # <<<<<<<<<<<<<<
  * cdef size_t MARRAY = 1, IARRAY = 2, JARRAY = 3
- * 
+ *
  */
   __pyx_v_11CRISPResso2_16CRISPResso2Align_UP = 1;
   __pyx_v_11CRISPResso2_16CRISPResso2Align_LEFT = 2;
@@ -23873,19 +23873,19 @@ if (!__Pyx_RefNanny) {
   __pyx_v_11CRISPResso2_16CRISPResso2Align_NONE = 4;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":25
- * 
+ *
  * cdef size_t UP = 1, LEFT = 2, DIAG = 3, NONE = 4
  * cdef size_t MARRAY = 1, IARRAY = 2, JARRAY = 3             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_v_11CRISPResso2_16CRISPResso2Align_MARRAY = 1;
   __pyx_v_11CRISPResso2_16CRISPResso2Align_IARRAY = 2;
   __pyx_v_11CRISPResso2_16CRISPResso2Align_JARRAY = 3;
 
   /* "CRISPResso2/CRISPResso2Align.pyx":35
- * 
- * 
+ *
+ *
  * def read_matrix(path):             # <<<<<<<<<<<<<<
  *     """
  *     Read a matrix in the NCBI format
@@ -23897,7 +23897,7 @@ if (!__Pyx_RefNanny) {
 
   /* "CRISPResso2/CRISPResso2Align.pyx":65
  *     return a
- * 
+ *
  * def make_matrix(match_score=5, mismatch_score=-4, n_mismatch_score=-2, n_match_score=-1):             # <<<<<<<<<<<<<<
  *     """
  *     Create a score matrix for matches/mismatches.
@@ -23922,7 +23922,7 @@ if (!__Pyx_RefNanny) {
   /* "CRISPResso2/CRISPResso2Align.pyx":1
  * #with help from friends at https://github.com/brentp/align for cython implementation (no thanks for bug-ridden algorithm)             # <<<<<<<<<<<<<<
  * # https://github.com/dnase/affine-gap-sequence-alignment/blob/master/alignment.py for affine gap algorithm
- * 
+ *
  */
   __pyx_t_1 = __Pyx_PyDict_NewPresized(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23931,9 +23931,9 @@ if (!__Pyx_RefNanny) {
 
   /* "View.MemoryView":209
  *         info.obj = self
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_array_getbuffer, "getbuffer(obj, view, flags)")             # <<<<<<<<<<<<<<
- * 
+ *
  *     def __dealloc__(array self):
  */
   __pyx_t_1 = __pyx_capsule_create(((void *)(&__pyx_array_getbuffer)), ((char *)"getbuffer(obj, view, flags)")); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 209, __pyx_L1_error)
@@ -23944,7 +23944,7 @@ if (!__Pyx_RefNanny) {
 
   /* "View.MemoryView":286
  *         return self.name
- * 
+ *
  * cdef generic = Enum("<strided and direct or indirect>")             # <<<<<<<<<<<<<<
  * cdef strided = Enum("<strided and direct>") # default
  * cdef indirect = Enum("<strided and indirect>")
@@ -23957,11 +23957,11 @@ if (!__Pyx_RefNanny) {
   __pyx_t_1 = 0;
 
   /* "View.MemoryView":287
- * 
+ *
  * cdef generic = Enum("<strided and direct or indirect>")
  * cdef strided = Enum("<strided and direct>") # default             # <<<<<<<<<<<<<<
  * cdef indirect = Enum("<strided and indirect>")
- * 
+ *
  */
   __pyx_t_1 = __Pyx_PyObject_Call(((PyObject *)__pyx_MemviewEnum_type), __pyx_tuple__37, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 287, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23974,8 +23974,8 @@ if (!__Pyx_RefNanny) {
  * cdef generic = Enum("<strided and direct or indirect>")
  * cdef strided = Enum("<strided and direct>") # default
  * cdef indirect = Enum("<strided and indirect>")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_1 = __Pyx_PyObject_Call(((PyObject *)__pyx_MemviewEnum_type), __pyx_tuple__38, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 288, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23985,11 +23985,11 @@ if (!__Pyx_RefNanny) {
   __pyx_t_1 = 0;
 
   /* "View.MemoryView":291
- * 
- * 
+ *
+ *
  * cdef contiguous = Enum("<contiguous and direct>")             # <<<<<<<<<<<<<<
  * cdef indirect_contiguous = Enum("<contiguous and indirect>")
- * 
+ *
  */
   __pyx_t_1 = __Pyx_PyObject_Call(((PyObject *)__pyx_MemviewEnum_type), __pyx_tuple__39, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 291, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -23999,11 +23999,11 @@ if (!__Pyx_RefNanny) {
   __pyx_t_1 = 0;
 
   /* "View.MemoryView":292
- * 
+ *
  * cdef contiguous = Enum("<contiguous and direct>")
  * cdef indirect_contiguous = Enum("<contiguous and indirect>")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_1 = __Pyx_PyObject_Call(((PyObject *)__pyx_MemviewEnum_type), __pyx_tuple__40, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 292, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -24013,7 +24013,7 @@ if (!__Pyx_RefNanny) {
   __pyx_t_1 = 0;
 
   /* "View.MemoryView":316
- * 
+ *
  * DEF THREAD_LOCKS_PREALLOCATED = 8
  * cdef int __pyx_memoryview_thread_locks_used = 0             # <<<<<<<<<<<<<<
  * cdef PyThread_type_lock[THREAD_LOCKS_PREALLOCATED] __pyx_memoryview_thread_locks = [
@@ -24040,10 +24040,10 @@ if (!__Pyx_RefNanny) {
 
   /* "View.MemoryView":549
  *         info.obj = self
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_1 = __pyx_capsule_create(((void *)(&__pyx_memoryview_getbuffer)), ((char *)"getbuffer(obj, view, flags)")); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 549, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -24053,10 +24053,10 @@ if (!__Pyx_RefNanny) {
 
   /* "View.MemoryView":995
  *         return self.from_object
- * 
+ *
  *     __pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)")             # <<<<<<<<<<<<<<
- * 
- * 
+ *
+ *
  */
   __pyx_t_1 = __pyx_capsule_create(((void *)(&__pyx_memoryview_getbuffer)), ((char *)"getbuffer(obj, view, flags)")); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 995, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
@@ -24894,8 +24894,8 @@ static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED
                 llx = lla + llb;
             return PyLong_FromLongLong(llx);
 #endif
-        
-        
+
+
     }
     #endif
     if (PyFloat_CheckExact(op1)) {
diff --git a/CRISPResso2/CRISPRessoAggregateCORE.py b/CRISPResso2/CRISPRessoAggregateCORE.py
index 748a0794..2cd6e890 100644
--- a/CRISPResso2/CRISPRessoAggregateCORE.py
+++ b/CRISPResso2/CRISPRessoAggregateCORE.py
@@ -17,10 +17,14 @@
 import traceback
 from datetime import datetime
 from CRISPResso2 import CRISPRessoShared
-from CRISPResso2 import CRISPRessoPlot
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 from CRISPResso2.CRISPRessoMultiProcessing import get_max_processes, run_plot
 
+if CRISPRessoShared.is_C2Pro_installed():
+    from CRISPRessoPro import __version__ as CRISPRessoProVersion
+    C2PRO_INSTALLED = True
+else:
+    C2PRO_INSTALLED = False
 
 import logging
 
@@ -67,9 +71,19 @@ def main():
 
         parser.add_argument('--debug', help='Show debug messages', action='store_true')
         parser.add_argument('-v', '--verbosity', type=int, help='Verbosity level of output to the console (1-4), 4 is the most verbose', default=3)
+        parser.add_argument('--halt_on_plot_fail', action="store_true", help="Halt execution if a plot fails to generate")
+
+        # CRISPRessoPro params
+        parser.add_argument('--use_matplotlib', action='store_true',
+                        help='Use matplotlib for plotting instead of plotly/d3 when CRISPRessoPro is installed')
 
         args = parser.parse_args()
 
+        if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
 
         output_folder_name='CRISPRessoAggregate_on_%s' % args.name
@@ -85,7 +99,7 @@ def main():
 
         log_filename=_jp('CRISPRessoAggregate_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoAggregate_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoAggregate_status.json')))
 
         with open(log_filename, 'w+') as outfile:
               outfile.write('[Command used]:\n%s\n\n[Execution log]:\n' % ' '.join(sys.argv))
@@ -96,6 +110,7 @@ def main():
         crispresso2_info = {'running_info': {}, 'results': {'alignment_stats': {}, 'general_plots': {}}} #keep track of all information for this run to be pickled and saved at the end of the run
         crispresso2_info['running_info']['version'] = CRISPRessoShared.__version__
         crispresso2_info['running_info']['args'] = deepcopy(args)
+        crispresso2_info['running_info']['command_used'] = ' '.join(sys.argv)
 
         crispresso2_info['running_info']['log_filename'] = os.path.basename(log_filename)
 
@@ -117,6 +132,7 @@ def main():
             num_processes=n_processes,
             process_pool=process_pool,
             process_futures=process_futures,
+            halt_on_plot_fail=args.halt_on_plot_fail,
         )
 
         #glob returns paths including the original prefix
@@ -214,7 +230,7 @@ def main():
 
         if successfully_imported_count > 0:
 
-            crispresso2_folders = crispresso2_folder_infos.keys()
+            crispresso2_folders = list(sorted(crispresso2_folder_infos.keys()))
             crispresso2_folder_names = {}
             crispresso2_folder_htmls = {}#file_loc->html folder loc
             quilt_plots_to_show = {}  # name->{'href':path to report, 'img': png}
@@ -502,8 +518,10 @@ def main():
                                     'fig_filename_root': this_window_nuc_pct_quilt_plot_name,
                                     'save_also_png': save_png,
                                     'sgRNA_intervals': sub_sgRNA_intervals,
+                                    'sgRNA_sequences': consensus_guides,
                                     'quantification_window_idxs': include_idxs,
                                     'group_column': 'Folder',
+                                    'custom_colors': None,
                                 }
                                 plot(
                                     CRISPRessoPlot.plot_nucleotide_quilt,
@@ -537,8 +555,10 @@ def main():
                                     'fig_filename_root': this_nuc_pct_quilt_plot_name,
                                     'save_also_png': save_png,
                                     'sgRNA_intervals': consensus_sgRNA_intervals,
+                                    'sgRNA_sequences': consensus_guides,
                                     'quantification_window_idxs': include_idxs,
                                     'group_column': 'Folder',
+                                    'custom_colors': None,
                                 }
                                 plot(
                                     CRISPRessoPlot.plot_nucleotide_quilt,
@@ -576,8 +596,10 @@ def main():
                                     'fig_filename_root': this_nuc_pct_quilt_plot_name,
                                     'save_also_png': save_png,
                                     'sgRNA_intervals': consensus_sgRNA_intervals,
+                                    'sgRNA_sequences': consensus_guides,
                                     'quantification_window_idxs': consensus_include_idxs,
                                     'group_column': 'Folder',
+                                    'custom_colors': None,
                                 }
                                 plot(
                                     CRISPRessoPlot.plot_nucleotide_quilt,
@@ -595,18 +617,20 @@ def main():
                                 this_plot_suffix_int += 1
                                 this_plot_suffix = "_" + str(this_plot_suffix_int)
 
-                    if not args.suppress_plots:
+                    if C2PRO_INSTALLED and not args.use_matplotlib and not args.suppress_plots:
                         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_names'] = []
                         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_paths'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_titles'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_labels'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_datas'] = {}
+                        crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_divs'] = {}
 
                         crispresso2_info['results']['general_plots']['allele_modification_line_plot_names'] = []
                         crispresso2_info['results']['general_plots']['allele_modification_line_plot_paths'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_line_plot_titles'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_line_plot_labels'] = {}
                         crispresso2_info['results']['general_plots']['allele_modification_line_plot_datas'] = {}
+                        crispresso2_info['results']['general_plots']['allele_modification_line_plot_divs'] = {}
                         if guides_all_same:
                             sgRNA_intervals = [consensus_sgRNA_intervals] * modification_frequency_summary_df.shape[0]
                         else:
@@ -632,11 +656,14 @@ def main():
                             plot_name = 'CRISPRessoAggregate_percentage_of_{0}_across_alleles_{1}_heatmap'.format(modification_type.lower(), amplicon_name)
                             plot_path = '{0}.html'.format(_jp(plot_name))
 
+                            heatmap_div_id = '{0}-allele-modification-heatmap-{1}'.format(amplicon_name.lower(), modification_type.lower())
                             allele_modification_heatmap_input = {
                                 'sample_values': modification_df,
                                 'sample_sgRNA_intervals': sgRNA_intervals,
                                 'plot_path': plot_path,
                                 'title': modification_type,
+                                'div_id': heatmap_div_id,
+                                'amplicon_name': amplicon_name,
                             }
                             plot(
                                 CRISPRessoPlot.plot_allele_modification_heatmap,
@@ -658,15 +685,19 @@ def main():
                                     ),
                                 ),
                             ]
+                            crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_divs'][plot_name] = heatmap_div_id
 
                             plot_name = 'CRISPRessoAggregate_percentage_of_{0}_across_alleles_{1}_line'.format(modification_type.lower(), amplicon_name)
                             plot_path = '{0}.html'.format(_jp(plot_name))
 
+                            line_div_id = '{0}-allele-modification-line-{1}'.format(amplicon_name.lower(), modification_type.lower())
                             allele_modification_line_input = {
                                 'sample_values': modification_df,
                                 'sample_sgRNA_intervals': sgRNA_intervals,
                                 'plot_path': plot_path,
                                 'title': modification_type,
+                                'div_id': line_div_id,
+                                'amplicon_name': amplicon_name,
                             }
                             plot(
                                 CRISPRessoPlot.plot_allele_modification_line,
@@ -687,6 +718,7 @@ def main():
                                     ),
                                 ),
                             ]
+                            crispresso2_info['results']['general_plots']['allele_modification_line_plot_divs'][plot_name] = line_div_id
 
             crispresso2_info['results']['general_plots']['window_nuc_pct_quilt_plot_names'] = window_nuc_pct_quilt_plot_names
             crispresso2_info['results']['general_plots']['nuc_pct_quilt_plot_names'] = nuc_pct_quilt_plot_names
@@ -758,7 +790,7 @@ def main():
 
             header = 'Name\tUnmodified%\tModified%\tReads_total\tReads_aligned\tUnmodified\tModified\tDiscarded\tInsertions\tDeletions\tSubstitutions\tOnly Insertions\tOnly Deletions\tOnly Substitutions\tInsertions and Deletions\tInsertions and Substitutions\tDeletions and Substitutions\tInsertions Deletions and Substitutions'
             header_els = header.split("\t")
-            df_summary_quantification=pd.DataFrame(quantification_summary, columns=header_els)
+            df_summary_quantification=pd.DataFrame(quantification_summary, columns=header_els).sort_values(by=['Name'])
             samples_quantification_summary_filename = _jp('CRISPRessoAggregate_quantification_of_editing_frequency.txt') #this file has one line for each run (sum of all amplicons)
             df_summary_quantification.fillna('NA').to_csv(samples_quantification_summary_filename, sep='\t', index=None)
             crispresso2_info['results']['alignment_stats']['samples_quantification_summary_filename'] = os.path.basename(samples_quantification_summary_filename)
@@ -820,11 +852,17 @@ def main():
                 report_filename = OUTPUT_DIRECTORY+'.html'
                 if (args.place_report_in_output_folder):
                     report_filename = _jp("CRISPResso2Aggregate_report.html")
-                CRISPRessoReport.make_aggregate_report(crispresso2_info, args.name,
-                                                       report_filename, OUTPUT_DIRECTORY,
-                                                       _ROOT, crispresso2_folders,
-                                                       crispresso2_folder_htmls,
-                                                       quilt_plots_to_show)
+                CRISPRessoReport.make_aggregate_report(
+                    crispresso2_info,
+                    args.name,
+                    report_filename,
+                    OUTPUT_DIRECTORY,
+                    _ROOT,
+                    crispresso2_folders,
+                    crispresso2_folder_htmls,
+                    logger,
+                    compact_plots_to_show=quilt_plots_to_show,
+                )
                 crispresso2_info['running_info']['report_location'] = report_filename
                 crispresso2_info['running_info']['report_filename'] = os.path.basename(report_filename)
         else: #no files successfully imported
@@ -856,7 +894,12 @@ def main():
                 debug('Plot pool results:')
                 for future in process_futures:
                     debug('future: ' + str(future))
-            future_results = [f.result() for f in process_futures] #required to raise exceptions thrown from within future
+            for future in process_futures:
+                try:
+                    future.result()
+                except Exception as e:
+                    logger.warning('Error in plot pool: %s' % e)
+                    logger.debug(traceback.format_exc())
             process_pool.shutdown()
 
         info('Analysis Complete!', {'percent_complete': 100})
diff --git a/CRISPResso2/CRISPRessoBatchCORE.py b/CRISPResso2/CRISPRessoBatchCORE.py
index c265a9d1..32fe005f 100644
--- a/CRISPResso2/CRISPRessoBatchCORE.py
+++ b/CRISPResso2/CRISPRessoBatchCORE.py
@@ -14,9 +14,14 @@
 import traceback
 from datetime import datetime
 from CRISPResso2 import CRISPRessoShared
-from CRISPResso2 import CRISPRessoPlot
 from CRISPResso2 import CRISPRessoMultiProcessing
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
+
+if CRISPRessoShared.is_C2Pro_installed():
+    import CRISPRessoPro
+    C2PRO_INSTALLED = True
+else:
+    C2PRO_INSTALLED = False
 
 import logging
 
@@ -44,6 +49,33 @@ def check_library(library_name):
 np = check_library('numpy')
 
 
+def should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff=300):
+    """Determine if large plots should be plotted.
+
+    Parameters
+    ----------
+    num_rows : int
+        Number of rows in the dataframe.
+    c2pro_installed : bool
+        Whether CRISPRessoPro is installed.
+    use_matplotlib : bool
+        Whether to use matplotlib when CRISPRessoPro is installed, i.e. value
+        of `--use_matplotlib`.
+    large_plot_cutoff : int, optional
+        Number of samples at which to not plot large plots with matplotlib.
+        Note that each sample has 6 rows in the datafame. Defaults to 300.
+
+    Returns
+    -------
+    bool
+        Whether to plot large plots.
+    """
+    return (
+        (not use_matplotlib and c2pro_installed)
+        or (num_rows / 6) < large_plot_cutoff
+    )
+
+
 def main():
     try:
         start_time =  datetime.now()
@@ -70,15 +102,7 @@ def main():
             ))
             sys.exit()
 
-        parser = CRISPRessoShared.getCRISPRessoArgParser(parser_title = 'CRISPRessoBatch Parameters')
-
-        #batch specific params
-        parser.add_argument('-bs', '--batch_settings', type=str, help='Settings file for batch. Must be tab-separated text file. The header row contains CRISPResso parameters (e.g., fastq_r1, fastq_r2, amplicon_seq, and other optional parameters). Each following row sets parameters for an additional batch.', required=True)
-        parser.add_argument('--skip_failed',  help='Continue with batch analysis even if one sample fails', action='store_true')
-        parser.add_argument('--min_reads_for_inclusion',  help='Minimum number of reads for a batch to be included in the batch summary', type=int, default=0)
-        parser.add_argument('-bo', '--batch_output_folder',  help='Directory where batch analysis output will be stored')
-        parser.add_argument('--suppress_batch_summary_plots',  help='Suppress batch summary plots - e.g. if many samples are run at once, the summary plots of all sub-runs may be too large. This parameter suppresses the production of these plots.', action='store_true')
-        parser.add_argument('--crispresso_command', help='CRISPResso command to call', default='CRISPResso')
+        parser = CRISPRessoShared.getCRISPRessoArgParser("Batch", parser_title = 'CRISPRessoBatch Parameters')
 
         args = parser.parse_args()
 
@@ -86,11 +110,12 @@ def main():
 
         debug_flag = args.debug
 
-        crispresso_options = CRISPRessoShared.get_crispresso_options()
+        crispresso_options = CRISPRessoShared.get_core_crispresso_options()
         options_to_ignore = {'name', 'output_folder', 'zip_output'}
         crispresso_options_for_batch = list(crispresso_options-options_to_ignore)
 
         CRISPRessoShared.check_file(args.batch_settings)
+        custom_config = CRISPRessoShared.check_custom_config(args)
 
         if args.zip_output and not args.place_report_in_output_folder:
             warn('Invalid arguement combination: If zip_output is True then place_report_in_output_folder must also be True. Setting place_report_in_output_folder to True.')
@@ -115,6 +140,12 @@ def main():
 
         _jp = lambda filename: os.path.join(OUTPUT_DIRECTORY, filename) #handy function to put a file in the output directory
 
+        if args.use_matplotlib or not C2PRO_INSTALLED:
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+        CRISPRessoPlot.setMatplotlibDefaults()
+
         try:
             info('Creating Folder %s' % OUTPUT_DIRECTORY, {'percent_complete': 0})
             os.makedirs(OUTPUT_DIRECTORY)
@@ -123,7 +154,7 @@ def main():
 
         log_filename = _jp('CRISPRessoBatch_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        status_handler = CRISPRessoShared.StatusHandler(_jp('CRISPRessoBatch_status.txt'))
+        status_handler = CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoBatch_status.json'))
         logger.addHandler(status_handler)
 
         with open(log_filename, 'w+') as outfile:
@@ -142,8 +173,6 @@ def main():
         else:
             n_processes_for_batch = int(args.n_processes)
 
-        # this value will be propagated to sub-commands, so set it as 1 here
-        args.n_processes = 1
 
         crispresso_cmd_to_write = ' '.join(sys.argv)
         if args.write_cleaned_report:
@@ -162,6 +191,13 @@ def main():
 #        batch_params=pd.read_csv(args.batch_settings,sep=None,engine='python',error_bad_lines=False)
         batch_params.columns = batch_params.columns.str.strip(' -\xd0')
 
+        # if there are more processes than batches, use more processes on each sub-crispresso run
+        # if there are more batches than processes, just use 1 process on each sub-crispresso run
+        args.n_processes = 1
+        num_batches = batch_params.shape[0]
+        if int(n_processes_for_batch/num_batches) > 1:
+            args.n_processes = int(n_processes_for_batch/num_batches)
+
         int_columns = ['default_min_aln_score', 'min_average_read_quality', 'min_single_bp_quality',
                        'min_bp_quality_or_N',
                        'quantification_window_size', 'quantification_window_center', 'exclude_bp_from_left',
@@ -169,11 +205,11 @@ def main():
                        'plot_window_size', 'max_rows_alleles_around_cut_to_plot']
         for int_col in int_columns:
             if int_col in batch_params.columns:
-                batch_params[int_col].fillna(getattr(args, int_col), inplace=True)
+                batch_params.fillna(value={int_col: getattr(args, int_col)}, inplace=True)
                 batch_params[int_col] = batch_params[int_col].astype(int)
 
         # rename column "a" to "amplicon_seq", etc
-        batch_params.rename(index=str, columns=CRISPRessoShared.get_crispresso_options_lookup(), inplace=True)
+        batch_params.rename(index=str, columns=CRISPRessoShared.get_crispresso_options_lookup("Core"), inplace=True)
         batch_count = batch_params.shape[0]
         batch_params.index = range(batch_count)
 
@@ -187,7 +223,7 @@ def main():
                 batch_params[arg] = getattr(args, arg)
             else:
                 if (getattr(args, arg) is not None):
-                    batch_params[arg].fillna(value=getattr(args, arg), inplace=True)
+                    batch_params.fillna(value={arg: getattr(args, arg)}, inplace=True)
 
         # assert that all names are unique
         # and clean names
@@ -262,7 +298,7 @@ def main():
                     discard_guide_positions_overhanging_amplicon_edge = False
                     if 'discard_guide_positions_overhanging_amplicon_edge' in row:
                         discard_guide_positions_overhanging_amplicon_edge = row.discard_guide_positions_overhanging_amplicon_edge
-                    (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_include_idxs,
+                    (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_sgRNA_include_idxs, this_include_idxs,
                         this_exclude_idxs) = CRISPRessoShared.get_amplicon_info_for_guides(curr_amplicon_seq, guides, guide_mismatches, guide_names, guide_qw_centers,
                         guide_qw_sizes, curr_amplicon_quant_window_coordinates, row.exclude_bp_from_left, row.exclude_bp_from_right, row.plot_window_size, guide_plot_cut_points, discard_guide_positions_overhanging_amplicon_edge)
                     for guide_seq in this_sgRNA_sequences:
@@ -292,7 +328,6 @@ def main():
 
         crispresso2_info['results']['batch_names_arr'] = batch_names_arr
         crispresso2_info['results']['batch_input_names'] = batch_input_names
-
         CRISPRessoMultiProcessing.run_crispresso_cmds(crispresso_cmds, n_processes_for_batch, 'batch', args.skip_failed, start_end_percent=[10, 90])
 
         run_datas = [] # crispresso2 info from each row
@@ -301,12 +336,16 @@ def main():
         amplicon_names = {}
         amplicon_counts = {}
         completed_batch_arr = []
+        failed_batch_arr = []
+        failed_batch_arr_desc = []
         for idx, row in batch_params.iterrows():
             batch_name = CRISPRessoShared.slugify(row["name"])
             folder_name = os.path.join(OUTPUT_DIRECTORY, 'CRISPResso_on_%s' % batch_name)
-            run_data_file = os.path.join(folder_name, 'CRISPResso2_info.json')
-            if not os.path.isfile(run_data_file):
-                info("Skipping folder '%s'. Cannot find run data at '%s'."%(folder_name, run_data_file))
+            # check if run failed
+            failed_run_bool, failed_status_string = CRISPRessoShared.check_if_failed_run(folder_name, info)
+            if failed_run_bool:
+                failed_batch_arr.append(batch_name)
+                failed_batch_arr_desc.append(failed_status_string)
                 run_datas.append(None)
                 continue
 
@@ -326,6 +365,8 @@ def main():
 
             completed_batch_arr.append(batch_name)
 
+        crispresso2_info['results']['failed_batch_arr'] = failed_batch_arr
+        crispresso2_info['results']['failed_batch_arr_desc'] = failed_batch_arr_desc
         crispresso2_info['results']['completed_batch_arr'] = completed_batch_arr
 
         # make sure amplicon names aren't super long
@@ -359,6 +400,7 @@ def main():
             num_processes=n_processes_for_batch,
             process_futures=process_futures,
             process_pool=process_pool,
+            halt_on_plot_fail=args.halt_on_plot_fail,
         )
 
         window_nuc_pct_quilt_plot_names = []
@@ -374,15 +416,20 @@ def main():
         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_titles'] = {}
         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_labels'] = {}
         crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_datas'] = {}
+        crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_divs'] = {}
 
         crispresso2_info['results']['general_plots']['allele_modification_line_plot_names'] = []
         crispresso2_info['results']['general_plots']['allele_modification_line_plot_paths'] = {}
         crispresso2_info['results']['general_plots']['allele_modification_line_plot_titles'] = {}
         crispresso2_info['results']['general_plots']['allele_modification_line_plot_labels'] = {}
         crispresso2_info['results']['general_plots']['allele_modification_line_plot_datas'] = {}
+        crispresso2_info['results']['general_plots']['allele_modification_line_plot_divs'] = {}
 
         percent_complete_start, percent_complete_end = 90, 99
-        percent_complete_step = (percent_complete_end - percent_complete_start) / len(all_amplicons)
+        if all_amplicons:
+            percent_complete_step = (percent_complete_end - percent_complete_start) / len(all_amplicons)
+        else:
+            percent_complete_step = 0
         # report for amplicons
         for amplicon_index, amplicon_seq in enumerate(all_amplicons):
             # only perform comparison if amplicon seen in more than one sample
@@ -561,11 +608,11 @@ def main():
                         sub_modification_percentage_summary_filename = _jp(amplicon_plot_name + 'Modification_percentage_summary_around_sgRNA_'+sgRNA+'.txt')
                         sub_modification_percentage_summary_df.to_csv(sub_modification_percentage_summary_filename, sep='\t', index=None)
 
-                        if not args.suppress_plots and not args.suppress_batch_summary_plots:
+                        if not args.suppress_plots and not args.suppress_batch_summary_plots and should_plot_large_plots(sub_nucleotide_percentage_summary_df.shape[0], C2PRO_INSTALLED, args.use_matplotlib):
                             # plot for each guide
                             # show all sgRNA's on the plot
-                            sub_sgRNA_intervals = []
-                            for sgRNA_interval in consensus_sgRNA_intervals:
+                            sub_sgRNA_intervals, sub_consensus_guides = [], []
+                            for sgRNA_index, sgRNA_interval in enumerate(consensus_sgRNA_intervals):
                                 newstart = None
                                 newend = None
                                 for idx, i in enumerate(sgRNA_plot_idxs):
@@ -587,15 +634,21 @@ def main():
                                     newend = len(include_idxs) - 1
                                 # and add it to the list
                                 sub_sgRNA_intervals.append((newstart, newend))
+                                sub_consensus_guides.append(consensus_guides[sgRNA_index])
 
-                            this_window_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name + 'Nucleotide_percentage_quilt_around_sgRNA_'+sgRNA)
+                            # scale the include_idxs to be in terms of the plot centered around the sgRNA
+                            sub_include_idxs = include_idxs - sgRNA_plot_idxs[0]
+
+                            this_window_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name.replace('.', '') + 'Nucleotide_percentage_quilt_around_sgRNA_'+sgRNA)
                             nucleotide_quilt_input = {
                                 'nuc_pct_df': sub_nucleotide_percentage_summary_df,
                                 'mod_pct_df': sub_modification_percentage_summary_df,
-                                'fig_filename_root': this_window_nuc_pct_quilt_plot_name,
+                                'fig_filename_root': f'{this_window_nuc_pct_quilt_plot_name}.json' if not args.use_matplotlib and C2PRO_INSTALLED else this_window_nuc_pct_quilt_plot_name,
                                 'save_also_png': save_png,
                                 'sgRNA_intervals': sub_sgRNA_intervals,
-                                'quantification_window_idxs': include_idxs,
+                                'sgRNA_sequences': sub_consensus_guides,
+                                'quantification_window_idxs': sub_include_idxs,
+                                'custom_colors': custom_config['colors'],
                             }
                             debug('Plotting nucleotide percentage quilt for amplicon {0}, sgRNA {1}'.format(amplicon_name, sgRNA))
                             plot(
@@ -605,12 +658,10 @@ def main():
                             plot_name = os.path.basename(this_window_nuc_pct_quilt_plot_name)
                             window_nuc_pct_quilt_plot_names.append(plot_name)
                             crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'sgRNA: ' + sgRNA + ' Amplicon: ' + amplicon_name
-                            if len(consensus_guides) == 1:
-                                crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = ''
                             crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Composition of each base around the guide ' + sgRNA + ' for the amplicon ' + amplicon_name
                             crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Nucleotide frequencies', os.path.basename(nucleotide_frequency_summary_filename)), ('Modification frequencies', os.path.basename(modification_frequency_summary_filename))]
 
-                            if args.base_editor_output:
+                            if args.base_editor_output and should_plot_large_plots(sub_nucleotide_percentage_summary_df.shape[0], False, args.use_matplotlib):
                                 this_window_nuc_conv_plot_name = _jp(amplicon_plot_name + 'Nucleotide_conversion_map_around_sgRNA_'+sgRNA)
                                 conversion_map_input = {
                                     'nuc_pct_df': sub_nucleotide_percentage_summary_df,
@@ -619,7 +670,8 @@ def main():
                                     'conversion_nuc_to': args.conversion_nuc_to,
                                     'save_also_png': save_png,
                                     'sgRNA_intervals': sub_sgRNA_intervals,
-                                    'quantification_window_idxs': include_idxs,
+                                    'quantification_window_idxs': sub_include_idxs,
+                                    'custom_colors': custom_config['colors']
                                 }
                                 debug('Plotting nucleotide conversion map for amplicon {0}, sgRNA {1}'.format(amplicon_name, sgRNA))
                                 plot(
@@ -637,15 +689,17 @@ def main():
                                                                                 ]
                         # done with per-sgRNA plots
 
-                    if not args.suppress_plots and not args.suppress_batch_summary_plots:  # plot the whole region
-                        this_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name + 'Nucleotide_percentage_quilt')
+                    if not args.suppress_plots and not args.suppress_batch_summary_plots and should_plot_large_plots(nucleotide_percentage_summary_df.shape[0], C2PRO_INSTALLED, args.use_matplotlib):  # plot the whole region
+                        this_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name.replace('.', '') + 'Nucleotide_percentage_quilt')
                         nucleotide_quilt_input = {
                             'nuc_pct_df': nucleotide_percentage_summary_df,
                             'mod_pct_df': modification_percentage_summary_df,
-                            'fig_filename_root': this_nuc_pct_quilt_plot_name,
+                            'fig_filename_root': f'{this_nuc_pct_quilt_plot_name}.json' if not args.use_matplotlib and C2PRO_INSTALLED else this_nuc_pct_quilt_plot_name,
                             'save_also_png': save_png,
                             'sgRNA_intervals': consensus_sgRNA_intervals,
+                            'sgRNA_sequences': consensus_guides,
                             'quantification_window_idxs': include_idxs,
+                            'custom_colors': custom_config['colors'],
                         }
                         debug('Plotting nucleotide quilt for {0}'.format(amplicon_name))
                         plot(
@@ -659,7 +713,7 @@ def main():
                             crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = ''
                         crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Composition of each base for the amplicon ' + amplicon_name
                         crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Nucleotide frequencies', os.path.basename(nucleotide_frequency_summary_filename)), ('Modification frequencies', os.path.basename(modification_frequency_summary_filename))]
-                        if args.base_editor_output:
+                        if args.base_editor_output and should_plot_large_plots(nucleotide_percentage_summary_df.shape[0], False, args.use_matplotlib):
                             this_nuc_conv_plot_name = _jp(amplicon_plot_name + 'Nucleotide_conversion_map')
                             conversion_map_input = {
                                 'nuc_pct_df': nucleotide_percentage_summary_df,
@@ -669,6 +723,7 @@ def main():
                                 'save_also_png': save_png,
                                 'sgRNA_intervals': consensus_sgRNA_intervals,
                                 'quantification_window_idxs': include_idxs,
+                                'custom_colors': custom_config['colors']
                             }
                             debug('Plotting nucleotide conversion map for {0}'.format(amplicon_name))
                             plot(
@@ -685,31 +740,33 @@ def main():
                             crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Nucleotide frequencies', os.path.basename(nucleotide_frequency_summary_filename)), ('Modification frequencies', os.path.basename(modification_frequency_summary_filename))]
 
                 else:  # guides are not the same
-                    if not args.suppress_plots and not args.suppress_batch_summary_plots:
-                        this_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name + 'Nucleotide_percentage_quilt')
+                    if not args.suppress_plots and not args.suppress_batch_summary_plots and should_plot_large_plots(nucleotide_percentage_summary_df.shape[0], C2PRO_INSTALLED, args.use_matplotlib):
+                        this_nuc_pct_quilt_plot_name = _jp(amplicon_plot_name.replace('.', '') + 'Nucleotide_percentage_quilt')
                         nucleotide_quilt_input = {
                             'nuc_pct_df': nucleotide_percentage_summary_df,
                             'mod_pct_df': modification_percentage_summary_df,
-                            'fig_filename_root': this_nuc_pct_quilt_plot_name,
+                            'fig_filename_root': f'{this_nuc_pct_quilt_plot_name}.json' if not args.use_matplotlib and C2PRO_INSTALLED else this_nuc_pct_quilt_plot_name,
                             'save_also_png': save_png,
+                            'custom_colors': custom_config['colors'],
                         }
                         debug('Plotting nucleotide quilt for {0}'.format(amplicon_name))
                         plot(
-                            CRISPRessoPlot.plot_nucleotide_quilt,
-                            nucleotide_quilt_input,
-                        )
+                                CRISPRessoPlot.plot_nucleotide_quilt,
+                                nucleotide_quilt_input,
+                            )
                         plot_name = os.path.basename(this_nuc_pct_quilt_plot_name)
                         nuc_pct_quilt_plot_names.append(plot_name)
                         crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Composition of each base for the amplicon ' + amplicon_name
                         crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Nucleotide frequencies', os.path.basename(nucleotide_frequency_summary_filename)), ('Modification frequencies', os.path.basename(modification_frequency_summary_filename))]
-                        if args.base_editor_output:
-                            this_nuc_conv_plot_name = _jp(amplicon_plot_name + 'Nucleotide_percentage_quilt')
+                        if args.base_editor_output and should_plot_large_plots(nucleotide_percentage_summary_df.shape[0], False, args.use_matplotlib):
+                            this_nuc_conv_plot_name = _jp(amplicon_plot_name + 'Nucleotide_conversion_map')
                             conversion_map_input = {
                                 'nuc_pct_df': nucleotide_percentage_summary_df,
                                 'fig_filename_root': this_nuc_conv_plot_name,
                                 'conversion_nuc_from': args.conversion_nuc_from,
                                 'conversion_nuc_to': args.conversion_nuc_to,
                                 'save_also_png': save_png,
+                                'custom_colors': custom_config['colors']
                             }
                             debug('Plotting BE nucleotide conversion map for {0}'.format(amplicon_name))
                             plot(
@@ -722,7 +779,7 @@ def main():
                             crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Nucleotide frequencies', os.path.basename(nucleotide_frequency_summary_filename)), ('Modification frequencies', os.path.basename(modification_frequency_summary_filename))]
 
                 # allele modification frequency heatmap and line plots
-                if not args.suppress_plots and not args.suppress_batch_summary_plots:
+                if C2PRO_INSTALLED and not args.use_matplotlib and not args.suppress_plots and not args.suppress_batch_summary_plots:
                     if guides_all_same:
                         sgRNA_intervals = [consensus_sgRNA_intervals] * modification_frequency_summary_df.shape[0]
                     else:
@@ -748,11 +805,14 @@ def main():
                         plot_name = 'CRISPRessoBatch_percentage_of_{0}_across_alleles_{1}_heatmap'.format(modification_type.lower(), amplicon_name)
                         plot_path = '{0}.html'.format(_jp(plot_name))
 
+                        heatmap_div_id = '{0}-allele-modification-heatmap-{1}'.format(amplicon_name.lower(), modification_type.lower())
                         allele_modification_heatmap_input = {
                             'sample_values': modification_df,
                             'sample_sgRNA_intervals': sgRNA_intervals,
                             'plot_path': plot_path,
                             'title': modification_type,
+                            'div_id': heatmap_div_id,
+                            'amplicon_name': amplicon_name,
                         }
                         debug('Plotting allele modification heatmap for {0}'.format(amplicon_name))
                         plot(
@@ -775,15 +835,19 @@ def main():
                                 ),
                             ),
                         ]
+                        crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_divs'][plot_name] = heatmap_div_id
 
                         plot_name = 'CRISPRessoBatch_percentage_of_{0}_across_alleles_{1}_line'.format(modification_type.lower(), amplicon_name)
                         plot_path = '{0}.html'.format(_jp(plot_name))
 
+                        line_div_id = '{0}-allele-modification-line-{1}'.format(amplicon_name.lower(), modification_type.lower())
                         allele_modification_line_input = {
                             'sample_values': modification_df,
                             'sample_sgRNA_intervals': sgRNA_intervals,
                             'plot_path': plot_path,
                             'title': modification_type,
+                            'div_id': line_div_id,
+                            'amplicon_name': amplicon_name,
                         }
                         debug('Plotting allele modification line plot for {0}'.format(amplicon_name))
                         plot(
@@ -806,6 +870,7 @@ def main():
                                 ),
                             ),
                         ]
+                        crispresso2_info['results']['general_plots']['allele_modification_line_plot_divs'][plot_name] = line_div_id
             #end if amp_found_count > 0 (how many folders had information for this amplicon)
         #end per-amplicon analysis
 
@@ -879,7 +944,12 @@ def main():
                 debug('CRISPResso batch results:')
                 for future in process_futures:
                     debug('future: ' + str(future))
-            future_results = [f.result() for f in process_futures] #required to raise exceptions thrown from within future
+            for future in process_futures:
+                try:
+                    future.result()
+                except Exception as e:
+                    logger.warning('Error in plot pool: %s' % e)
+                    logger.debug(traceback.format_exc())
             process_pool.shutdown()
 
         if not args.suppress_report:
@@ -887,7 +957,7 @@ def main():
                 report_name = _jp("CRISPResso2Batch_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_batch_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_batch_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
diff --git a/CRISPResso2/CRISPRessoCORE.py b/CRISPResso2/CRISPRessoCORE.py
index cbfb2911..251aaba9 100644
--- a/CRISPResso2/CRISPRessoCORE.py
+++ b/CRISPResso2/CRISPRessoCORE.py
@@ -25,12 +25,19 @@
 import re
 import subprocess as sb
 import traceback
+from multiprocessing import Process
 
 
 from CRISPResso2 import CRISPRessoCOREResources
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 from CRISPResso2 import CRISPRessoShared
-from CRISPResso2 import CRISPRessoPlot
+
+if CRISPRessoShared.is_C2Pro_installed():
+    from CRISPRessoPro import __version__ as CRISPRessoProVersion
+    C2PRO_INSTALLED = True
+else:
+    C2PRO_INSTALLED = False
+
 from CRISPResso2 import CRISPResso2Align
 from CRISPResso2 import CRISPRessoMultiProcessing
 
@@ -81,13 +88,50 @@ def is_exe(fpath):
 
     return None
 
-def check_program(binary_name,download_url=None):
+
+def check_program(binary_name, download_url=None, version_flag=None, version_regex=None, version=None):
+    """Check if a program is installed and accessible.
+
+    Parameters
+    ----------
+    binary_name : str
+        Name of the binary to check.
+    download_url : str, optional
+        URL to download the program from that is displayed if not installed.
+    version_flag : str, optional
+        Flag to pass to the program to get the version.
+    version_regex : str, optional
+        Regex to extract the version from the output of the program.
+    version : str, optional
+        Version to check against.
+
+    Returns
+    -------
+    None, will exit if program is not installed.
+    """
     if not which(binary_name):
-        error('You need to install and have the command #####%s##### in your PATH variable to use CRISPResso!\n Please read the documentation!' % binary_name)
+        error('You need to install and have the command #####{0}##### in your PATH variable to use CRISPResso!\n Please read the documentation!'.format(binary_name))
         if download_url:
-            error('You can download it here:%s' % download_url)
+            error('You can download it here: {0}'.format(download_url))
         sys.exit(1)
 
+    if version_flag:
+        p = sb.Popen('{0} {1}'.format(binary_name, version_flag), shell=True, stdout=sb.PIPE, stderr=sb.STDOUT)
+        if binary_name == 'fastp':
+            p_output = p.communicate()[0].decode('utf-8')
+        else:
+            p_output = p.communicate()[1].decode('utf-8')
+        major_version, minor_version, patch_version = map(int, re.search(version_regex, p_output).groups())
+        if major_version <= version[0] and minor_version <= version[1] and patch_version < version[2]:
+            error('You need to install version {0} of {1} to use CRISPResso!'.format(version, binary_name))
+            error('You have version {0}.{1}.{2} installed'.format(major_version, minor_version, patch_version))
+            if download_url:
+                error('You can download it here: {0}'.format(download_url))
+            sys.exit(1)
+
+
+check_fastp = lambda: check_program('fastp', download_url='http://opengene.org/fastp/fastp', version_flag='--version', version_regex=r'fastp (\d+)\.(\d+)\.(\d+)', version=(0, 19, 8))
+
 def get_avg_read_length_fastq(fastq_filename):
      cmd=('z' if fastq_filename.endswith('.gz') else '' ) +('cat < \"%s\"' % fastq_filename)+\
                   r''' | awk 'BN {n=0;s=0;} NR%4 == 2 {s+=length($0);n++;} END { printf("%d\n",s/n)}' '''
@@ -107,25 +151,9 @@ def get_n_reads_bam(bam_filename,bam_chr_loc=""):
         raise CRISPRessoShared.InstallationException('Error when running the command:' + cmd + '\nCheck that samtools is installed correctly.')
     return retval
 
-#import time
-#start = time.time()
-matplotlib=check_library('matplotlib')
-#end = time.time()
-#start = time.time()
-from matplotlib import font_manager as fm
-CRISPRessoPlot.setMatplotlibDefaults()
-#end = time.time()
-
-#start = time.time()
-plt=check_library('pylab')
-#end = time.time()
-
-from matplotlib import font_manager as fm
-import matplotlib.gridspec as gridspec
 
 pd=check_library('pandas')
 np=check_library('numpy')
-check_program('flash')
 
 #start = time.time()
 sns=check_library('seaborn')
@@ -136,6 +164,79 @@ def get_n_reads_bam(bam_filename,bam_chr_loc=""):
 
 #########################################
 
+
+def split_quant_window_coordinates(quant_window_coordinates):
+    """Split the quantification window coordinates to be iterated over.
+
+    Parameters
+    ----------
+    quant_window_coordinates: str
+        The quantification window coordinates, in the form "5-10_100-101", where
+        the "_" delimits separate ranges and the "-" delimits the range itself.
+
+    Returns
+    -------
+    list of tuples
+        Where each element is a tuple and the first element of the tuple is the
+        start of the range and the second element is the end of the range.
+    """
+    coord_re = re.compile(r'^(\d+)-(\d+)$')
+    try:
+        return [tuple(map(int, coord_re.match(c).groups())) for c in quant_window_coordinates.split('_')]
+    except:
+        raise CRISPRessoShared.BadParameterException("Cannot parse analysis window coordinate '" + str(quant_window_coordinates))
+
+
+def get_include_idxs_from_quant_window_coordinates(quant_window_coordinates):
+    """Get the include_idxs from the quantification window coordinates.
+
+    Parameters
+    ----------
+    quant_window_coordinates: str
+        The quantification window coordinates, in the form "5-10_100-101", where
+        the "_" delimits separate ranges and the "-" delimits the range itself.
+
+    Returns
+    -------
+    list of int
+        The include_idxs to be used for quantification, which is the quantification
+        window coordinates expanded to include all individual indexes contained therein.
+    """
+    return [
+        i
+        for coord in split_quant_window_coordinates(quant_window_coordinates)
+        for i in range(coord[0], coord[1] + 1)
+    ]
+
+
+def get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, idxs):
+    """Get the include_idxs from the quantification window coordinates, but adjusted according to s1ind.
+
+    Parameters
+    ----------
+    quant_window_coordinates: str
+        The quantification window coordinates, in the form "5-10_100-101", where
+        the "_" delimits separate ranges and the "-" delimits the range itself.
+    idxs: list of int
+        The index values mapped to the amplicon from which this is being cloned.
+
+    Returns
+    -------
+    list of int
+        The include_idxs to be used for quantification, which is the quantification
+        window coordinates expanded to include all individual indexes contained therein,
+        but adjusted according to s1inds.
+    """
+    include_idxs = []
+    for i in range(1, len(idxs)):
+        if abs(idxs[i-1]) == idxs[i]:
+            idxs[i] = -1 * abs(idxs[i])
+    for coord in split_quant_window_coordinates(quant_window_coordinates):
+        include_idxs.extend(idxs[coord[0]:coord[1] + 1])
+
+    return list(filter(lambda x: x >= 0, include_idxs))
+
+
 def get_pe_scaffold_search(prime_edited_ref_sequence, prime_editing_pegRNA_extension_seq, prime_editing_pegRNA_scaffold_seq, prime_editing_pegRNA_scaffold_min_match_length):
     """
     For prime editing, determines the scaffold string to search for (the shortest substring of args.prime_editing_pegRNA_scaffold_seq not in the prime-edited reference sequence)
@@ -152,7 +253,7 @@ def get_pe_scaffold_search(prime_edited_ref_sequence, prime_editing_pegRNA_exten
     """
     info('Processing pegRNA scaffold sequence...')
     #first, define the sequence we are looking for (extension plus the first base(s) of the scaffold)
-    scaffold_dna = CRISPRessoShared.reverse_complement(prime_editing_pegRNA_scaffold_seq)
+    scaffold_dna = CRISPRessoShared.reverse_complement(prime_editing_pegRNA_scaffold_seq.upper().replace('U','T'))
 
     extension_seq_dna_top_strand = prime_editing_pegRNA_extension_seq.upper().replace('U', 'T')
     prime_editing_extension_seq_dna = CRISPRessoShared.reverse_complement(extension_seq_dna_top_strand)
@@ -268,6 +369,24 @@ def get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaf
 
             payload['ref_name'] = best_match_name
             payload['aln_scores'] = aln_scores
+
+            payload['irregular_ends'] = False
+            if best_match_s1s[idx][0] == '-' or best_match_s2s[idx][0] == '-' or best_match_s1s[idx][0] != best_match_s2s[idx][0]:
+                payload['irregular_ends'] = True
+            elif best_match_s1s[idx][-1] == '-' or best_match_s2s[idx][-1] == '-' or best_match_s1s[idx][-1] != best_match_s2s[idx][-1]:
+                payload['irregular_ends'] = True
+
+            #Insertions out of quantification window
+            payload['insertions_outside_window'] = (len(payload['all_insertion_positions'])/2) - (len(payload['insertion_positions'])/2)
+            #Deletions out of quantification window
+            payload['deletions_outside_window'] = len(payload['all_deletion_coordinates']) - len(payload['deletion_coordinates'])
+            #Substitutions out of quantification window
+            payload['substitutions_outside_window'] = len(payload['all_substitution_positions']) - len(payload['substitution_positions'])
+            #Sums
+            payload['total_mods'] = (len(payload['all_insertion_positions'])/2) + len(payload['all_deletion_positions']) + len(payload['all_substitution_positions'])
+            payload['mods_in_window'] = payload['substitution_n'] + payload['deletion_n'] + payload['insertion_n']
+            payload['mods_outside_window'] = payload['total_mods'] - payload['mods_in_window']
+
             # If there is an insertion/deletion/substitution in the quantification window, the read is modified.
             is_modified = False
             if not args.ignore_deletions and payload['deletion_n'] > 0:
@@ -289,6 +408,7 @@ def get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaf
             payload['aln_strand'] = best_match_strands[idx]
 
             new_variant['variant_'+best_match_name] = payload
+            new_variant['best_match_name'] = best_match_name
 
         new_variant['class_name'] = "&".join(class_names)
 
@@ -322,9 +442,84 @@ def get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaf
 
     return new_variant
 
-def process_fastq(fastq_filename, variantCache, ref_names, refs, args):
+
+
+
+def get_variant_cache_equal_boundaries(num_unique_sequences, n_processes):
+    """Determines the boundaries for the number of unique sequences to be processed by each process
+    Parameters
+    ----------
+        num_unique_sequences: the number of unique sequences to be processed
+        n_processes: the number of processes to be used
+    Returns
+    ----------
+    boundaries: a list of n+1 integer indexes, where n is the number of processes.
+    """
+
+    if num_unique_sequences < n_processes:
+        raise Exception("The number of unique sequences is less than the number of processes. Please reduce the number of processes.")
+
+    boundaries = [0]
+    # Determine roughly equal segment size for each process
+    segment_size = num_unique_sequences // n_processes
+
+    for i in range(n_processes - 1):
+        boundaries.append(boundaries[-1] + segment_size)
+    boundaries.append(num_unique_sequences)
+    return boundaries
+
+
+def variant_file_generator_process(seq_list, get_new_variant_object, args, refs, ref_names, aln_matrix, pe_scaffold_dna_info, process_id, variants_dir):
+    """the target of the multiprocessing.Process object, generates the new variants for a subset of the reads in the fastq file and stores them in tsv files
+    Parameters
+    ----------
+        seq_list: list of reads to process
+        get_new_variant_object: function to generate the new variant object
+        args: CRISPResso2 args
+        refs: dict with info for all refs
+        ref_names: list of ref names
+        aln_matrix: alignment matrix for needleman wunsch
+        pe_scaffold_dna_info: tuple of(
+        index of location in ref to find scaffold seq if it exists
+        shortest dna sequence to identify scaffold sequence
+        )
+        process_id: the id of the process to print out debug information
+        variants_dir: the directory to store the tsv files
+    Returns
+    ----------
+    Nothing
+
+    """
+    def numpy_encoder(obj):
+        """ Custom encoding for numpy arrays and other non-serializable types """
+        if isinstance(obj, np.ndarray):
+            return obj.tolist()  # Convert numpy arrays to lists
+        raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
+
+    variant_file_path = os.path.join(variants_dir, f"variants_{process_id}.tsv")
+
+    variant_lines = ""
+    with open(variant_file_path, 'w') as file:
+        file.truncate() # Ensures tsv file is empty before writing to it
+        for index, fastq_seq in enumerate(seq_list):
+            new_variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
+            # Convert the complex object to a JSON string
+            json_string = json.dumps(new_variant, default=numpy_encoder)
+            variant_lines += f"{fastq_seq}\t{json_string}\n"
+            if index % 10000 == 0 and index != 0:
+                info(f"Process {process_id + 1} has processed {index} unique reads", {'percent_complete': 10})
+                file.write(variant_lines)
+                variant_lines = ""
+        file.write(variant_lines)
+
+    info(f"Process {process_id + 1} has finished processing {index} unique reads", {'percent_complete': 10})
+
+
+def process_fastq(fastq_filename, variantCache, ref_names, refs, args, files_to_remove, output_directory, fastq_write_out=False):
     """process_fastq processes each of the reads contained in a fastq file, given a cache of pre-computed variants
-        fastqIn: name of fastq (e.g. output of FLASH)
+    Parameters
+    ----------
+        fastq_filename: name of fastq (e.g. output of fastp)
             This file can be gzipped or plain text
 
         variantCache: dict with keys: sequence
@@ -395,72 +590,206 @@ def process_fastq(fastq_filename, variantCache, ref_names, refs, args):
            -allelic varaints if two variants are known to exist
 
         """
-
-    N_TOT_READS = 0
-    N_CACHED_ALN = 0 # read was found in cache
-    N_CACHED_NOTALN = 0 #read was found in 'not aligned' cache
-    N_COMPUTED_ALN = 0 # not in cache, aligned to at least 1 sequence with min cutoff
-    N_COMPUTED_NOTALN = 0 #not in cache, not aligned to any sequence with min cutoff
-
     aln_matrix_loc = os.path.join(_ROOT, args.needleman_wunsch_aln_matrix_loc)
     CRISPRessoShared.check_file(aln_matrix_loc)
     aln_matrix = CRISPResso2Align.read_matrix(aln_matrix_loc)
-
     pe_scaffold_dna_info = (0, None) #scaffold start loc, scaffold seq to search
-    if args.prime_editing_pegRNA_scaffold_seq != "":
+    if args.prime_editing_pegRNA_scaffold_seq != "" and args.prime_editing_pegRNA_extension_seq != "":
         pe_scaffold_dna_info = get_pe_scaffold_search(refs['Prime-edited']['sequence'], args.prime_editing_pegRNA_extension_seq, args.prime_editing_pegRNA_scaffold_seq, args.prime_editing_pegRNA_scaffold_min_match_length)
 
-    not_aln = {} #cache for reads that don't align
+    if fastq_write_out:
+        not_aligned_variants = {}
 
-    if fastq_filename.endswith('.gz'):
-        fastq_handle = gzip.open(fastq_filename, 'rt')
-    else:
-        fastq_handle=open(fastq_filename)
-
-    while(fastq_handle.readline()):
-        #read through fastq in sets of 4
-        fastq_seq = fastq_handle.readline().strip()
-        fastq_plus = fastq_handle.readline()
-        fastq_qual = fastq_handle.readline()
 
-        if (N_TOT_READS % 10000 == 0):
-            info("Processing reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
 
-        N_TOT_READS+=1
-
-        #if the sequence has been seen and can't be aligned, skip it
-        if (fastq_seq in not_aln):
-            N_CACHED_NOTALN += 1
-            continue
-        #if the sequence is already associated with a variant in the variant cache, pull it out
-        if (fastq_seq in variantCache):
-            N_CACHED_ALN+=1
-            variantCache[fastq_seq]['count'] += 1
+    if fastq_filename.endswith('.gz'):
+        fastq_input_opener = lambda x: gzip.open(x, 'rt')
+    else:
+        fastq_input_opener = open
+
+    with fastq_input_opener(fastq_filename) as fastq_handle:
+
+        # Reading through the fastq file and enriching variantCache as a dictionary with the following:
+            # Key: the unique DNA sequence from the fastq file
+            # Value: an integer that represents how many times we've seen this specific read
+        num_reads = 0
+        fastq_id = fastq_handle.readline()
+        while(fastq_id):
+            if num_reads % 50000 == 0 and num_reads != 0:
+                info("Iterating over fastq file to identify reads; %d reads identified."%(num_reads))
+            #read through fastq in sets of 4
+            fastq_seq = fastq_handle.readline().strip()
+            fastq_plus = fastq_handle.readline().strip()
+            fastq_qual = fastq_handle.readline()
+            if fastq_seq in variantCache:
+                # if the read has already been seen, we increment its value by 1 to track number of copies
+                variantCache[fastq_seq] += 1
+            # If the sequence is not in the cache, we create it and set its value to 1
+            elif fastq_seq not in variantCache:
+                variantCache[fastq_seq] = 1
+            fastq_id = fastq_handle.readline()
+            num_reads += 1
+
+        num_unique_reads = len(variantCache.keys())
+        info("Finished reading fastq file; %d unique reads found of %d total reads found "%(num_unique_reads, num_reads))
+
+    n_processes = 1
+    if args.n_processes == "max":
+        n_processes = CRISPRessoMultiProcessing.get_max_processes()
+    elif args.n_processes.isdigit():
+        n_processes = int(args.n_processes)
 
-        #otherwise, create a new variant object, and put it in the cache
+    N_TOT_READS = 0
+    N_CACHED_ALN = 0 # number of copies of all aligned reads
+    N_CACHED_NOTALN = 0 # number of copies of all non-aligned reads
+    N_COMPUTED_ALN = 0 # number of unique reads aligned to at least 1 sequence with min cutoff
+    N_COMPUTED_NOTALN = 0 # number of unique reads not aligned to any sequence with min cutoff
+    N_GLOBAL_SUBS = 0 # number of substitutions across all reads - indicator of sequencing quality
+    N_SUBS_OUTSIDE_WINDOW = 0
+    N_MODS_IN_WINDOW = 0 # number of modifications found inside the quantification window
+    N_MODS_OUTSIDE_WINDOW = 0 # number of modifications found outside the quantification window
+    N_READS_IRREGULAR_ENDS = 0 # number of reads with modifications at the 0 or -1 position
+    READ_LENGTH = 0
+    unaligned_reads = []
+
+    if n_processes > 1 and num_unique_reads > n_processes:
+        boundaries = get_variant_cache_equal_boundaries(num_unique_reads, n_processes)
+
+        processes = [] # list to hold the processes so we can wait for them to complete with join()
+
+        info("Spinning up %d parallel processes to analyze unique reads..."%(n_processes))
+        # We create n_processes, sending each a weighted sublist of the sequences for variant generation
+        variants_dir = output_directory
+        for i in range(n_processes):
+            left_sublist_index = boundaries[i]
+            right_sublist_index = boundaries[i+1]
+            process = Process(
+                target=variant_file_generator_process,
+                args=(
+                      (list(variantCache.keys())[left_sublist_index:right_sublist_index]),
+                      get_new_variant_object,
+                      args,
+                      refs,
+                      ref_names,
+                      aln_matrix,
+                      pe_scaffold_dna_info,
+                      i,
+                      variants_dir
+                    )
+            )
+            process.start()
+            processes.append(process)
+        for p in processes:
+            p.join() # pauses the main thread until the processes are finished
+        info("Finished processing unique reads, now generating statistics...", {'percent_complete': 15})
+        if os.path.exists(variants_dir):
+            variant_file_list = []
+            for n_processes in range(n_processes):
+                variant_file_list.append(os.path.join(variants_dir, f"variants_{n_processes}.tsv"))
+            # List all files in the directory
+            for file_path in variant_file_list:
+                # Ensure the file is a .tsv before processing
+                if file_path.endswith(".tsv"):
+                    try:
+                        with open(file_path, 'r') as file:
+                            for index, line in enumerate(file):
+                                # Each line contains a sequence followed by a JSON string
+                                parts = line.strip().split('\t')
+                                if len(parts) == 2:
+                                    seq = parts[0]
+                                    json_data = parts[1]
+                                    variant_dict = json.loads(json_data)
+                                else:
+                                    error(f"Error splitting line: {line}")
+                                variant_count = variantCache[seq]
+                                N_TOT_READS += variant_count
+                                variant = variant_dict
+                                variant['count'] = variant_count
+                                if variant['best_match_score'] <= 0:
+                                    N_COMPUTED_NOTALN += 1
+                                    N_CACHED_NOTALN += (variant_count - 1)
+                                    if fastq_write_out:
+                                        not_aligned_variants[seq] = variant
+                                    # remove the unaligned reads from the cache
+                                    unaligned_reads.append(seq)
+                                elif variant['best_match_score'] > 0:
+                                    variantCache[seq] = variant
+                                    N_COMPUTED_ALN += 1
+                                    N_CACHED_ALN += (variant_count - 1)
+                                    match_name = "variant_" + variant['best_match_name']
+                                    if READ_LENGTH == 0:
+                                        READ_LENGTH = len(variant[match_name]['aln_seq'])
+                                    N_GLOBAL_SUBS += (variant[match_name]['substitution_n'] + variant[match_name]['substitutions_outside_window']) * variant_count
+                                    N_SUBS_OUTSIDE_WINDOW += variant[match_name]['substitutions_outside_window'] * variant_count
+                                    N_MODS_IN_WINDOW += variant[match_name]['mods_in_window'] * variant_count
+                                    N_MODS_OUTSIDE_WINDOW += variant[match_name]['mods_outside_window'] * variant_count
+                                    if variant[match_name]['irregular_ends']:
+                                        N_READS_IRREGULAR_ENDS += variant_count
+                                if (index % 50000 == 0 and index > 0):
+                                    info("Calculating statistics; %d completed out of %d unique reads"%(index, num_unique_reads))
+                    except FileNotFoundError:
+                        raise CRISPRessoShared.OutputFolderIncompleteException(f"Could not find generated variants file, try deleting output folder, checking input files, and rerunning CRISPResso")
+                files_to_remove.append(file_path)
         else:
-            new_variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
-            if new_variant['best_match_score'] < 0:
-                N_COMPUTED_NOTALN+=1
-                not_aln[fastq_seq] = 1
-            else:
-                N_COMPUTED_ALN+=1
-                variantCache[fastq_seq] = new_variant
+            raise CRISPRessoShared.OutputFolderIncompleteException(f"Could not find output folder, try deleting output folder and rerunning CRISPResso")
 
-    fastq_handle.close()
+        if N_COMPUTED_ALN + N_COMPUTED_NOTALN != num_unique_reads:
+            raise CRISPRessoShared.OutputFolderIncompleteException(f"Number of unique reads processed by parallel processes does not match the number of unique reads found in the fastq file. Try rerunning CRISPResso.")
+    else:
+        for index, fastq_seq in enumerate(variantCache.keys()):
+            variant_count = variantCache[fastq_seq]
+            N_TOT_READS += variant_count
+            variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
+            variant['count'] = variant_count
+            if variant['best_match_score'] <= 0:
+                N_COMPUTED_NOTALN += 1
+                N_CACHED_NOTALN += (variant_count - 1)
+                unaligned_reads.append(fastq_seq)
+                if fastq_write_out:
+                    not_aligned_variants[fastq_seq] = variant
+            elif variant['best_match_score'] > 0:
+                variantCache[fastq_seq] = variant
+                N_COMPUTED_ALN += 1
+                N_CACHED_ALN += (variant_count - 1)
+                match_name = "variant_" + variant['best_match_name']
+                if READ_LENGTH == 0:
+                    READ_LENGTH = len(variant[match_name]['aln_seq'])
+                N_GLOBAL_SUBS += (variant[match_name]['substitution_n'] + variant[match_name]['substitutions_outside_window']) * variant_count
+                N_SUBS_OUTSIDE_WINDOW += variant[match_name]['substitutions_outside_window'] * variant_count
+                N_MODS_IN_WINDOW += variant[match_name]['mods_in_window'] * variant_count
+                N_MODS_OUTSIDE_WINDOW += variant[match_name]['mods_outside_window'] * variant_count
+                if variant[match_name]['irregular_ends']:
+                    N_READS_IRREGULAR_ENDS += variant_count
+            if (index % 10000 == 0):
+                info("Processing Reads; %d Completed out of %d Unique Reads"%(index, num_unique_reads))
+
+    # This deletes non-aligned reads from variantCache
+    for seq in unaligned_reads:
+        del variantCache[seq]
 
     info("Finished reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
     aln_stats = {"N_TOT_READS" : N_TOT_READS,
-               "N_CACHED_ALN" : N_CACHED_ALN,
-               "N_CACHED_NOTALN" : N_CACHED_NOTALN,
-               "N_COMPUTED_ALN" : N_COMPUTED_ALN,
-               "N_COMPUTED_NOTALN" : N_COMPUTED_NOTALN
-               }
-    return(aln_stats)
+            "N_CACHED_ALN" : N_CACHED_ALN,
+            "N_CACHED_NOTALN" : N_CACHED_NOTALN,
+            "N_COMPUTED_ALN" : N_COMPUTED_ALN,
+            "N_COMPUTED_NOTALN" : N_COMPUTED_NOTALN,
+            "N_GLOBAL_SUBS": N_GLOBAL_SUBS,
+            "N_SUBS_OUTSIDE_WINDOW": N_SUBS_OUTSIDE_WINDOW,
+            "N_MODS_IN_WINDOW": N_MODS_IN_WINDOW,
+            "N_MODS_OUTSIDE_WINDOW": N_MODS_OUTSIDE_WINDOW,
+            "N_READS_IRREGULAR_ENDS": N_READS_IRREGULAR_ENDS,
+            "READ_LENGTH": READ_LENGTH
+            }
+    if fastq_write_out:
+        return(aln_stats, not_aligned_variants)
+    else:
+        return(aln_stats)
 
-def process_bam(bam_filename, bam_chr_loc, output_bam, variantCache, ref_names, refs, args):
+
+def process_bam(bam_filename, bam_chr_loc, output_bam, variantCache, ref_names, refs, args, files_to_remove, output_directory):
     """
     process_bam processes each of the reads contained in a bam file, given a cache of pre-computed variants
+    like process_fastq, it also spins up parallel processes to analyze unique reads if more than one has been specified
         bam_filename: name of bam (e.g. output of bowtie2)
         bam_chr_loc: positions in the input bam to read from
         output_bam: name out bam to write to (includes crispresso alignment info)
@@ -469,24 +798,17 @@ def process_bam(bam_filename, bam_chr_loc, output_bam, variantCache, ref_names,
         ref_names: list of reference names
         refs: dictionary of sequences name>ref object
         args: crispresso2 args
+        files_to_remove: list of files to remove
+        output_directory: directory to store tsv files
     """
-
-    N_TOT_READS = 0
-    N_CACHED_ALN = 0 # read was found in cache
-    N_CACHED_NOTALN = 0 #read was found in 'not aligned' cache
-    N_COMPUTED_ALN = 0 # not in cache, aligned to at least 1 sequence with min cutoff
-    N_COMPUTED_NOTALN = 0 #not in cache, not aligned to any sequence with min cutoff
-
     aln_matrix_loc = os.path.join(_ROOT, args.needleman_wunsch_aln_matrix_loc)
     CRISPRessoShared.check_file(aln_matrix_loc)
     aln_matrix = CRISPResso2Align.read_matrix(aln_matrix_loc)
 
     pe_scaffold_dna_info = (0, None) #scaffold start loc, scaffold sequence
-    if args.prime_editing_pegRNA_scaffold_seq != "":
+    if args.prime_editing_pegRNA_scaffold_seq != "" and args.prime_editing_pegRNA_extension_seq != "":
         pe_scaffold_dna_info = get_pe_scaffold_search(refs['Prime-edited']['sequence'], args.prime_editing_pegRNA_extension_seq, args.prime_editing_pegRNA_scaffold_seq, args.prime_editing_pegRNA_scaffold_min_match_length)
 
-    not_aln = {} #cache for reads that don't align
-
     output_sam = output_bam+".sam"
     with open(output_sam, "w") as sam_out:
         #first, write header to sam
@@ -496,55 +818,182 @@ def process_bam(bam_filename, bam_chr_loc, output_bam, variantCache, ref_names,
 
         crispresso_cmd_to_write = ' '.join(sys.argv)
         sam_out.write('@PG\tID:crispresso2\tPN:crispresso2\tVN:'+CRISPRessoShared.__version__+'\tCL:"'+crispresso_cmd_to_write+'"\n')
-
         if bam_chr_loc != "":
             proc = sb.Popen(['samtools', 'view', bam_filename, bam_chr_loc], stdout=sb.PIPE, encoding='utf-8')
         else:
             proc = sb.Popen(['samtools', 'view', bam_filename], stdout=sb.PIPE, encoding='utf-8')
+        num_reads = 0
+
+        # Reading through the bam file and enriching variantCache as a dictionary with the following:
+        # Key: the unique DNA sequence from the fastq file
+        # Value: an integer that represents how many times we've seen this specific read
         for sam_line in proc.stdout:
+            if num_reads % 50000 == 0 and num_reads != 0:
+                info("Iterating over sam file to identify reads; %d reads identified."%(num_reads))
             sam_line_els = sam_line.rstrip().split("\t")
             fastq_seq = sam_line_els[9]
-
-            if (N_TOT_READS % 10000 == 0):
-                info("Processing reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
-
-            N_TOT_READS+=1
-
-            #if the sequence has been seen and can't be aligned, skip it
-            if (fastq_seq in not_aln):
-                N_CACHED_NOTALN += 1
-                sam_line_els.append(not_aln[fastq_seq]) #Crispresso2 alignment: NA
-                sam_out.write("\t".join(sam_line_els)+"\n")
-                continue
-            #if the sequence is already associated with a variant in the variant cache, pull it out
-            if (fastq_seq in variantCache):
-                N_CACHED_ALN+=1
-                variantCache[fastq_seq]['count'] += 1
-                #sam_line_els[5] = variantCache[fastq_seq]['sam_cigar']
-                sam_line_els.append(variantCache[fastq_seq]['crispresso2_annotation'])
-                sam_out.write("\t".join(sam_line_els)+"\n")
-
-            #otherwise, create a new variant object, and put it in the cache
+            if fastq_seq in variantCache:
+                # if the read has already been seen, we increment its value by 1 to track number of copies
+                variantCache[fastq_seq] += 1
+            # If the sequence is not in the cache, we create it and set its value to 1
             else:
+                variantCache[fastq_seq] = 1
+            num_reads += 1
+        num_unique_reads = len(variantCache.keys())
+        info("Finished reading bam file; %d unique reads found of %d total reads found "%(num_unique_reads, num_reads))
+        n_processes = 1
+        if args.n_processes == "max":
+            n_processes = CRISPRessoMultiProcessing.get_max_processes()
+        elif args.n_processes.isdigit():
+            n_processes = int(args.n_processes)
+
+        N_TOT_READS = 0
+        N_CACHED_ALN = 0 # number of copies of all aligned reads
+        N_CACHED_NOTALN = 0 # number of copies of all non-aligned reads
+        N_COMPUTED_ALN = 0 # number of unique reads aligned to at least 1 sequence with min cutoff
+        N_COMPUTED_NOTALN = 0 # number of unique reads not aligned to any sequence with min cutoff
+        N_GLOBAL_SUBS = 0 # number of substitutions across all reads - indicator of sequencing quality
+        N_SUBS_OUTSIDE_WINDOW = 0
+        N_MODS_IN_WINDOW = 0 # number of modifications found inside the quantification window
+        N_MODS_OUTSIDE_WINDOW = 0 # number of modifications found outside the quantification window
+        N_READS_IRREGULAR_ENDS = 0 # number of reads with modifications at the 0 or -1 position
+        READ_LENGTH = 0
+        not_aln = {}
+
+        if n_processes > 1 and num_unique_reads > n_processes:
+            boundaries = get_variant_cache_equal_boundaries(num_unique_reads, n_processes)
+            processes = [] # list to hold the processes so we can wait for them to complete with join()
+            variants_dir = output_directory
+
+            info("Spinning up %d parallel processes to analyze unique reads..."%(n_processes))
+            for i in range(n_processes):
+                left_sublist_index = boundaries[i]
+                right_sublist_index = boundaries[i+1]
+                process = Process(
+                    target=variant_file_generator_process,
+                    args=(
+                        (list(variantCache.keys())[left_sublist_index:right_sublist_index]),
+                        get_new_variant_object,
+                        args,
+                        refs,
+                        ref_names,
+                        aln_matrix,
+                        pe_scaffold_dna_info,
+                        i,
+                        variants_dir
+                        )
+                )
+                process.start()
+                processes.append(process)
+            for p in processes:
+                p.join() # pauses the main thread until the processes are finished
+
+            info("Finished processing unique reads, now generating statistics...", {'percent_complete': 15})
+            variant_file_list = []
+            if os.path.exists(variants_dir):
+                for n_processes in range(n_processes):
+                    variant_file_list.append(os.path.join(variants_dir, f"variants_{n_processes}.tsv"))
+            for file_path in variant_file_list:
+                # Ensure the file is a .tsv before processing
+                if file_path.endswith(".tsv"):
+                    try:
+                        with open(file_path, 'r') as file:
+                            for index, line in enumerate(file):
+                                # Each line contains a sequence followed by a JSON string
+                                parts = line.strip().split('\t')
+                                if len(parts) == 2:
+                                    seq = parts[0]
+                                    json_data = parts[1]
+                                    new_variant = json.loads(json_data)
+                                else:
+                                    error(f"Error splitting line: {line}")
+                                variant_count = variantCache[seq]
+                                new_variant['count'] = variant_count
+                                N_TOT_READS += variant_count
+                                if new_variant['best_match_score'] <= 0:
+                                    N_COMPUTED_NOTALN+=1
+                                    N_CACHED_NOTALN += (variant_count - 1)
+                                    crispresso_sam_optional_fields = "c2:Z:ALN=NA" +\
+                                            " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
+                                            " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']]))
+                                    not_aln[seq] = crispresso_sam_optional_fields
+                                else:
+                                    class_names = []
+                                    ins_inds = []
+                                    del_inds = []
+                                    sub_inds = []
+                                    edit_strings = []
+                                    for idx, best_match_name in enumerate(new_variant['aln_ref_names']):
+                                        payload=new_variant['variant_'+best_match_name]
+
+                                        del_inds.append([str(x[0][0])+"("+str(x[1])+")" for x in zip(payload['deletion_coordinates'], payload['deletion_sizes'])])
+
+                                        ins_vals = []
+                                        for ins_coord,ins_size in zip(payload['insertion_coordinates'],payload['insertion_sizes']):
+                                            ins_start = payload['ref_positions'].index(ins_coord[0])
+                                            ins_vals.append(payload['aln_seq'][ins_start+1:ins_start+1+ins_size])
+                                        ins_inds.append([str(x[0][0])+"("+str(x[1])+"+"+x[2]+")" for x in zip(payload['insertion_coordinates'], payload['insertion_sizes'], ins_vals)])
+
+                                        sub_inds.append(payload['substitution_positions'])
+                                        edit_strings.append('D'+str(int(payload['deletion_n']))+';I'+str(int(payload['insertion_n']))+';S'+str(int(payload['substitution_n'])))
+
+
+                                    crispresso_sam_optional_fields = "c2:Z:ALN="+("&".join(new_variant['aln_ref_names'])) +\
+                                            " CLASS="+new_variant['class_name']+\
+                                            " MODS="+("&".join(edit_strings))+\
+                                            " DEL="+("&".join([';'.join(x) for x in del_inds])) +\
+                                            " INS="+("&".join([';'.join(x) for x in ins_inds])) +\
+                                            " SUB=" + ("&".join([';'.join([str(y) for y in x]) for x in sub_inds])) +\
+                                            " ALN_REF=" + ('&'.join([new_variant['variant_'+name]['aln_ref'] for name in new_variant['aln_ref_names']])) +\
+                                            " ALN_SEQ=" + ('&'.join([new_variant['variant_'+name]['aln_seq'] for name in new_variant['aln_ref_names']]))
+                                    #cigar strings are in reference to the given amplicon, not to the genomic sequence to which this read is aligned..
+                                    #first_variant = new_variant['variant_'+new_variant['aln_ref_names'][0]]
+                                    #sam_cigar = ''.join(CRISPRessoShared.unexplode_cigar(''.join([CRISPRessoShared.CIGAR_LOOKUP[x] for x in zip(first_variant['aln_seq'],first_variant['aln_ref'])])))
+                                    #sam_line_els[5] = sam_cigar
+                                    #new_variant['sam_cigar'] = sam_cigar
+                                    new_variant['crispresso2_annotation'] = crispresso_sam_optional_fields
+                                    variantCache[seq] = new_variant
+                                    N_COMPUTED_ALN+=1
+                                    N_CACHED_ALN += (variant_count - 1)
+                                    match_name = 'variant_' + new_variant['best_match_name']
+                                    if READ_LENGTH == 0:
+                                        READ_LENGTH = len(new_variant[match_name]['aln_seq'])
+                                    N_GLOBAL_SUBS += new_variant[match_name]['substitution_n'] + new_variant[match_name]['substitutions_outside_window'] * variant_count
+                                    N_SUBS_OUTSIDE_WINDOW += new_variant[match_name]['substitutions_outside_window'] * variant_count
+                                    N_MODS_IN_WINDOW += new_variant[match_name]['mods_in_window'] * variant_count
+                                    N_MODS_OUTSIDE_WINDOW += new_variant[match_name]['mods_outside_window'] * variant_count
+                                    if new_variant[match_name]['irregular_ends']:
+                                        N_READS_IRREGULAR_ENDS += variant_count
+
+                    except FileNotFoundError:
+                        raise CRISPRessoShared.OutputFolderIncompleteException(f"Could not find generated variants file, try deleting output folder, checking input files, and rerunning CRISPResso")
+                files_to_remove.append(file_path)
+            if N_COMPUTED_ALN + N_COMPUTED_NOTALN != num_unique_reads:
+                raise CRISPRessoShared.OutputFolderIncompleteException(f"Number of unique reads processed by parallel processes does not match the number of unique reads found in the bam file. Try rerunning CRISPResso.")
+        else:
+        # Single process mode
+            for idx, fastq_seq in enumerate(variantCache.keys()):
+                variant_count = variantCache[fastq_seq]
+                N_TOT_READS += variant_count
                 new_variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
-                if new_variant['best_match_score'] < 0:
+                new_variant['count'] = variant_count
+                if new_variant['best_match_score'] <= 0:
+                    N_CACHED_NOTALN += (variant_count - 1)
                     N_COMPUTED_NOTALN+=1
                     crispresso_sam_optional_fields = "c2:Z:ALN=NA" +\
                             " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
                             " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']]))
                     sam_line_els.append(crispresso_sam_optional_fields)
                     not_aln[fastq_seq] = crispresso_sam_optional_fields
-                    sam_out.write("\t".join(sam_line_els)+"\n")
                 else:
                     N_COMPUTED_ALN+=1
-
+                    N_CACHED_ALN += (variant_count - 1)
                     class_names = []
                     ins_inds = []
                     del_inds = []
                     sub_inds = []
                     edit_strings = []
 
-    #                for idx, best_match_name in enumerate(best_match_names):
                     for idx, best_match_name in enumerate(new_variant['aln_ref_names']):
                         payload=new_variant['variant_'+best_match_name]
 
@@ -568,155 +1017,126 @@ def process_bam(bam_filename, bam_chr_loc, output_bam, variantCache, ref_names,
                             " SUB=" + ("&".join([';'.join([str(y) for y in x]) for x in sub_inds])) +\
                             " ALN_REF=" + ('&'.join([new_variant['variant_'+name]['aln_ref'] for name in new_variant['aln_ref_names']])) +\
                             " ALN_SEQ=" + ('&'.join([new_variant['variant_'+name]['aln_seq'] for name in new_variant['aln_ref_names']]))
-                    sam_line_els.append(crispresso_sam_optional_fields)
-
-                    #cigar strings are in reference to the given amplicon, not to the genomic sequence to which this read is aligned..
-                    #first_variant = new_variant['variant_'+new_variant['aln_ref_names'][0]]
-                    #sam_cigar = ''.join(CRISPRessoShared.unexplode_cigar(''.join([CRISPRessoShared.CIGAR_LOOKUP[x] for x in zip(first_variant['aln_seq'],first_variant['aln_ref'])])))
-                    #sam_line_els[5] = sam_cigar
-                    #new_variant['sam_cigar'] = sam_cigar
                     new_variant['crispresso2_annotation'] = crispresso_sam_optional_fields
+                    variantCache[fastq_seq] = new_variant
 
-                    sam_out.write("\t".join(sam_line_els)+"\n")
+                    match_name = 'variant_' + new_variant['best_match_name']
+                    if READ_LENGTH == 0:
+                        READ_LENGTH = len(new_variant[match_name]['aln_seq'])
+                    N_GLOBAL_SUBS += new_variant[match_name]['substitution_n'] + new_variant[match_name]['substitutions_outside_window'] * variant_count
+                    N_SUBS_OUTSIDE_WINDOW += new_variant[match_name]['substitutions_outside_window'] * variant_count
+                    N_MODS_IN_WINDOW += new_variant[match_name]['mods_in_window'] * variant_count
+                    N_MODS_OUTSIDE_WINDOW += new_variant[match_name]['mods_outside_window'] * variant_count
+                    if new_variant[match_name]['irregular_ends']:
+                        N_READS_IRREGULAR_ENDS += variant_count
+            if (idx % 10000 == 0 and idx > 0):
+                info("Processing Reads; %d Completed out of %d Unique Reads"%(idx, num_unique_reads))
 
-                    variantCache[fastq_seq] = new_variant
 
-    output_sam = output_bam+".sam"
-    cmd = 'samtools view -Sb '+output_sam + '>'+output_bam + ' && samtools index ' + output_bam
-    bam_status=sb.call(cmd, shell=True)
-    if bam_status:
-            raise CRISPRessoShared.BadParameterException(
-                'Bam creation failed. Command used: {0}'.format(cmd),
-            )
+        # Now that we've enriched variantCache with the unique reads, we read through the bam file again to write variants to the output file
+        if bam_chr_loc != "":
+            new_proc = sb.Popen(['samtools', 'view', bam_filename, bam_chr_loc], stdout=sb.PIPE, encoding='utf-8')
+        else:
+            new_proc = sb.Popen(['samtools', 'view', bam_filename], stdout=sb.PIPE, encoding='utf-8')
+
+        for sam_line in new_proc.stdout:
+            sam_line_els = sam_line.rstrip().split("\t")
+            fastq_seq = sam_line_els[9]
+            if fastq_seq in not_aln:
+                sam_line_els.append(not_aln[fastq_seq]) #Crispresso2 alignment: NA
+                sam_out.write("\t".join(sam_line_els)+"\n")
+            elif fastq_seq in variantCache:
+                sam_line_els.append(variantCache[fastq_seq]['crispresso2_annotation'])
+                sam_out.write("\t".join(sam_line_els)+"\n")
 
+    for fastq_seq in not_aln.keys():
+        del variantCache[fastq_seq]
     info("Finished reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
-    aln_stats = {"N_TOT_READS": N_TOT_READS,
-               "N_CACHED_ALN": N_CACHED_ALN,
-               "N_CACHED_NOTALN": N_CACHED_NOTALN,
-               "N_COMPUTED_ALN": N_COMPUTED_ALN,
-               "N_COMPUTED_NOTALN": N_COMPUTED_NOTALN,
-               }
+    aln_stats = {"N_TOT_READS" : N_TOT_READS,
+            "N_CACHED_ALN" : N_CACHED_ALN,
+            "N_CACHED_NOTALN" : N_CACHED_NOTALN,
+            "N_COMPUTED_ALN" : N_COMPUTED_ALN,
+            "N_COMPUTED_NOTALN" : N_COMPUTED_NOTALN,
+            "N_GLOBAL_SUBS": N_GLOBAL_SUBS,
+            "N_SUBS_OUTSIDE_WINDOW": N_SUBS_OUTSIDE_WINDOW,
+            "N_MODS_IN_WINDOW": N_MODS_IN_WINDOW,
+            "N_MODS_OUTSIDE_WINDOW": N_MODS_OUTSIDE_WINDOW,
+            "N_READS_IRREGULAR_ENDS": N_READS_IRREGULAR_ENDS,
+            "READ_LENGTH": READ_LENGTH
+            }
     return(aln_stats)
 
-def process_fastq_write_out(fastq_input, fastq_output, variantCache, ref_names, refs, args):
-    """
-    process_fastq_write_out processes each of the reads contained in a fastq input file, given a cache of pre-computed variants. All reads are read in, analyzed, and written to output with annotation
-        fastq_input: input fastq
-        fastq_output: fastq to write out
-        variantCache: dict with keys: sequence dict with keys (described in process_fastq)
-
-        ref_names: list of reference names
-        refs: dictionary of sequences name>ref object
-        args: crispresso2 args
-    """
+def process_fastq_write_out(fastq_input, fastq_output, variantCache, ref_names, refs, args, files_to_remove, output_directory):
 
-    N_TOT_READS = 0
-    N_CACHED_ALN = 0 # read was found in cache
-    N_CACHED_NOTALN = 0 #read was found in 'not aligned' cache
-    N_COMPUTED_ALN = 0 # not in cache, aligned to at least 1 sequence with min cutoff
-    N_COMPUTED_NOTALN = 0 #not in cache, not aligned to any sequence with min cutoff
+    aln_stats, not_aln = process_fastq(fastq_input, variantCache, ref_names, refs, args, files_to_remove, output_directory, True)
+    info("Reads processed, now annotating fastq_output file: %s"%(fastq_output))
 
-    aln_matrix_loc = os.path.join(_ROOT, args.needleman_wunsch_aln_matrix_loc)
-    CRISPRessoShared.check_file(aln_matrix_loc)
-    aln_matrix = CRISPResso2Align.read_matrix(aln_matrix_loc)
 
-    pe_scaffold_dna_info = (0, None) #scaffold start loc, scaffold sequence
-    if args.prime_editing_pegRNA_scaffold_seq != "":
-        pe_scaffold_dna_info = get_pe_scaffold_search(refs['Prime-edited']['sequence'], args.prime_editing_pegRNA_extension_seq, args.prime_editing_pegRNA_scaffold_seq, args.prime_editing_pegRNA_scaffold_min_match_length)
-    not_aln = {} #cache for reads that don't align
 
     if fastq_input.endswith('.gz'):
-        fastq_input_handle=gzip.open(fastq_input, 'rt')
+        fastq_input_opener = lambda x: gzip.open(x, 'rt')
     else:
-        fastq_input_handle=open(fastq_input)
-
-    fastq_out_handle = gzip.open(fastq_output, 'wt')
-
-    fastq_id = fastq_input_handle.readline()
-    while(fastq_id):
-        #read through fastq in sets of 4
-        fastq_seq = fastq_input_handle.readline().strip()
-        fastq_plus = fastq_input_handle.readline().strip()
-        fastq_qual = fastq_input_handle.readline()
-
-        if (N_TOT_READS % 10000 == 0):
-            info("Processing reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
+        fastq_input_opener = open
 
-        N_TOT_READS+=1
+    with gzip.open(fastq_output, 'wt') as fastq_out_handle, fastq_input_opener(fastq_input) as fastq_input_handle:
 
-        #if the sequence has been seen and can't be aligned, skip it
-        if fastq_seq in not_aln:
-            N_CACHED_NOTALN += 1
-            fastq_out_handle.write(fastq_id+fastq_seq+"\n"+fastq_plus+not_aln[fastq_seq]+"\n"+fastq_qual) #not_aln[fastq_seq] is alignment: NA
-        elif fastq_seq in variantCache: #if the sequence is already associated with a variant in the variant cache, pull it out
-            N_CACHED_ALN+=1
-            variantCache[fastq_seq]['count'] += 1
-            fastq_out_handle.write(fastq_id+fastq_seq+"\n"+fastq_plus+variantCache[fastq_seq]['crispresso2_annotation']+"\n"+fastq_qual)
-
-        #otherwise, create a new variant object, and put it in the cache
-        else:
-            new_variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
-            if new_variant['best_match_score'] < 0:
-                N_COMPUTED_NOTALN+=1
+        fastq_id = fastq_input_handle.readline()
+        while(fastq_id):
+            #read through fastq in sets of 4
+            fastq_seq = fastq_input_handle.readline().strip()
+            fastq_plus = fastq_input_handle.readline().strip()
+            fastq_qual = fastq_input_handle.readline()
+
+            if fastq_seq in not_aln:
+                new_variant = not_aln[fastq_seq]
                 crispresso2_annotation = " ALN=NA" +\
-                        " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
-                        " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']]))
-                not_aln[fastq_seq] = crispresso2_annotation
+                            " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
+                            " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']]))
                 fastq_out_handle.write(fastq_id+fastq_seq+"\n"+fastq_plus+crispresso2_annotation+"\n"+fastq_qual)
-            else:
-                N_COMPUTED_ALN+=1
-                ins_inds = []
-                del_inds = []
-                sub_inds = []
-                edit_strings = []
-
-#                for idx, best_match_name in enumerate(best_match_names):
-                for idx, best_match_name in enumerate(new_variant['aln_ref_names']):
-                    payload=new_variant['variant_'+best_match_name]
+                fastq_id = fastq_input_handle.readline()
+                continue
 
-                    del_inds.append([str(x[0][0])+"("+str(x[1])+")" for x in zip(payload['deletion_coordinates'], payload['deletion_sizes'])])
+            if fastq_seq in variantCache:
+                new_variant = variantCache[fastq_seq]
 
-                    ins_vals = []
-                    for ins_coord,ins_size in zip(payload['insertion_coordinates'],payload['insertion_sizes']):
-                        ins_start = payload['ref_positions'].index(ins_coord[0])
-                        ins_vals.append(payload['aln_seq'][ins_start+1:ins_start+1+ins_size])
-                    ins_inds.append([str(x[0][0])+"("+str(x[1])+"+"+x[2]+")" for x in zip(payload['insertion_coordinates'], payload['insertion_sizes'], ins_vals)])
+            ins_inds = []
+            del_inds = []
+            sub_inds = []
+            edit_strings = []
 
-                    sub_inds.append(payload['substitution_positions'])
-                    edit_strings.append('D'+str(int(payload['deletion_n']))+';I'+str(int(payload['insertion_n']))+';S'+str(int(payload['substitution_n'])))
+            for idx, best_match_name in enumerate(new_variant['aln_ref_names']):
+                payload=new_variant['variant_'+best_match_name]
 
-                crispresso2_annotation = " ALN="+("&".join(new_variant['aln_ref_names'])) +\
-                        " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
-                        " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']])) +\
-                        " CLASS="+new_variant['class_name']+\
-                        " MODS="+("&".join(edit_strings))+\
-                        " DEL="+("&".join([';'.join(x) for x in del_inds])) +\
-                        " INS="+("&".join([';'.join(x) for x in ins_inds])) +\
-                        " SUB=" + ("&".join([';'.join([str(y) for y in x]) for x in sub_inds])) +\
-                        " ALN_REF=" + ('&'.join([new_variant['variant_'+name]['aln_ref'] for name in new_variant['aln_ref_names']])) +\
-                        " ALN_SEQ=" + ('&'.join([new_variant['variant_'+name]['aln_seq'] for name in new_variant['aln_ref_names']]))
+                del_inds.append([str(x[0][0])+"("+str(x[1])+")" for x in zip(payload['deletion_coordinates'], payload['deletion_sizes'])])
 
-                new_variant['crispresso2_annotation'] = crispresso2_annotation
+                ins_vals = []
+                for ins_coord,ins_size in zip(payload['insertion_coordinates'],payload['insertion_sizes']):
+                    ins_start = payload['ref_positions'].index(ins_coord[0])
+                    ins_vals.append(payload['aln_seq'][ins_start+1:ins_start+1+ins_size])
+                ins_inds.append([str(x[0][0])+"("+str(x[1])+"+"+x[2]+")" for x in zip(payload['insertion_coordinates'], payload['insertion_sizes'], ins_vals)])
 
-                fastq_out_handle.write(fastq_id+fastq_seq+"\n"+fastq_plus+crispresso2_annotation+"\n"+fastq_qual)
+                sub_inds.append(payload['substitution_positions'])
+                edit_strings.append('D'+str(int(payload['deletion_n']))+';I'+str(int(payload['insertion_n']))+';S'+str(int(payload['substitution_n'])))
 
-                variantCache[fastq_seq] = new_variant
+            crispresso2_annotation = " ALN="+("&".join(new_variant['aln_ref_names'])) +\
+                    " ALN_SCORES=" + ('&'.join([str(x) for x in new_variant['aln_scores']])) +\
+                    " ALN_DETAILS=" + ('&'.join([','.join([str(y) for y in x]) for x in new_variant['ref_aln_details']])) +\
+                    " CLASS="+new_variant['class_name']+\
+                    " MODS="+("&".join(edit_strings))+\
+                    " DEL="+("&".join([';'.join(x) for x in del_inds])) +\
+                    " INS="+("&".join([';'.join(x) for x in ins_inds])) +\
+                    " SUB=" + ("&".join([';'.join([str(y) for y in x]) for x in sub_inds])) +\
+                    " ALN_REF=" + ('&'.join([new_variant['variant_'+name]['aln_ref'] for name in new_variant['aln_ref_names']])) +\
+                    " ALN_SEQ=" + ('&'.join([new_variant['variant_'+name]['aln_seq'] for name in new_variant['aln_ref_names']]))
+            new_variant['crispresso2_annotation'] = crispresso2_annotation
+            fastq_out_handle.write(fastq_id+fastq_seq+"\n"+fastq_plus+crispresso2_annotation+"\n"+fastq_qual)
+            #last step of loop = read next line
+            fastq_id = fastq_input_handle.readline()
 
-        #last step of loop = read next line
-        fastq_id = fastq_input_handle.readline()
-    fastq_input_handle.close()
-    fastq_out_handle.close()
 
-    info("Finished reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
-    aln_stats = {"N_TOT_READS": N_TOT_READS,
-               "N_CACHED_ALN": N_CACHED_ALN,
-               "N_CACHED_NOTALN": N_CACHED_NOTALN,
-               "N_COMPUTED_ALN": N_COMPUTED_ALN,
-               "N_COMPUTED_NOTALN": N_COMPUTED_NOTALN,
-               }
-    return(aln_stats)
+    return aln_stats
 
-def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, variantCache, ref_names, refs, args):
+def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, variantCache, ref_names, refs, args, files_to_remove, output_directory):
     """
     process_fastq_write_out processes each of the reads contained in a fastq input file, given a cache of pre-computed variants. All reads are read in, analyzed, and written to output with annotation
 
@@ -736,66 +1156,37 @@ def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, vari
         ref_names: list of reference names
         refs: dictionary of sequences name>ref object
         args: crispresso2 args
+        files_to_remove: list of files to remove
+        output_directory: directory to write output tsv files to
     """
+    aln_stats, not_aln = process_fastq(fastq_input, variantCache, ref_names, refs, args, files_to_remove, output_directory, True)
+    info("Reads processed, now annotating fastq_output file: %s"%(bam_output))
 
-    N_TOT_READS = 0
-    N_CACHED_ALN = 0  # read was found in cache
-    N_CACHED_NOTALN = 0  # read was found in 'not aligned' cache
-    N_COMPUTED_ALN = 0  # not in cache, aligned to at least 1 sequence with min cutoff
-    N_COMPUTED_NOTALN = 0  # not in cache, not aligned to any sequence with min cutoff
 
     aln_matrix_loc = os.path.join(_ROOT, args.needleman_wunsch_aln_matrix_loc)
     CRISPRessoShared.check_file(aln_matrix_loc)
     aln_matrix = CRISPResso2Align.read_matrix(aln_matrix_loc)
 
-    pe_scaffold_dna_info = (0, None)  # scaffold start loc, scaffold sequence
-    if args.prime_editing_pegRNA_scaffold_seq != "":
-        pe_scaffold_dna_info = get_pe_scaffold_search(refs['Prime-edited']['sequence'], args.prime_editing_pegRNA_extension_seq, args.prime_editing_pegRNA_scaffold_seq, args.prime_editing_pegRNA_scaffold_min_match_length)
-    not_aln = {}  # cache for reads that don't align
-
     if fastq_input.endswith('.gz'):
-        fastq_input_handle = gzip.open(fastq_input, 'rt')
+        fastq_input_opener = lambda x: gzip.open(x, 'rt')
     else:
-        fastq_input_handle = open(fastq_input)
+        fastq_input_opener = open
 
     sam_out = bam_output+".sam"
-    sam_out_handle = open(sam_out, 'wt')
-
-    # write sam output header
-    sam_out_handle.write(bam_header)
-
-    fastq_id = fastq_input_handle.readline().strip()[1:]
-    while(fastq_id):
-        # read through fastq in sets of 4
-        fastq_seq = fastq_input_handle.readline().strip()
-        fastq_plus = fastq_input_handle.readline().strip()
-        fastq_qual = fastq_input_handle.readline().strip()
-
-        if (N_TOT_READS % 10000 == 0):
-            info("Processing reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
-
-        N_TOT_READS += 1
-
-        # if the sequence has been seen and can't be aligned, skip it
-        if fastq_seq in not_aln:
-            N_CACHED_NOTALN += 1
-            new_sam_entry = not_aln[fastq_seq][:]
-            new_sam_entry[0] = fastq_id
-            new_sam_entry[10] = fastq_qual
-            sam_out_handle.write("\t".join(new_sam_entry)+"\n")  # not_aln[fastq_seq] is alignment: NA
-        elif fastq_seq in variantCache:  # if the sequence is already associated with a variant in the variant cache, pull it out
-            N_CACHED_ALN += 1
-            variantCache[fastq_seq]['count'] += 1
-            new_sam_entry = variantCache[fastq_seq]['sam_entry'][:]
-            new_sam_entry[0] = fastq_id
-            new_sam_entry[10] = fastq_qual
-            sam_out_handle.write("\t".join(new_sam_entry)+"\n")  # write cached alignment with modified read id and qual
-
-        # otherwise, create a new variant object, and put it in the cache
-        else:
-            new_variant = get_new_variant_object(args, fastq_seq, refs, ref_names, aln_matrix, pe_scaffold_dna_info)
-            if new_variant['best_match_score'] < 0:
-                N_COMPUTED_NOTALN += 1
+
+    with open(sam_out, 'wt') as sam_out_handle, fastq_input_opener(fastq_input) as fastq_input_handle:
+        # write sam output header
+        sam_out_handle.write(bam_header)
+
+        fastq_id = fastq_input_handle.readline().strip()[1:]
+        while(fastq_id):
+            # read through fastq in sets of 4
+            fastq_seq = fastq_input_handle.readline().strip()
+            fastq_plus = fastq_input_handle.readline().strip()
+            fastq_qual = fastq_input_handle.readline().strip()
+
+            # if the sequence has been seen and can't be aligned, skip it
+            if fastq_seq in not_aln:
                 new_sam_entry = [
                     fastq_id,  # read id
                     '4',             # flag = unmapped 0x4
@@ -815,14 +1206,15 @@ def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, vari
                 new_sam_entry.append(crispresso_sam_optional_fields)
                 not_aln[fastq_seq] = new_sam_entry
                 sam_out_handle.write("\t".join(new_sam_entry)+"\n")  # write cached alignment with modified read id and qual
-            else:
-                N_COMPUTED_ALN += 1
+                continue
+
+            if fastq_seq in variantCache:
+                new_variant = variantCache[fastq_seq]
                 ins_inds = []
                 del_inds = []
                 sub_inds = []
                 edit_strings = []
 
-#                for idx, best_match_name in enumerate(best_match_names):
                 for idx, best_match_name in enumerate(new_variant['aln_ref_names']):
                     payload = new_variant['variant_'+best_match_name]
 
@@ -897,13 +1289,8 @@ def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, vari
 
                 sam_out_handle.write("\t".join(new_sam_entry)+"\n")  # write cached alignment with modified read id and qual
 
-                variantCache[fastq_seq] = new_variant
-
-        #last step of loop = read next line
-        fastq_id = fastq_input_handle.readline().strip()[1:]
-    fastq_input_handle.close()
-    sam_out_handle.close()
-
+            #last step of loop = read next line
+            fastq_id = fastq_input_handle.readline().strip()[1:]
     sort_and_index_cmd = 'samtools sort ' + sam_out + ' -o ' + bam_output + ' && samtools index ' + bam_output
     sort_bam_status = sb.call(sort_and_index_cmd, shell=True)
     if sort_bam_status:
@@ -914,31 +1301,10 @@ def process_single_fastq_write_bam_out(fastq_input, bam_output, bam_header, vari
     if not args.debug:
         os.remove(sam_out)
 
-    info("Finished reads; N_TOT_READS: %d N_COMPUTED_ALN: %d N_CACHED_ALN: %d N_COMPUTED_NOTALN: %d N_CACHED_NOTALN: %d"%(N_TOT_READS, N_COMPUTED_ALN, N_CACHED_ALN, N_COMPUTED_NOTALN, N_CACHED_NOTALN))
-    aln_stats = {"N_TOT_READS": N_TOT_READS,
-               "N_CACHED_ALN": N_CACHED_ALN,
-               "N_CACHED_NOTALN": N_CACHED_NOTALN,
-               "N_COMPUTED_ALN": N_COMPUTED_ALN,
-               "N_COMPUTED_NOTALN": N_COMPUTED_NOTALN,
-               }
+    info("Finished writing out to bam file: %s"%(bam_output))
     return(aln_stats)
 
 
-def split_interleaved_fastq(fastq_filename, output_filename_r1, output_filename_r2):
-    if fastq_filename.endswith('.gz'):
-        fastq_handle = gzip.open(fastq_filename, 'rt')
-    else:
-        fastq_handle=open(fastq_filename)
-
-    try:
-        fastq_splitted_outfile_r1 = gzip.open(output_filename_r1, 'wt')
-        fastq_splitted_outfile_r2 = gzip.open(output_filename_r2, 'wt')
-        [fastq_splitted_outfile_r1.write(line) if (i % 8 < 4) else fastq_splitted_outfile_r2.write(line) for i, line in enumerate(fastq_handle)]
-    except:
-        raise CRISPRessoShared.BadParameterException('Error in splitting read pairs from a single file')
-
-    return output_filename_r1, output_filename_r2
-
 
 def normalize_name(name, fastq_r1, fastq_r2, bam_input):
     """Normalize the name according to the inputs and clean it.
@@ -976,6 +1342,27 @@ def normalize_name(name, fastq_r1, fastq_r2, bam_input):
         return clean_name
 
 
+def to_numeric_ignore_columns(df, ignore_columns):
+    """Convert the columns of a dataframe to numeric, ignoring some columns.
+
+    Parameters
+    ----------
+    df : pandas.DataFrame
+        The dataframe to convert.
+    ignore_columns : list or set
+        The columns to ignore, i.e. not convert to numeric.
+
+    Returns
+    -------
+    pandas.DataFrame
+        The dataframe with the columns (except for ignore_columns) converted to numeric.
+    """
+    for col in df.columns:
+        if col not in ignore_columns:
+            df[col] = df[col].apply(pd.to_numeric, errors='raise')
+    return df
+
+
 def main():
 
     def print_stacktrace_if_debug():
@@ -1012,7 +1399,7 @@ def print_stacktrace_if_debug():
             sys.exit()
 
 
-        arg_parser = CRISPRessoShared.getCRISPRessoArgParser()
+        arg_parser = CRISPRessoShared.getCRISPRessoArgParser("Core")
         args = arg_parser.parse_args()
 
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
@@ -1045,7 +1432,12 @@ def print_stacktrace_if_debug():
             with open(log_filename, 'w+') as outfile:
                 outfile.write('CRISPResso version %s\n[Command used]:\n%s\n\n[Execution log]:\n' %(CRISPRessoShared.__version__, crispresso_cmd_to_write))
 
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPResso_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPResso_status.json')))
+
+        if C2PRO_INSTALLED:
+            debug(f'CRISPRessoPro v{CRISPRessoProVersion} installed', {'percent_complete': 0.5})
+        else:
+            debug(f'CRISPRessoPro not installed', {'percent_complete': 0.5})
 
         aln_matrix_loc = os.path.join(_ROOT, "EDNAFULL")
         CRISPRessoShared.check_file(aln_matrix_loc)
@@ -1079,7 +1471,7 @@ def print_stacktrace_if_debug():
 
         if args.amplicon_seq is None and args.auto is False:
             arg_parser.print_help()
-            raise CRISPRessoShared.BadParameterException('Please provide an amplicon sequence for analysis using the --amplicon_seq parameter.')
+            raise CRISPRessoShared.BadParameterException('Please provide an amplicon sequence for analysis using the --amplicon_seq parameter or use the --auto parameter to automatically assign amplicon to most common read.')
 
         if (args.needleman_wunsch_gap_open > 0):
             raise CRISPRessoShared.BadParameterException("Needleman Wunsch gap open penalty must be <= 0")
@@ -1087,6 +1479,13 @@ def print_stacktrace_if_debug():
             raise CRISPRessoShared.BadParameterException("Needleman Wunsch gap extend penalty must be <= 0")
 
 
+        if args.use_matplotlib or not C2PRO_INSTALLED:
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+        CRISPRessoPlot.setMatplotlibDefaults()
+
+
         #create output directory
         crispresso2_info_file = os.path.join(OUTPUT_DIRECTORY, 'CRISPResso2_info.json')
         crispresso2_info = {'running_info': {}, 'results': {'alignment_stats': {}, 'general_plots': {}}} #keep track of all information for this run to be pickled and saved at the end of the run
@@ -1219,6 +1618,7 @@ def rreplace(s, old, new):
 
             info('Inferring reference amplicon sequence..', {'percent_complete': 1})
 
+            check_fastp()
             auto_fastq_r1 = args.fastq_r1 #paths to fastq files for performing auto functions
             auto_fastq_r2 = args.fastq_r2
             if args.bam_input != "": #if input is a bam, create temp files with reads for processing here
@@ -1241,8 +1641,7 @@ def rreplace(s, old, new):
                         fastq_r1=auto_fastq_r1,
                         fastq_r2=auto_fastq_r2,
                         number_of_reads_to_consider=number_of_reads_to_consider,
-                        flash_command=args.flash_command,
-                        max_paired_end_reads_overlap=args.max_paired_end_reads_overlap,
+                        fastp_command=args.fastp_command,
                         min_paired_end_reads_overlap=args.min_paired_end_reads_overlap,
                         aln_matrix=aln_matrix,
                         needleman_wunsch_gap_open=args.needleman_wunsch_gap_open,
@@ -1270,8 +1669,7 @@ def rreplace(s, old, new):
                                     fastq_r1=auto_fastq_r1,
                                     fastq_r2=auto_fastq_r2,
                                     number_of_reads_to_consider=number_of_reads_to_consider,
-                                    flash_command=args.flash_command,
-                                    max_paired_end_reads_overlap=args.max_paired_end_reads_overlap,
+                                    fastp_command=args.fastp_command,
                                     min_paired_end_reads_overlap=args.min_paired_end_reads_overlap,
                                     exclude_bp_from_left=args.exclude_bp_from_left,
                                     exclude_bp_from_right=args.exclude_bp_from_right,
@@ -1309,7 +1707,7 @@ def rreplace(s, old, new):
                 )
 
         else: #not auto
-            amplicon_seq_arr = args.amplicon_seq.split(",")
+            amplicon_seq_arr = list(map(lambda x: x.upper(), args.amplicon_seq.split(",")))
             amplicon_name_arr = args.amplicon_name.split(",")
             #split on commas, only accept empty values
             amplicon_min_alignment_score_arr = [float(x) for x in args.amplicon_min_alignment_score.split(",") if x]
@@ -1348,6 +1746,8 @@ def rreplace(s, old, new):
 
 
         #Prime editing
+        if 'Prime-edited' in amplicon_name_arr:
+            raise CRISPRessoShared.BadParameterException("An amplicon named 'Prime-edited' must not be provided.")
         prime_editing_extension_seq_dna = "" #global var for the editing extension sequence for the scaffold quantification below
         prime_editing_edited_amp_seq = ""
         if args.prime_editing_pegRNA_extension_seq != "":
@@ -1380,8 +1780,8 @@ def rreplace(s, old, new):
                     raise CRISPRessoShared.BadParameterException(error_msg)
 
             ref_incentive = np.zeros(len(prime_editing_extension_seq_dna)+1, dtype=int)
-            f1, f2, fw_score=CRISPResso2Align.global_align(pegRNA_spacer_seq, prime_editing_extension_seq_dna, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.needleman_wunsch_gap_open, gap_extend=0,)
-            r1, r2, rv_score=CRISPResso2Align.global_align(pegRNA_spacer_seq, extension_seq_dna_top_strand, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.needleman_wunsch_gap_open, gap_extend=0,)
+            f1, f2, fw_score=CRISPResso2Align.global_align(pegRNA_spacer_seq, prime_editing_extension_seq_dna, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.prime_editing_gap_open_penalty, gap_extend=args.prime_editing_gap_extend_penalty,)
+            r1, r2, rv_score=CRISPResso2Align.global_align(pegRNA_spacer_seq, extension_seq_dna_top_strand, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.prime_editing_gap_open_penalty, gap_extend=args.prime_editing_gap_extend_penalty,)
             if rv_score > fw_score:
                 if args.debug:
                     info('pegRNA spacer vs extension_seq alignment:\nForward (correct orientation):\n%s\n%s\nScore: %s\nReverse (incorrect orientation):\n%s\n%s\nScore: %s' % (f1,f2,fw_score,r1,r2,rv_score))
@@ -1394,7 +1794,7 @@ def rreplace(s, old, new):
             #setting refs['Prime-edited']['sequence']
             #first, align the extension seq to the reference amplicon
             #we're going to consider the first reference only (so if multiple alleles exist at the editing position, this may get messy)
-            best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, amplicon_seq_arr[0],aln_matrix,args.needleman_wunsch_gap_open,0)
+            best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, amplicon_seq_arr[0],aln_matrix,args.prime_editing_gap_open_penalty,args.prime_editing_gap_extend_penalty)
             new_ref = s2[0:best_aln_start] + prime_editing_extension_seq_dna + s2[best_aln_end:]
             if args.debug:
                 info('Alignment between extension sequence and reference sequence: \n' + s1 + '\n' + s2)
@@ -1409,8 +1809,6 @@ def rreplace(s, old, new):
             if new_ref in amplicon_seq_arr:
                 raise CRISPRessoShared.BadParameterException('The calculated prime-edited amplicon is the same as the reference sequence.')
             amplicon_seq_arr.append(new_ref)
-            if 'Prime-edited' in amplicon_name_arr:
-                raise CRISPRessoShared.BadParameterException("An amplicon named 'Prime-edited' must not be provided.")
             amplicon_name_arr.append('Prime-edited')
             amplicon_quant_window_coordinates_arr.append('')
             prime_editing_edited_amp_seq = new_ref
@@ -1434,7 +1832,8 @@ def rreplace(s, old, new):
 
 
 
-        def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited_seq, prime_editing_extension_seq_dna, prime_editing_pegRNA_extension_quantification_window_size, nicking_qw_center, nicking_qw_size,aln_matrix,needleman_wunsch_gap_open,needleman_wunsch_gap_extend):
+        def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited_seq, prime_editing_extension_seq_dna, prime_editing_pegRNA_extension_quantification_window_size,
+                                     nicking_qw_center, nicking_qw_size,aln_matrix,needleman_wunsch_gap_open,needleman_wunsch_gap_extend, prime_editing_gap_open, prime_editing_gap_extend):
             """
             gets prime editing guide sequences for this amplicon
             this_amp_seq : sequence of this amplicon
@@ -1446,8 +1845,10 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
             nicking_qw_center: for the nicking guides, what is the quantification center (usually int(args.quantification_window_center.split(",")[0]))
             nicking_qw_size: for the nicking guides, what is the quantification size (usually int(args.quantification_window_size.split(",")[0]))
             aln_matrix: matrix specifying alignment substitution scores in the NCBI format
-            needleman_wunsch_gap_open: alignment penalty assignment used to determine similarity of two sequences
-            needleman_wunsch_gap_extend: alignment penalty assignment used to determine similarity of two sequences
+            needleman_wunsch_gap_open: alignment penalty assignment used to determine similarity of two sequences.
+            needleman_wunsch_gap_extend: alignment penalty assignment used to determine similarity of two sequences.
+            prime_editing_gap_open: alignment penalty assignment used to determine similarity of two pegRNA components. For prime editing the gap open is usually larger while the extension penalty is lower/zero to accomodate insertions of large sequences.
+            prime_editing_gap_extend: alignment penalty assignment used to determine similarity of two pegRNA components
             """
             pe_guides = []
             pe_orig_guide_seqs = []
@@ -1460,7 +1861,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
             #editing extension aligns to the prime-edited sequence only
             #if this is the prime edited sequence, add it directly
             if this_amp_seq == prime_editing_edited_amp_seq:
-                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, prime_editing_edited_amp_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, prime_editing_edited_amp_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                 pe_guides.append(best_aln_seq)
                 pe_orig_guide_seqs.append(args.prime_editing_pegRNA_extension_seq)
                 pe_guide_mismatches.append(best_aln_mismatches)
@@ -1470,7 +1871,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 pe_guide_plot_cut_points.append(False)
             #otherwise, clone the coordinates from the prime_editing_edited_amp_seq
             else:
-                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, prime_editing_edited_amp_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(prime_editing_extension_seq_dna, prime_editing_edited_amp_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                 match = re.search(best_aln_seq, prime_editing_edited_amp_seq)
                 pe_start_loc = match.start()
                 pe_end_loc = match.end()
@@ -1503,7 +1904,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
             #spacer is found in the first amplicon (unmodified ref), may be modified in the other amplicons
             #if this is the first sequence, add it directly
             if this_amp_seq == ref0_seq:
-                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(pegRNA_spacer_seq, ref0_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(pegRNA_spacer_seq, ref0_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                 pe_guides.append(best_aln_seq)
                 pe_orig_guide_seqs.append(args.prime_editing_pegRNA_spacer_seq)
                 pe_guide_mismatches.append(best_aln_mismatches)
@@ -1513,7 +1914,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 pe_guide_plot_cut_points.append(True)
             #otherwise, clone the coordinates from the ref0 amplicon
             else:
-                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(pegRNA_spacer_seq, ref0_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(pegRNA_spacer_seq, ref0_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                 match = re.search(best_aln_seq, ref0_seq)
                 r0_start_loc = match.start()
                 r0_end_loc = match.end()
@@ -1543,7 +1944,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 #nicking guide is found in the reverse_complement of the first amplicon, may be modified in the other amplicons
                 if this_amp_seq == ref0_seq:
                     rc_ref0_seq = CRISPRessoShared.reverse_complement(ref0_seq)
-                    best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(nicking_guide_seq, rc_ref0_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                    best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(nicking_guide_seq, rc_ref0_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                     if nicking_guide_seq not in rc_ref0_seq:
                         warn('The given prime editing nicking guide is not found in the reference sequence. Using the best match: ' + str(best_aln_seq))
                     pe_guides.append(best_aln_seq)
@@ -1557,7 +1958,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 else:
                     rc_ref0_seq = CRISPRessoShared.reverse_complement(ref0_seq)
                     rc_this_amp_seq = CRISPRessoShared.reverse_complement(this_amp_seq)
-                    best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(nicking_guide_seq, rc_ref0_seq,aln_matrix,needleman_wunsch_gap_open,0)
+                    best_aln_seq, best_aln_score, best_aln_mismatches, best_aln_start, best_aln_end, s1, s2 = CRISPRessoShared.get_best_aln_pos_and_mismatches(nicking_guide_seq, rc_ref0_seq,aln_matrix,prime_editing_gap_open,prime_editing_gap_extend)
                     match = re.search(best_aln_seq, rc_ref0_seq)
                     r0_start_loc = match.start()
                     r0_end_loc = match.end()
@@ -1586,16 +1987,9 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
         found_guide_seq = [False]*len(guides)
         found_coding_seq = [False]*len(coding_seqs)
 
-        max_amplicon_len = 0 #for flash
-        min_amplicon_len = 99**99 #for flash
-
         for idx, seq in enumerate(amplicon_seq_arr):
             this_seq = seq.strip().upper()
             this_seq_length = len(this_seq)
-            if this_seq_length > max_amplicon_len:
-                max_amplicon_len = this_seq_length
-            if this_seq_length < min_amplicon_len:
-                min_amplicon_len = this_seq_length
 
             this_name = 'Amplicon'+str(idx)
             if idx < len(amplicon_name_arr):
@@ -1628,14 +2022,14 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                     #for all amps in forward and reverse complement amps:
                     for amp_seq in [this_seq, CRISPRessoShared.reverse_complement(this_seq)]:
                         ref_incentive = np.zeros(len(amp_seq)+1, dtype=int)
-                        s1, s2, score=CRISPResso2Align.global_align(guide, amp_seq, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.needleman_wunsch_gap_open, gap_extend=args.needleman_wunsch_gap_extend,)
+                        s1, s2, score=CRISPResso2Align.global_align(guide, amp_seq, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.flexiguide_gap_open_penalty, gap_extend=args.flexiguide_gap_extend_penalty)
                         potential_guide = s1.strip("-")
                         if abs(len(potential_guide) - len(guide)) < 2: #if length of putative guide is off by less than 2, keep it (allows 1 gap)
                             loc = s1.find(potential_guide)
                             potential_ref = amp_seq[loc:loc+len(potential_guide)]
                             #realign to test for number of mismatches
                             ref_incentive = np.zeros(len(potential_ref)+1, dtype=int)
-                            sub_s1, sub_s2, sub_score=CRISPResso2Align.global_align(guide, potential_ref, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.needleman_wunsch_gap_open, gap_extend=args.needleman_wunsch_gap_extend,)
+                            sub_s1, sub_s2, sub_score=CRISPResso2Align.global_align(guide, potential_ref, matrix=aln_matrix, gap_incentive=ref_incentive, gap_open=args.flexiguide_gap_open_penalty, gap_extend=args.flexiguide_gap_extend_penalty)
                             mismatches = []
                             for i in range(len(sub_s1)):
                                 if sub_s1[i] != sub_s2[i]:
@@ -1665,13 +2059,14 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                             this_guide_plot_cut_points.append(False)
                         else:
                             this_guide_plot_cut_points.append(True)
-                info('Added %d guides with flexible matching\n\tOriginal flexiguides: %s\n\tFound guides: %s\n\tMismatch locations: %s'%(flexi_guide_count, str(args.flexiguide_seq.split(",")), str(flexi_guides), str(flexi_guide_mismatches)), {'percent_complete': 7})
+                debug('Added %d guides with flexible matching\n\tOriginal flexiguides: %s\n\tFound guides: %s\n\tMismatch locations: %s'%(flexi_guide_count, str(args.flexiguide_seq.split(",")), str(flexi_guides), str(flexi_guide_mismatches)), {'percent_complete': 7})
 
             if args.prime_editing_pegRNA_extension_seq:
                 nicking_qw_center = int(args.quantification_window_center.split(",")[0])
                 nicking_qw_size = int(args.quantification_window_size.split(",")[0])
                 pe_guides, pe_orig_guide_seqs, pe_guide_mismatches, pe_guide_names, pe_guide_qw_centers, pe_guide_qw_sizes, pe_guide_plot_cut_points = get_prime_editing_guides(this_seq, this_name, amplicon_seq_arr[0],
-                        prime_editing_edited_amp_seq, prime_editing_extension_seq_dna, args.prime_editing_pegRNA_extension_quantification_window_size, nicking_qw_center, nicking_qw_size, aln_matrix,args.needleman_wunsch_gap_open,args.needleman_wunsch_gap_extend)
+                        prime_editing_edited_amp_seq, prime_editing_extension_seq_dna, args.prime_editing_pegRNA_extension_quantification_window_size, nicking_qw_center, nicking_qw_size, aln_matrix,
+                        args.needleman_wunsch_gap_open, args.needleman_wunsch_gap_extend, args.prime_editing_gap_open_penalty, args.prime_editing_gap_extend_penalty)
                 this_guides.extend(pe_guides)
                 this_orig_guide_seqs.extend(pe_orig_guide_seqs)
                 this_guide_mismatches.extend(pe_guide_mismatches)
@@ -1682,7 +2077,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
 
 
             # Calculate cut sites for this reference
-            (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_include_idxs,
+            (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_sgRNA_include_idxs, this_include_idxs,
                 this_exclude_idxs) = CRISPRessoShared.get_amplicon_info_for_guides(this_seq, this_guides, this_guide_mismatches, this_guide_names, this_guide_qw_centers,
                 this_guide_qw_sizes, this_quant_window_coordinates, args.exclude_bp_from_left, args.exclude_bp_from_right, args.plot_window_size, this_guide_plot_cut_points, args.discard_guide_positions_overhanging_amplicon_edge)
 
@@ -1771,6 +2166,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                    'sgRNA_names': this_sgRNA_names,
                    'sgRNA_mismatches': this_sgRNA_mismatches,
                    'sgRNA_orig_sequences': this_sgRNA_orig_seqs,
+                   'sgRNA_include_idxs': this_sgRNA_include_idxs,
                    'contains_guide': this_contains_guide,
                    'contains_coding_seq': this_contains_coding_seq,
                    'exon_positions': this_exon_positions,
@@ -1830,7 +2226,7 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 break
 
         if clone_ref_name is not None:
-            for ref_name in ref_names:
+            for this_ref_idx, ref_name in enumerate(ref_names):
                 if clone_ref_name == ref_name:
                     continue
                 cut_points = refs[ref_name]['sgRNA_cut_points']
@@ -1954,17 +2350,15 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                     refs[ref_name]['contains_guide'] = refs[clone_ref_name]['contains_guide']
 
                 #quantification window coordinates override other options
-                if amplicon_quant_window_coordinates_arr[clone_ref_idx] != "":
-                    this_include_idxs = []
-                    these_coords = amplicon_quant_window_coordinates_arr[clone_ref_idx].split("_")
-                    for coord in these_coords:
-                        coordRE = re.match(r'^(\d+)-(\d+)$', coord)
-                        if coordRE:
-                            start = s1inds[int(coordRE.group(1))]
-                            end = s1inds[int(coordRE.group(2)) + 1]
-                            this_include_idxs.extend(range(start, end))
-                        else:
-                            raise NTException("Cannot parse analysis window coordinate '" + str(coord))
+                if amplicon_quant_window_coordinates_arr[clone_ref_idx] != "" and amplicon_quant_window_coordinates_arr[this_ref_idx] != '0':
+                    if amplicon_quant_window_coordinates_arr[this_ref_idx] != "":
+                        this_include_idxs = get_include_idxs_from_quant_window_coordinates(amplicon_quant_window_coordinates_arr[this_ref_idx])
+                    else:
+                        this_include_idxs = get_cloned_include_idxs_from_quant_window_coordinates(
+                            amplicon_quant_window_coordinates_arr[clone_ref_idx],
+                            s1inds.copy(),
+                        )
+
                     #subtract any indices in 'exclude_idxs' -- e.g. in case some of the cloned include_idxs were near the read ends (excluded)
                     this_exclude_idxs = sorted(list(set(refs[ref_name]['exclude_idxs'])))
                     this_include_idxs = sorted(list(set(np.setdiff1d(this_include_idxs, this_exclude_idxs))))
@@ -2093,190 +2487,133 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
                 raise CRISPRessoShared.BadParameterException('The option --split_interleaved_input is available only when a single fastq file is specified!')
             else:
                 info('Splitting paired end single fastq file into two files...')
-                args.fastq_r1, args.fastq_r2=split_interleaved_fastq(args.fastq_r1,
+                args.fastq_r1, args.fastq_r2 = CRISPRessoShared.split_interleaved_fastq(args.fastq_r1,
                     output_filename_r1=_jp(os.path.basename(args.fastq_r1.replace('.fastq', '')).replace('.gz', '')+'_splitted_r1.fastq.gz'),
                     output_filename_r2=_jp(os.path.basename(args.fastq_r1.replace('.fastq', '')).replace('.gz', '')+'_splitted_r2.fastq.gz'),)
-                files_to_remove+=[args.fastq_r1, args.fastq_r2]
-                N_READS_INPUT = N_READS_INPUT/2
+                files_to_remove += [args.fastq_r1, args.fastq_r2]
+                N_READS_INPUT /= 2
 
                 info('Done!', {'percent_complete': 4})
 
-        if args.min_average_read_quality>0 or args.min_single_bp_quality>0 or args.min_bp_quality_or_N>0:
-            if args.bam_input != '':
-                raise CRISPRessoShared.BadParameterException('The read filtering options are not available with bam input')
-            info('Filtering reads with average bp quality < %d and single bp quality < %d and replacing bases with quality < %d with N ...' % (args.min_average_read_quality, args.min_single_bp_quality, args.min_bp_quality_or_N))
-            min_av_quality = None
-            if args.min_average_read_quality > 0:
-                min_av_quality = args.min_average_read_quality
-
-            min_single_bp_quality = None
-            if args.min_single_bp_quality > 0:
-                min_single_bp_quality = args.min_single_bp_quality
-
-            min_bp_quality_or_N = None
-            if args.min_bp_quality_or_N > 0:
-                min_bp_quality_or_N = args.min_bp_quality_or_N
-
-            if args.fastq_r2!='':
-                output_filename_r1=_jp(os.path.basename(args.fastq_r1.replace('.fastq', '')).replace('.gz', '')+'_filtered.fastq.gz')
-                output_filename_r2=_jp(os.path.basename(args.fastq_r2.replace('.fastq', '')).replace('.gz', '')+'_filtered.fastq.gz')
-
-                from CRISPResso2 import filterFastqs
-                filterFastqs.filterFastqs(fastq_r1=args.fastq_r1, fastq_r2=args.fastq_r2, fastq_r1_out=output_filename_r1, fastq_r2_out=output_filename_r2, min_bp_qual_in_read=min_single_bp_quality, min_av_read_qual=min_av_quality, min_bp_qual_or_N=min_bp_quality_or_N)
-
-                args.fastq_r1 = output_filename_r1
-                args.fastq_r2 = output_filename_r2
-                files_to_remove += [output_filename_r1]
-                files_to_remove += [output_filename_r2]
-
-            else:
-                output_filename_r1=_jp(os.path.basename(args.fastq_r1.replace('.fastq', '')).replace('.gz', '')+'_filtered.fastq.gz')
-
-                from CRISPResso2 import filterFastqs
-                filterFastqs.filterFastqs(fastq_r1=args.fastq_r1, fastq_r1_out=output_filename_r1, min_bp_qual_in_read=min_single_bp_quality, min_av_read_qual=min_av_quality, min_bp_qual_or_N=min_bp_quality_or_N)
-
-                args.fastq_r1 = output_filename_r1
-                files_to_remove += [output_filename_r1]
-
-
         #Trim and merge reads
         if args.bam_input != '' and args.trim_sequences:
             raise CRISPRessoShared.BadParameterException('Read trimming options are not available with bam input')
         elif args.fastq_r1 != '' and args.fastq_r2 == '': #single end reads
             if not args.trim_sequences: #no trimming or merging required
-                output_forward_filename=args.fastq_r1
+                output_forward_filename = args.fastq_r1
             else:
+                check_fastp()
+                info('Trimming sequences with fastp...')
                 output_forward_filename=_jp('reads.trimmed.fq.gz')
-                #Trimming with trimmomatic
-                cmd='%s SE -phred33 %s  %s %s >>%s 2>&1'\
-                % (args.trimmomatic_command, args.fastq_r1,
-                   output_forward_filename,
-                   args.trimmomatic_options_string.replace('NexteraPE-PE.fa', 'TruSeq3-SE.fa'),
-                   log_filename)
-                #print cmd
-                TRIMMOMATIC_STATUS=sb.call(cmd, shell=True)
-
-                if TRIMMOMATIC_STATUS:
-                        raise CRISPRessoShared.TrimmomaticException('TRIMMOMATIC failed to run, please check the log file.')
-                crispresso2_info['trimmomatic_command'] = cmd
+                cmd = '{command} -i {r1} -o {out} {options} --json {json_report} --html {html_report} >> {log} 2>&1'.format(
+                    command=args.fastp_command,
+                    r1=args.fastq_r1,
+                    out=output_forward_filename,
+                    options=args.fastp_options_string,
+                    json_report=_jp('fastp_report.json'),
+                    html_report=_jp('fastp_report.html'),
+                    log=log_filename,
+                )
+                fastp_status = sb.call(cmd, shell=True)
+
+                if fastp_status:
+                    raise CRISPRessoShared.FastpException('FASTP failed to run, please check the log file.')
+                crispresso2_info['fastp_command'] = cmd
 
                 files_to_remove += [output_forward_filename]
 
-            processed_output_filename=output_forward_filename
+                info('Done!')
+
+            processed_output_filename = output_forward_filename
 
         elif args.fastq_r1 != '' and args.fastq_r2 != '':#paired end reads
+            processed_output_filename = _jp('out.extendedFrags.fastq.gz')
+            not_combined_1_filename = _jp('out.notCombined_1.fastq.gz')
+            not_combined_2_filename = _jp('out.notCombined_2.fastq.gz')
+            check_fastp()
+            info('Processing sequences with fastp...')
             if not args.trim_sequences:
-                output_forward_paired_filename=args.fastq_r1
-                output_reverse_paired_filename=args.fastq_r2
-            else:
-                info('Trimming sequences with Trimmomatic...')
-                output_forward_paired_filename=_jp('output_forward_paired.fq.gz')
-                output_forward_unpaired_filename=_jp('output_forward_unpaired.fq.gz')
-                output_reverse_paired_filename=_jp('output_reverse_paired.fq.gz')
-                output_reverse_unpaired_filename=_jp('output_reverse_unpaired.fq.gz')
-
-                #Trimming with trimmomatic
-                cmd='%s PE -phred33 %s  %s %s  %s  %s  %s %s >>%s 2>&1'\
-                    % (args.trimmomatic_command,
-                        args.fastq_r1, args.fastq_r2, output_forward_paired_filename,
-                        output_forward_unpaired_filename, output_reverse_paired_filename,
-                        output_reverse_unpaired_filename, args.trimmomatic_options_string, log_filename)
-                #print cmd
-                TRIMMOMATIC_STATUS=sb.call(cmd, shell=True)
-                if TRIMMOMATIC_STATUS:
-                    raise CRISPRessoShared.TrimmomaticException('TRIMMOMATIC failed to run, please check the log file.')
-                crispresso2_info['trimmomatic_command'] = cmd
-
-                files_to_remove += [output_forward_paired_filename]
-                files_to_remove += [output_reverse_paired_filename]
-
-                info('Done!', {'percent_complete': 6})
-
-            #for paired-end reads, merge them
-            info('Estimating average read length...')
-            if args.debug:
-                info('Checking average read length from ' + output_forward_paired_filename)
-            if get_n_reads_fastq(output_forward_paired_filename):
-                avg_read_length=get_avg_read_length_fastq(output_forward_paired_filename)
-                if args.debug:
-                    info('Average read length is ' + str(avg_read_length) + ' from ' + output_forward_paired_filename)
+                args.fastp_options_string += ' --disable_adapter_trimming --disable_trim_poly_g --disable_quality_filtering --disable_length_filtering'
             else:
-               raise CRISPRessoShared.NoReadsAfterQualityFilteringException('No reads survived the average or single bp quality filtering.')
-
-            #Merging with Flash
-            info('Merging paired sequences with Flash...')
-            min_overlap = args.min_paired_end_reads_overlap
-            max_overlap = args.max_paired_end_reads_overlap
-            if args.stringent_flash_merging:
-                expected_max_overlap=2*avg_read_length - min_amplicon_len
-                expected_min_overlap=2*avg_read_length - max_amplicon_len
-    #            print('avg read len: ' + str(avg_read_length))
-    #            print('expected_max_overlap' + str(expected_max_overlap))
-    #            print('expected_min_overlap' + str(expected_min_overlap))
-    #            print('min amplicon len:' + str(min_amplicon_len))
-    #            print('max amplicon len:' + str(max_amplicon_len))
-                indel_overlap_tolerance = 10 # magic number bound on how many bp inserted/deleted in ~90% of reads (for flash)
-                #max overlap is either the entire read (avg_read_length) or the expected amplicon length + indel tolerance
-                max_overlap = max(10, min(avg_read_length, expected_max_overlap+indel_overlap_tolerance))
-                #min overlap is either 4bp (as in crispresso1) or the expected amplicon length - indel tolerance
-                min_overlap = max(4, expected_min_overlap-indel_overlap_tolerance)
-    #            print('max_overlap: ' + str(max_overlap))
-    #            print('min_overlap: ' + str(min_overlap))
-                # if reads are longer than the amplicon, there is no way to tell flash to have them overlap like this..
-                if avg_read_length > min_amplicon_len:
-                    info('Warning: Reads are longer than amplicon.')
-                    min_overlap = avg_read_length-10
-                    max_overlap = 2*avg_read_length
-
-            output_prefix = "out"
-            if clean_file_prefix != "":
-                output_prefix = clean_file_prefix + "out"
-            cmd='%s "%s" "%s" --min-overlap %d --max-overlap %d --allow-outies -z -d %s -o %s >>%s 2>&1' %\
-            (args.flash_command,
-                 output_forward_paired_filename,
-                 output_reverse_paired_filename,
-                 min_overlap,
-                 max_overlap,
-                 OUTPUT_DIRECTORY,
-                 output_prefix,
-                 log_filename)
-
-            info('Running FLASH command: ' + cmd)
-            crispresso2_info['flash_command'] = cmd
-            FLASH_STATUS=sb.call(cmd, shell=True)
-            if FLASH_STATUS:
-                raise CRISPRessoShared.FlashException('Flash failed to run, please check the log file.')
-
-            flash_hist_filename=_jp('out.hist')
-            flash_histogram_filename=_jp('out.histogram')
-            flash_not_combined_1_filename=_jp('out.notCombined_1.fastq.gz')
-            flash_not_combined_2_filename=_jp('out.notCombined_2.fastq.gz')
-
-            processed_output_filename=_jp('out.extendedFrags.fastq.gz')
-            if os.path.isfile(processed_output_filename) is False:
-                raise CRISPRessoShared.FlashException('Flash failed to produce merged reads file, please check the log file.')
-
-            files_to_remove+=[processed_output_filename, flash_hist_filename, flash_histogram_filename,\
-                    flash_not_combined_1_filename, flash_not_combined_2_filename, _jp('out.hist.innie'), _jp('out.histogram.innie'), _jp('out.histogram.outie'), _jp('out.hist.outie')]
-
-            if (args.force_merge_pairs):
-                 old_flashed_filename = processed_output_filename
+                args.fastp_options_string += ' --detect_adapter_for_pe'
+
+            fastp_cmd = '{command} -i {r1} -I {r2} --merge --merged_out {out_merged} --unpaired1 {unpaired1} --unpaired2 {unpaired2} --overlap_len_require {min_overlap} --thread {num_threads} --json {json_report} --html {html_report} {options} >> {log} 2>&1'.format(
+                command=args.fastp_command,
+                r1=args.fastq_r1,
+                r2=args.fastq_r2,
+                out_merged=processed_output_filename,
+                unpaired1=not_combined_1_filename,
+                unpaired2=not_combined_2_filename,
+                min_overlap=args.min_paired_end_reads_overlap,
+                num_threads=n_processes,
+                json_report=_jp('fastp_report.json'),
+                html_report=_jp('fastp_report.html'),
+                options=args.fastp_options_string,
+                log=log_filename,
+            )
+            fastp_status = sb.call(fastp_cmd, shell=True)
+            if fastp_status:
+                raise CRISPRessoShared.FastpException('Fastp failed to run, please check the log file.')
+            crispresso2_info['running_info']['fastp_command'] = fastp_cmd
+
+            if not os.path.isfile(processed_output_filename):
+                raise CRISPRessoShared.FastpException('Fastp failed to produce merged reads file, please check the log file.')
+
+            info('Done!', {'percent_complete': 6})
+
+            files_to_remove += [
+                processed_output_filename,
+                not_combined_1_filename,
+                not_combined_2_filename,
+            ]
+
+            if args.force_merge_pairs:
                  new_merged_filename=_jp('out.forcemerged_uncombined.fastq.gz')
-                 num_reads_force_merged = CRISPRessoShared.force_merge_pairs(flash_not_combined_1_filename, flash_not_combined_2_filename, new_merged_filename)
+                 num_reads_force_merged = CRISPRessoShared.force_merge_pairs(not_combined_1_filename, not_combined_2_filename, new_merged_filename)
                  new_output_filename=_jp('out.forcemerged.fastq.gz')
-                 merge_command = "cat %s %s > %s"%(processed_output_filename, new_merged_filename, new_output_filename)
-                 MERGE_STATUS=sb.call(merge_command, shell=True)
-                 if MERGE_STATUS:
-                     raise FlashException('Force-merging read pairs failed to run, please check the log file.')
+                 merge_command = "cat {0} {1} > {2}".format(
+                     processed_output_filename, new_merged_filename, new_output_filename,
+                 )
+                 merge_status = sb.call(merge_command, shell=True)
+                 if merge_status:
+                     raise CRISPRessoShared.FastpException('Force-merging read pairs failed to run, please check the log file.')
+                 else:
+                     info(f'Forced {num_reads_force_merged} read paisr together.')
                  processed_output_filename = new_output_filename
 
-                 files_to_remove+=[new_merged_filename]
-                 files_to_remove+=[new_output_filename]
+                 files_to_remove += [new_merged_filename]
+                 files_to_remove += [new_output_filename]
                  if args.debug:
                      info('Wrote force-merged reads to ' + new_merged_filename)
 
             info('Done!', {'percent_complete': 7})
+        else: # single end reads with no trimming
+            processed_output_filename = args.fastq_r1
 
+        if args.min_average_read_quality > 0 or args.min_single_bp_quality > 0 or args.min_bp_quality_or_N > 0:
+            if args.bam_input != '':
+                raise CRISPRessoShared.BadParameterException('The read filtering options are not available with bam input')
+            info('Filtering reads with average bp quality < %d and single bp quality < %d and replacing bases with quality < %d with N ...' % (args.min_average_read_quality, args.min_single_bp_quality, args.min_bp_quality_or_N))
+            min_av_quality = None
+            if args.min_average_read_quality > 0:
+                min_av_quality = args.min_average_read_quality
+
+            min_single_bp_quality = None
+            if args.min_single_bp_quality > 0:
+                min_single_bp_quality = args.min_single_bp_quality
+
+            min_bp_quality_or_N = None
+            if args.min_bp_quality_or_N > 0:
+                min_bp_quality_or_N = args.min_bp_quality_or_N
+
+            output_filename_r1 = _jp(os.path.basename(
+                processed_output_filename.replace('.fastq', '')).replace('.gz', '') + '_filtered.fastq.gz',
+            )
+
+            from CRISPResso2 import filterFastqs
+            filterFastqs.filterFastqs(fastq_r1=processed_output_filename, fastq_r1_out=output_filename_r1, min_bp_qual_in_read=min_single_bp_quality, min_av_read_qual=min_av_quality, min_bp_qual_or_N=min_bp_quality_or_N)
+
+            processed_output_filename = output_filename_r1
 
         #count reads
         N_READS_AFTER_PREPROCESSING = 0
@@ -2294,18 +2631,23 @@ def get_prime_editing_guides(this_amp_seq, this_amp_name, ref0_seq, prime_edited
 
         #operates on variantCache
         if args.bam_input:
-            aln_stats = process_bam(args.bam_input, args.bam_chr_loc, crispresso2_info['bam_output'], variantCache, ref_names, refs, args)
+            aln_stats = process_bam(args.bam_input, args.bam_chr_loc, crispresso2_info['bam_output'], variantCache, ref_names, refs, args, files_to_remove, OUTPUT_DIRECTORY)
         elif args.fastq_output:
-            aln_stats = process_fastq_write_out(processed_output_filename, crispresso2_info['fastq_output'], variantCache, ref_names, refs, args)
+            aln_stats = process_fastq_write_out(processed_output_filename, crispresso2_info['fastq_output'], variantCache, ref_names, refs, args, files_to_remove, OUTPUT_DIRECTORY)
         elif args.bam_output:
             bam_header += '@PG\tID:crispresso2\tPN:crispresso2\tVN:'+CRISPRessoShared.__version__+'\tCL:"'+crispresso_cmd_to_write+'"\n'
-            aln_stats = process_single_fastq_write_bam_out(processed_output_filename, crispresso2_info['bam_output'], bam_header, variantCache, ref_names, refs, args)
+            aln_stats = process_single_fastq_write_bam_out(processed_output_filename, crispresso2_info['bam_output'], bam_header, variantCache, ref_names, refs, args, files_to_remove, OUTPUT_DIRECTORY)
         else:
-            aln_stats = process_fastq(processed_output_filename, variantCache, ref_names, refs, args)
+            aln_stats = process_fastq(processed_output_filename, variantCache, ref_names, refs, args, files_to_remove, OUTPUT_DIRECTORY)
+
+        #put empty sequence into cache
+        cache_fastq_seq = ''
+        variantCache[cache_fastq_seq] = {}
+        variantCache[cache_fastq_seq]['count'] = 0
 
         info('Done!', {'percent_complete': 20})
 
-        if args.prime_editing_pegRNA_scaffold_seq != "":
+        if args.prime_editing_pegRNA_scaffold_seq != "" and args.prime_editing_pegRNA_extension_seq != "":
             #introduce a new ref (that we didn't align to) called 'Scaffold Incorporated' -- copy it from the ref called 'prime-edited'
             new_ref = deepcopy(refs['Prime-edited'])
             new_ref['name'] = "Scaffold-incorporated"
@@ -3353,6 +3695,7 @@ def save_count_vectors_to_file(vectors, vectorNames, refSeq, filename):
             info('Making Plots...')
         ###############################################################################################################################################
         save_png = True
+        custom_config = CRISPRessoShared.check_custom_config(args)
         if args.suppress_report:
             save_png = False
 
@@ -3397,11 +3740,13 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
         else:
             process_pool = None
             process_futures = None
+
         plot = partial(
             CRISPRessoMultiProcessing.run_plot,
             num_processes=n_processes,
             process_pool=process_pool,
             process_futures=process_futures,
+            halt_on_plot_fail=args.halt_on_plot_fail,
         )
         ###############################################################################################################################################
         ### FIGURE 1: Alignment
@@ -3430,6 +3775,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                 'N_TOTAL': N_TOTAL,
                 'piechart_plot_root': plot_1b_root,
                 'barplot_plot_root': plot_1c_root,
+                'custom_colors': custom_config['colors'],
                 'save_png': save_png
             }
             crispresso2_info['results']['general_plots']['plot_1b_root'] = os.path.basename(plot_1b_root)
@@ -3565,23 +3911,26 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     mod_pcts.append(np.concatenate((['All_modifications'], np.array(all_indelsub_count_vectors[ref_name]).astype(float)/tot)))
                     mod_pcts.append(np.concatenate((['Total'], [counts_total[ref_name]]*refs[ref_name]['sequence_length'])))
                     colnames = ['Modification']+list(ref_seq)
-                    modification_percentage_summary_df = pd.DataFrame(mod_pcts, columns=colnames).apply(pd.to_numeric, errors='ignore')
+                    modification_percentage_summary_df = to_numeric_ignore_columns(pd.DataFrame(mod_pcts, columns=colnames), {'Modification'})
 
                     nuc_df_for_plot = df_nuc_pct_all.reset_index().rename(columns={'index':'Nucleotide'})
                     nuc_df_for_plot.insert(0, 'Batch', ref_name) #this function was designed for plottin batch... so just add a column in there to make it happy
                     mod_df_for_plot = modification_percentage_summary_df.copy()
                     mod_df_for_plot.insert(0, 'Batch', ref_name)
 
-                    plot_root = _jp('2a.'+ref_plot_name + 'Nucleotide_percentage_quilt')
+                    plot_root = _jp('2a.'+ ref_plot_name + 'Nucleotide_percentage_quilt')
+                    pro_output_name = os.path.join(OUTPUT_DIRECTORY, f'plot_{os.path.basename(plot_root)}.json')
                     plot_2a_input = {
                         'nuc_pct_df': nuc_df_for_plot,
                         'mod_pct_df': mod_df_for_plot,
-                        'fig_filename_root': plot_root,
+                        'fig_filename_root': pro_output_name if not args.use_matplotlib and C2PRO_INSTALLED else plot_root,
                         'save_also_png': save_png,
                         'sgRNA_intervals': sgRNA_intervals,
                         'sgRNA_names': sgRNA_names,
                         'sgRNA_mismatches': sgRNA_mismatches,
+                        'sgRNA_sequences': sgRNA_sequences,
                         'quantification_window_idxs': include_idxs_list,
+                        'custom_colors': custom_config["colors"],
                     }
                     debug('Plotting nucleotide quilt across amplicon')
                     plot(CRISPRessoPlot.plot_nucleotide_quilt, plot_2a_input)
@@ -3618,16 +3967,19 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         new_include_idx = []
                         for x in include_idxs_list:
                             new_include_idx += [x - new_sel_cols_start]
-                        plot_root = _jp('2b.'+ref_plot_name + 'Nucleotide_percentage_quilt_around_' + sgRNA_label)
+                        plot_root = _jp('2b.'+ ref_plot_name + 'Nucleotide_percentage_quilt_around_' + sgRNA_label)
+                        pro_output_name = os.path.join(OUTPUT_DIRECTORY, f'plot_{os.path.basename(plot_root)}.json')
                         plot_2b_input = {
                             'nuc_pct_df': nuc_df_for_plot.iloc[:, sel_cols],
                             'mod_pct_df': mod_df_for_plot.iloc[:, sel_cols],
-                            'fig_filename_root': plot_root,
+                            'fig_filename_root': pro_output_name if not args.use_matplotlib and C2PRO_INSTALLED else plot_root,
                             'save_also_png': save_png,
                             'sgRNA_intervals': new_sgRNA_intervals,
                             'sgRNA_names': sgRNA_names,
                             'sgRNA_mismatches': sgRNA_mismatches,
+                            'sgRNA_sequences': sgRNA_sequences,
                             'quantification_window_idxs': new_include_idx,
+                            'custom_colors': custom_config["colors"],
                         }
                         debug('Plotting nucleotide distribuition around {0} for {1}'.format(sgRNA_legend, ref_name))
                         plot(CRISPRessoPlot.plot_nucleotide_quilt, plot_2b_input)
@@ -3679,6 +4031,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     ),
                     'plot_root': plot_root,
                     'save_also_png': save_png,
+                    'ref_name': ref_name,
                 }
                 debug('Plotting indel size distribution for {0}'.format(ref_name))
                 plot(CRISPRessoPlot.plot_indel_size_distribution, plot_3a_input)
@@ -3760,6 +4113,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     'xmax_ins': xmax_ins,
                     'xmax_mut': xmax_mut,
                     'save_also_png': save_png,
+                    'custom_colors': custom_config["colors"],
+                    'ref_name' : ref_name,
                 }
                 debug('Plotting frequency deletions/insertions for {0}'.format(ref_name))
                 plot(CRISPRessoPlot.plot_frequency_deletions_insertions, plot_3b_input)
@@ -3805,6 +4160,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         ),
                     },
                     'plot_root': plot_root,
+                    'custom_colors': custom_config["colors"],
                     'save_also_png': save_png,
                 }
                 debug('Plotting amplication modifications for {0}'.format(ref_name))
@@ -3834,12 +4190,13 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'Mutation position distribution', ref_name,
                     ),
                     'plot_root': plot_root,
+                    'custom_colors': custom_config["colors"],
                     'save_also_png': save_png,
                 }
                 debug('Plotting modification frequency for {0}'.format(ref_name))
                 plot(CRISPRessoPlot.plot_modification_frequency, plot_4b_input)
                 crispresso2_info['results']['refs'][ref_name]['plot_4b_root'] = os.path.basename(plot_root)
-                crispresso2_info['results']['refs'][ref_name]['plot_4b_caption'] = "Figure 4b: Frequency of insertions (red), deletions (purple), and substitutions (green) across the entire amplicon, including modifications outside of the quantification window."
+                crispresso2_info['results']['refs'][ref_name]['plot_4b_caption'] = "Figure 4b: Frequency of insertions, deletions, and substitutions across the entire amplicon, including modifications outside of the quantification window."
                 crispresso2_info['results']['refs'][ref_name]['plot_4b_data'] = [('Modification frequency', os.path.basename(mod_count_filename))]
 
                 plot_root = _jp(
@@ -3862,6 +4219,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     ),
                     'ref_name': ref_name,
                     'plot_root': plot_root,
+                    'custom_colors': custom_config["colors"],
                     'save_also_png': save_png,
                 }
                 debug('Plotting quantification window locations for {0}'.format(ref_name))
@@ -3870,7 +4228,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     plot_4c_input,
                 )
                 crispresso2_info['results']['refs'][ref_name]['plot_4c_root'] = os.path.basename(plot_root)
-                crispresso2_info['results']['refs'][ref_name]['plot_4c_caption'] = "Figure 4c: Frequency of insertions (red), deletions (purple), and substitutions (green) across the entire amplicon, considering only modifications that overlap with the quantification window."
+                crispresso2_info['results']['refs'][ref_name]['plot_4c_caption'] = "Figure 4c: Frequency of insertions, deletions, and substitutions across the entire amplicon, considering only modifications that overlap with the quantification window."
                 crispresso2_info['results']['refs'][ref_name]['plot_4c_data'] = [('Modification frequency in quantification window', os.path.basename(quant_window_mod_count_filename))]
 
                 #Position dependent indels plot
@@ -3893,6 +4251,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     },
                     'plot_root': plot_root,
                     'save_also_png': save_png,
+                    'ref_name': ref_name,
                 }
                 debug('Plotting position dependent indel for {0}'.format(ref_name))
                 plot(
@@ -3918,6 +4277,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'n_total': N_TOTAL,
                         'ref_len': ref_len,
                         'ref_name': ref_names[0],
+                        'custom_colors': custom_config["colors"],
                         'save_also_png': save_png,
                     }
                     if ref_name == ref_names[0]:
@@ -3932,7 +4292,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         plot_4e_input['plot_title'] = 'Mutation position distribution in %s reads with reference to %s'%(ref_name, ref_names[0])
                         plot_4e_input['plot_root'] = plot_root
                         crispresso2_info['results']['refs'][ref_names[0]]['plot_4f_root'] = os.path.basename(plot_root)
-                        crispresso2_info['results']['refs'][ref_names[0]]['plot_4f_caption'] = "Figure 4f: Positions of modifications in HDR reads with respect to the reference sequence ("+ref_names[0]+"). Insertions: red, deletions: purple, substitutions: green. All modifications (including those outside the quantification window) are shown."
+                        crispresso2_info['results']['refs'][ref_names[0]]['plot_4f_caption'] = f"Figure 4f: Positions of modifications in HDR reads with respect to the reference sequence ({ref_names[0]}). All modifications (including those outside the quantification window) are shown."
                         crispresso2_info['results']['refs'][ref_names[0]]['plot_4f_data'] = []
                     debug('Plotting global modifications with respect to reference')
                     plot(
@@ -3952,7 +4312,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         for nuc in ['A', 'C', 'G', 'T', 'N', '-']:
                             nuc_pcts.append(np.concatenate(([ref_name_for_hdr, nuc], np.array(ref1_all_base_count_vectors[ref_name_for_hdr+"_"+nuc]).astype(float)/tot)))
                     colnames = ['Batch', 'Nucleotide']+list(refs[ref_names_for_hdr[0]]['sequence'])
-                    hdr_nucleotide_percentage_summary_df = pd.DataFrame(nuc_pcts, columns=colnames).apply(pd.to_numeric, errors='ignore')
+                    hdr_nucleotide_percentage_summary_df = to_numeric_ignore_columns(pd.DataFrame(nuc_pcts, columns=colnames), {'Batch', 'Nucleotide'})
 
                     mod_pcts = []
                     for ref_name_for_hdr in ref_names_for_hdr:
@@ -3964,10 +4324,12 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         mod_pcts.append(np.concatenate(([ref_name_for_hdr, 'All_modifications'], np.array(ref1_all_indelsub_count_vectors[ref_name_for_hdr]).astype(float)/tot)))
                         mod_pcts.append(np.concatenate(([ref_name_for_hdr, 'Total'], [counts_total[ref_name_for_hdr]]*refs[ref_names_for_hdr[0]]['sequence_length'])))
                     colnames = ['Batch', 'Modification']+list(refs[ref_names_for_hdr[0]]['sequence'])
-                    hdr_modification_percentage_summary_df = pd.DataFrame(mod_pcts, columns=colnames).apply(pd.to_numeric, errors='ignore')
+                    hdr_modification_percentage_summary_df = to_numeric_ignore_columns(pd.DataFrame(mod_pcts, columns=colnames), {'Batch', 'Modification'})
+
                     sgRNA_intervals = refs[ref_names_for_hdr[0]]['sgRNA_intervals']
                     sgRNA_names = refs[ref_names_for_hdr[0]]['sgRNA_names']
                     sgRNA_mismatches = refs[ref_names_for_hdr[0]]['sgRNA_mismatches']
+                    sgRNA_sequences = refs[ref_names_for_hdr[0]]['sgRNA_sequences']
 #                    include_idxs_list = refs[ref_names_for_hdr[0]]['include_idxs']
                     include_idxs_list = [] # the quantification windows may be different between different amplicons
 
@@ -3978,15 +4340,18 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     hdr_nucleotide_percentage_summary_df.rename(columns={'Batch':'Amplicon'}).to_csv(nuc_freq_filename,sep='\t',header=True,index=False)
 
                     plot_root = _jp('4g.HDR_nucleotide_percentage_quilt')
+                    pro_output_name = f'plot_{os.path.basename(plot_root)}.json'
                     plot_4g_input = {
                         'nuc_pct_df': hdr_nucleotide_percentage_summary_df,
                         'mod_pct_df': hdr_modification_percentage_summary_df,
-                        'fig_filename_root': plot_root,
+                        'fig_filename_root': f'{_jp(pro_output_name)}' if not args.use_matplotlib and C2PRO_INSTALLED else plot_root,
                         'save_also_png': save_png,
                         'sgRNA_intervals': sgRNA_intervals,
                         'quantification_window_idxs': include_idxs_list,
                         'sgRNA_names': sgRNA_names,
                         'sgRNA_mismatches': sgRNA_mismatches,
+                        'sgRNA_sequences': sgRNA_sequences,
+                        'custom_colors': custom_config["colors"],
                     }
                     debug('Plotting HDR nucleotide quilt')
                     plot(CRISPRessoPlot.plot_nucleotide_quilt, plot_4g_input)
@@ -4035,6 +4400,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             'ref_name': ref_name,
                             'plot_root': plot_root,
                             'save_also_png': save_png,
+                            'custom_colors': custom_config['colors'],
                         }
                         debug('Plotting frameshift analysis for {0}'.format(ref_name))
                         plot(CRISPRessoPlot.plot_frameshift_analysis, plot_5_input)
@@ -4060,6 +4426,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
 
                         'plot_root': plot_root,
                         'save_also_png': save_png,
+                        'ref_name': ref_name,
                     }
                     debug('Plotting frameshift frequency for {0}'.format(ref_name))
                     plot(CRISPRessoPlot.plot_frameshift_frequency, plot_6_input)
@@ -4087,12 +4454,14 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             ref_name,
                         ),
                         'plot_root': plot_root,
+                        'custom_colors': custom_config["colors"],
                         'save_also_png': save_png,
+                        'ref_name': ref_name,
                     }
                     debug('Plotting non-coding mutation positions for {0}'.format(ref_name))
                     plot(CRISPRessoPlot.plot_non_coding_mutations, plot_7_input)
                     crispresso2_info['results']['refs'][ref_name]['plot_7_root'] = os.path.basename(plot_root)
-                    crispresso2_info['results']['refs'][ref_name]['plot_7_caption'] = "Figure 7: Reads with insertions (red), deletions (purple), and substitutions (green) mapped to reference amplicon position exclusively in noncoding region/s (that is, without mutations affecting coding sequences). The predicted cleavage site is indicated by a vertical dashed line. Only sequence positions directly adjacent to insertions or directly affected by deletions or substitutions are plotted."
+                    crispresso2_info['results']['refs'][ref_name]['plot_7_caption'] = "Figure 7: Reads with insertions, deletions, and substitutions mapped to reference amplicon position exclusively in noncoding region/s (that is, without mutations affecting coding sequences). The predicted cleavage site is indicated by a vertical dashed line. Only sequence positions directly adjacent to insertions or directly affected by deletions or substitutions are plotted."
                     crispresso2_info['results']['refs'][ref_name]['plot_7_data'] = []
 
                     plot_root = _jp('8.'+ref_plot_name+'Potential_splice_sites_pie_chart')
@@ -4101,6 +4470,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'count_total': count_total,
                         'plot_root': plot_root,
                         'save_also_png': save_png,
+                        'ref_name': ref_name,
+                        'custom_colors': custom_config['colors'],
                     }
                     debug('Plotting potential splice sites')
                     plot(CRISPRessoPlot.plot_potential_splice_sites, plot_8_input)
@@ -4126,6 +4497,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'fig_filename_root': fig_filename_root,
                         'save_also_png': save_png,
                         'quantification_window_idxs': include_idxs_list,
+                        'custom_colors': custom_config['colors'],
+                        'ref_name': ref_name,
                     }
                     debug('Plotting substitutions across reference for {0}'.format(ref_name))
                     plot(CRISPRessoPlot.plot_subs_across_ref, plot_10a_input)
@@ -4143,7 +4516,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             'Substitution frequency\nin entire amplicon', ref_name,
                         ),
                         'fig_filename_root': fig_filename_root,
-                        'save_also_png': save_png
+                        'save_also_png': save_png,
+                        'custom_colors': custom_config['colors']
                     }
                     debug('Plotting substitution frequency barplot for {0}'.format(ref_name))
                     plot(CRISPRessoPlot.plot_sub_freqs, plot_10b_input)
@@ -4157,7 +4531,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'alt_nuc_counts': alt_nuc_counts,
                         'plot_title': get_plot_title_with_ref_name('Substitution frequency\nin quantification window', ref_name),
                         'fig_filename_root': fig_filename_root,
-                        'save_also_png': save_png
+                        'save_also_png': save_png,
+                        'custom_colors': custom_config['colors']
                     }
                     debug('Plotting substitution frequency barplot in quantification window for {0}'.format(ref_name))
                     plot(CRISPRessoPlot.plot_sub_freqs, plot_10c_input)
@@ -4236,12 +4611,24 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     new_sel_cols_start = cut_point - plot_half_window
                     for (int_start, int_end) in refs[ref_name]['sgRNA_intervals']:
                         new_sgRNA_intervals += [(int_start - new_sel_cols_start - 1, int_end - new_sel_cols_start - 1)]
+
+
+                    prepped_df_alleles, annotations, y_labels, insertion_dict, per_element_annot_kws, is_reference = CRISPRessoPlot.prep_alleles_table(
+                        df_to_plot,
+                        ref_seq_around_cut,
+                        args.max_rows_alleles_around_cut_to_plot,
+                        args.min_frequency_alleles_around_cut_to_plot,
+                    )
                     plot_9_input = {
                         'reference_seq': ref_seq_around_cut,
-                        'df_alleles': df_to_plot,
+                        'prepped_df_alleles': prepped_df_alleles,
+                        'annotations': annotations,
+                        'y_labels': y_labels,
+                        'insertion_dict': insertion_dict,
+                        'per_element_annot_kws': per_element_annot_kws,
+                        'is_reference': is_reference,
                         'fig_filename_root': fig_filename_root,
-                        'MIN_FREQUENCY': args.min_frequency_alleles_around_cut_to_plot,
-                        'MAX_N_ROWS': args.max_rows_alleles_around_cut_to_plot,
+                        'custom_colors': custom_config["colors"],
                         'SAVE_ALSO_PNG': save_png,
                         'plot_cut_point': plot_cut_point,
                         'sgRNA_intervals': new_sgRNA_intervals,
@@ -4250,7 +4637,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'annotate_wildtype_allele': args.annotate_wildtype_allele,
                     }
                     debug('Plotting allele distribution around cut for {0}'.format(ref_name))
-                    plot(CRISPRessoPlot.plot_alleles_table, plot_9_input)
+                    plot(CRISPRessoPlot.plot_alleles_table_prepped, plot_9_input)
                     crispresso2_info['results']['refs'][ref_name]['plot_9_roots'].append(os.path.basename(fig_filename_root))
                     crispresso2_info['results']['refs'][ref_name]['plot_9_captions'].append("Figure 9: Visualization of the distribution of identified alleles around the cleavage site for the " + sgRNA_legend + ". Nucleotides are indicated by unique colors (A = green; C = red; G = yellow; T = purple). Substitutions are shown in bold font. Red rectangles highlight inserted sequences. Horizontal dashed lines indicate deleted sequences. The vertical dashed line indicates the predicted cleavage site.")
                     crispresso2_info['results']['refs'][ref_name]['plot_9_datas'].append([('Allele frequency table', os.path.basename(allele_filename))])
@@ -4334,6 +4721,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             'conversion_nuc_from': args.conversion_nuc_from,
                             'fig_filename_root': fig_filename_root,
                             'save_also_png': save_png,
+                            'custom_colors': custom_config['colors'],
                         }
                         debug('Plotting conversion at {0}s around the {1} for {2}'.format(args.conversion_nuc_from, sgRNA_legend, ref_name))
                         plot(
@@ -4353,6 +4741,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             'conversion_nuc_from': args.conversion_nuc_from,
                             'fig_filename_root': fig_filename_root,
                             'save_also_png': save_png,
+                            'custom_colors': custom_config['colors']
                         }
                         debug('Plotting non-reference conversion at {0}s around the {1} for {2}'.format(args.conversion_nuc_from, sgRNA_legend, ref_name))
                         plot(
@@ -4374,7 +4763,8 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                             ),
                             'conversion_nuc_from': args.conversion_nuc_from,
                             'fig_filename_root': fig_filename_root,
-                            'save_also_png': save_png
+                            'save_also_png': save_png,
+                            'custom_colors': custom_config['colors']
                         }
                         debug('Plotting scaled non-reference conversion at {0}s around the {1} for {2}'.format(args.conversion_nuc_from, sgRNA_legend, ref_name))
                         plot(
@@ -4441,6 +4831,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                         'global_non_modified_non_frameshift': global_NON_MODIFIED_NON_FRAMESHIFT,
                         'plot_root': plot_root,
                         'save_also_png': save_png,
+                        'custom_colors': custom_config['colors'],
                     }
                     debug('Plotting global frameshift in-frame mutations pie chart', {'percent_complete': 90})
                     plot(
@@ -4480,6 +4871,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
                     'global_count_total': global_count_total,
                     'plot_root': plot_root,
                     'save_also_png': save_png,
+                    'custom_colors': custom_config['colors'],
                 }
                 debug('Plotting global potential splice sites pie chart', {'percent_complete': 94})
                 plot(CRISPRessoPlot.plot_impact_on_splice_sites, plot_8a_input)
@@ -4495,7 +4887,7 @@ def count_alternate_alleles(sub_base_vectors, ref_name, ref_sequence, ref_total_
             scaffold_insertion_sizes_filename = ""
             if args.prime_editing_pegRNA_scaffold_seq != "":
                 #first, define the sequence we are looking for (extension plus the first base(s) of the scaffold)
-                scaffold_dna_seq = CRISPRessoShared.reverse_complement(args.prime_editing_pegRNA_scaffold_seq)
+                scaffold_dna_seq = CRISPRessoShared.reverse_complement(args.prime_editing_pegRNA_scaffold_seq.upper().replace('U','T'))
                 pe_seq = refs['Prime-edited']['sequence']
                 pe_scaffold_dna_info = get_pe_scaffold_search(pe_seq, args.prime_editing_pegRNA_extension_seq, args.prime_editing_pegRNA_scaffold_seq, args.prime_editing_pegRNA_scaffold_min_match_length)
 
@@ -4509,7 +4901,7 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                     matches_scaffold_to_this_point = True
                     has_gaps_to_this_point = True
                     aln_seq_to_test = row['Aligned_Sequence'][pe_read_possible_scaffold_loc:].replace("-", "")
-                    while i < len(scaffold_seq) and (matches_scaffold_to_this_point or has_gaps_to_this_point):
+                    while i < len(scaffold_seq) and i < len(aln_seq_to_test) and (matches_scaffold_to_this_point or has_gaps_to_this_point):
                         if matches_scaffold_to_this_point and aln_seq_to_test[i] == scaffold_seq[i] :
                             num_match_scaffold += 1
                         else:
@@ -4538,7 +4930,7 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                     for nuc in ['A', 'C', 'G', 'T', 'N', '-']:
                         nuc_pcts.append(np.concatenate(([ref_name, nuc], np.array(ref1_all_base_count_vectors[ref_name+"_"+nuc]).astype(float)/tot)))
                 colnames = ['Batch', 'Nucleotide']+list(refs[ref_names[0]]['sequence'])
-                pe_nucleotide_percentage_summary_df = pd.DataFrame(nuc_pcts, columns=colnames).apply(pd.to_numeric,errors='ignore')
+                pe_nucleotide_percentage_summary_df = to_numeric_ignore_columns(pd.DataFrame(nuc_pcts, columns=colnames), {'Batch', 'Nucleotide'})
 
                 mod_pcts = []
                 for ref_name in ref_names_for_pe:
@@ -4548,45 +4940,50 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                     mod_pcts.append(np.concatenate(([ref_name, 'Deletions'], np.array(ref1_all_deletion_count_vectors[ref_name]).astype(float)/tot)))
                     mod_pcts.append(np.concatenate(([ref_name, 'Substitutions'], np.array(ref1_all_substitution_count_vectors[ref_name]).astype(float)/tot)))
                     mod_pcts.append(np.concatenate(([ref_name, 'All_modifications'], np.array(ref1_all_indelsub_count_vectors[ref_name]).astype(float)/tot)))
-                    mod_pcts.append(np.concatenate(([ref_name, 'Total'], [counts_total[ref_name]]*refs[ref_names_for_pe[0]]['sequence_length'])))
-                colnames = ['Batch', 'Modification']+list(refs[ref_names_for_pe[0]]['sequence'])
-                pe_modification_percentage_summary_df = pd.DataFrame(mod_pcts, columns=colnames).apply(pd.to_numeric,errors='ignore')
-                sgRNA_intervals = refs[ref_names_for_pe[0]]['sgRNA_intervals']
-                sgRNA_names = refs[ref_names_for_pe[0]]['sgRNA_names']
-                sgRNA_mismatches = refs[ref_names_for_pe[0]]['sgRNA_mismatches']
-                include_idxs_list = refs[ref_names_for_pe[0]]['include_idxs']
+                    mod_pcts.append(np.concatenate(([ref_name, 'Total'], [counts_total[ref_name]]*refs[ref_names[0]]['sequence_length'])))
+                colnames = ['Batch', 'Modification']+list(refs[ref_names[0]]['sequence'])
+                pe_modification_percentage_summary_df = to_numeric_ignore_columns(pd.DataFrame(mod_pcts, columns=colnames), {'Batch', 'Modification'})
+
+                sgRNA_intervals = refs[ref_names[0]]['sgRNA_intervals']
+                sgRNA_names = refs[ref_names[0]]['sgRNA_names']
+                sgRNA_mismatches = refs[ref_names[0]]['sgRNA_mismatches']
+                sgRNA_sequences = refs[ref_names[0]]['sgRNA_sequences']
+                include_idxs_list = refs[ref_names[0]]['include_idxs']
 
                 plot_root = _jp('11a.Prime_editing_nucleotide_percentage_quilt')
+                pro_output_name = f'plot_{os.path.basename(plot_root)}.json'
                 plot_11a_input = {
                     'nuc_pct_df': pe_nucleotide_percentage_summary_df,
                     'mod_pct_df': pe_modification_percentage_summary_df,
-                    'fig_filename_root': plot_root,
+                    'fig_filename_root': f'{_jp(pro_output_name)}' if not args.use_matplotlib and C2PRO_INSTALLED else plot_root,
                     'save_also_png': save_png,
                     'sgRNA_intervals': sgRNA_intervals,
                     'sgRNA_names': sgRNA_names,
                     'sgRNA_mismatches': sgRNA_mismatches,
+                    'sgRNA_sequences': sgRNA_sequences,
                     'quantification_window_idxs': include_idxs_list,
+                    'custom_colors': custom_config['colors']
                 }
                 info('Plotting prime editing nucleotide percentage quilt', {'percent_complete': 96})
                 plot(CRISPRessoPlot.plot_nucleotide_quilt, plot_11a_input)
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11a_root'] = os.path.basename(plot_root)
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11a_caption'] = "Figure 11a: Nucleotide distribution across all amplicons. At each base in the reference amplicon, the percentage of each base as observed in sequencing reads is shown (A = green; C = orange; G = yellow; T = purple). Black bars show the percentage of reads for which that base was deleted. Brown bars between bases show the percentage of reads having an insertion at that position."
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11a_data'] = [('Nucleotide frequency table for ' + ref_name, os.path.basename(crispresso2_info['results']['refs'][ref_name]['nuc_freq_filename'])) for ref_name in ref_names_for_pe]
-
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_roots'] = []
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_captions'] = []
-                crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_datas'] = []
-
-                pe_sgRNA_sequences = refs[ref_names_for_pe[0]]['sgRNA_sequences']
-                pe_sgRNA_orig_sequences = refs[ref_names_for_pe[0]]['sgRNA_orig_sequences']
-                pe_sgRNA_cut_points = refs[ref_names_for_pe[0]]['sgRNA_cut_points']
-                pe_sgRNA_plot_cut_points = refs[ref_names_for_pe[0]]['sgRNA_plot_cut_points']
-                pe_sgRNA_intervals = refs[ref_names_for_pe[0]]['sgRNA_intervals']
-                pe_sgRNA_names = refs[ref_names_for_pe[0]]['sgRNA_names']
-                pe_sgRNA_plot_idxs = refs[ref_names_for_pe[0]]['sgRNA_plot_idxs']
-                pe_sgRNA_mismatches = refs[ref_names_for_pe[0]]['sgRNA_mismatches']
-                pe_ref_len = refs[ref_names_for_pe[0]]['sequence_length']
-                pe_include_idxs_list = refs[ref_names_for_pe[0]]['include_idxs']
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11a_root'] = os.path.basename(plot_root)
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11a_caption'] = "Figure 11a: Nucleotide distribution across all amplicons. At each base in the reference amplicon, the percentage of each base as observed in sequencing reads is shown (A = green; C = orange; G = yellow; T = purple). Black bars show the percentage of reads for which that base was deleted. Brown bars between bases show the percentage of reads having an insertion at that position."
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11a_data'] = [('Nucleotide frequency table for ' + ref_name, os.path.basename(crispresso2_info['results']['refs'][ref_name]['nuc_freq_filename'])) for ref_name in ref_names_for_pe]
+
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_roots'] = []
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_captions'] = []
+                crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_datas'] = []
+
+                pe_sgRNA_sequences = refs[ref_names[0]]['sgRNA_sequences']
+                pe_sgRNA_orig_sequences = refs[ref_names[0]]['sgRNA_orig_sequences']
+                pe_sgRNA_cut_points = refs[ref_names[0]]['sgRNA_cut_points']
+                pe_sgRNA_plot_cut_points = refs[ref_names[0]]['sgRNA_plot_cut_points']
+                pe_sgRNA_intervals = refs[ref_names[0]]['sgRNA_intervals']
+                pe_sgRNA_names = refs[ref_names[0]]['sgRNA_names']
+                pe_sgRNA_plot_idxs = refs[ref_names[0]]['sgRNA_plot_idxs']
+                pe_sgRNA_mismatches = refs[ref_names[0]]['sgRNA_mismatches']
+                pe_ref_len = refs[ref_names[0]]['sequence_length']
+                pe_include_idxs_list = refs[ref_names[0]]['include_idxs']
 
                 for i in range(len(pe_sgRNA_cut_points)):
                     cut_point = pe_sgRNA_cut_points[i]
@@ -4609,27 +5006,30 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                     #get new intervals
                     new_sgRNA_intervals = []
                     #add annotations for each sgRNA (to be plotted on this sgRNA's plot)
-                    for (int_start, int_end) in refs[ref_names_for_pe[0]]['sgRNA_intervals']:
+                    for (int_start, int_end) in refs[ref_names[0]]['sgRNA_intervals']:
                         new_sgRNA_intervals += [(int_start - new_sel_cols_start, int_end - new_sel_cols_start)]
                     new_include_idx = []
                     for x in pe_include_idxs_list:
                         new_include_idx += [x - new_sel_cols_start]
                     plot_root = _jp('11b.Nucleotide_percentage_quilt_around_' + sgRNA_label)
+                    pro_output_name = f'plot_{os.path.basename(plot_root)}.json'
                     plot_11b_input = {
                         'nuc_pct_df': pe_nucleotide_percentage_summary_df.iloc[:, sel_cols],
                         'mod_pct_df': pe_modification_percentage_summary_df.iloc[:, sel_cols],
-                        'fig_filename_root': plot_root,
+                        'fig_filename_root': f'{_jp(pro_output_name)}' if not args.use_matplotlib and C2PRO_INSTALLED else plot_root,
                         'save_also_png': save_png,
                         'sgRNA_intervals': new_sgRNA_intervals,
                         'sgRNA_names': sgRNA_names,
                         'sgRNA_mismatches': sgRNA_mismatches,
+                        'sgRNA_sequences': sgRNA_sequences,
                         'quantification_window_idxs': new_include_idx,
+                        'custom_colors': custom_config['colors']
                     }
                     info('Plotting nucleotide quilt', {'percent_complete': 97})
                     plot(CRISPRessoPlot.plot_nucleotide_quilt, plot_11b_input)
-                    crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_roots'].append(os.path.basename(plot_root))
-                    crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_captions'].append('Figure 11b: Nucleotide distribution around the ' + sgRNA_legend + '.')
-                    crispresso2_info['results']['refs'][ref_names_for_pe[0]]['plot_11b_datas'].append([('Nucleotide frequency in quantification window for ' + ref_name, os.path.basename(crispresso2_info['results']['refs'][ref_name]['quant_window_nuc_freq_filename'])) for ref_name in ref_names_for_pe])
+                    crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_roots'].append(os.path.basename(plot_root))
+                    crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_captions'].append('Figure 11b: Nucleotide distribution around the ' + sgRNA_legend + '.')
+                    crispresso2_info['results']['refs'][ref_names[0]]['plot_11b_datas'].append([('Nucleotide frequency in quantification window for ' + ref_name, os.path.basename(crispresso2_info['results']['refs'][ref_name]['quant_window_nuc_freq_filename'])) for ref_name in ref_names_for_pe])
 
                 if args.prime_editing_pegRNA_scaffold_seq != "" and df_scaffold_insertion_sizes.shape[0] > 0 and df_scaffold_insertion_sizes['Num_match_scaffold'].max() > 0 and df_scaffold_insertion_sizes['Num_gaps'].max() > 0:
                     plot_root = _jp('11c.Prime_editing_scaffold_insertion_sizes')
@@ -4644,7 +5044,7 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                         plot_11c_input,
                     )
                     crispresso2_info['results']['general_plots']['plot_11c_root'] = os.path.basename(plot_root)
-                    crispresso2_info['results']['general_plots']['plot_11c_caption'] = "Figure 11a: Scaffold insertion lengths and deletion lengths in reads that contain a scaffold insertion. 'Length matching scaffold' shows the number of basepairs immediately after the pegRNA extension sequence that exactly match the scaffold RNA sequence. 'Insertion length' shows the length of the insertion immediately after the pegRNA extension sequence (including bases that do not match the scaffold sequence)."
+                    crispresso2_info['results']['general_plots']['plot_11c_caption'] = "Figure 11c: Scaffold insertion lengths and deletion lengths in reads that contain a scaffold insertion. 'Length matching scaffold' shows the number of basepairs immediately after the pegRNA extension sequence that exactly match the scaffold RNA sequence. 'Insertion length' shows the length of the insertion immediately after the pegRNA extension sequence (including bases that do not match the scaffold sequence)."
                     crispresso2_info['results']['general_plots']['plot_11c_data'] = [('Scaffold insertion alleles with insertion sizes', os.path.basename(scaffold_insertion_sizes_filename))]
 
         # join plotting pool
@@ -4654,7 +5054,12 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
                 debug('Plot pool results:')
                 for future in process_futures:
                     debug('future: ' + str(future))
-            future_results = [f.result() for f in process_futures] #required to raise exceptions thrown from within future
+            for future in process_futures:
+                try:
+                    future.result()
+                except Exception as e:
+                    logger.warning('Error in plot pool: %s' % e)
+                    logger.debug(traceback.format_exc())
             process_pool.shutdown()
 
         info('Done!')
@@ -4703,13 +5108,18 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
         crispresso2_info['running_info']['running_time'] = running_time
         crispresso2_info['running_info']['running_time_string'] = running_time_string
 
+        if args.disable_guardrails:
+            crispresso2_info['results']['guardrails_htmls'] = []
+        else:
+            crispresso2_info['results']['guardrails_htmls'] = CRISPRessoShared.safety_check(crispresso2_info, aln_stats, custom_config['guardrails'])
+
 
         if not args.suppress_report:
             if (args.place_report_in_output_folder):
                 report_name = _jp("CRISPResso2_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_report(crispresso2_info, report_name, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_report(crispresso2_info, report_name, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
@@ -4733,13 +5143,9 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
         print_stacktrace_if_debug()
         error('sgRNA error, please check your input.\n\nERROR: %s' % e)
         sys.exit(2)
-    except CRISPRessoShared.TrimmomaticException as e:
+    except CRISPRessoShared.FastpException as e:
         print_stacktrace_if_debug()
-        error('Trimming error, please check your input.\n\nERROR: %s' % e)
-        sys.exit(4)
-    except CRISPRessoShared.FlashException as e:
-        print_stacktrace_if_debug()
-        error('Merging error, please check your input.\n\nERROR: %s' % e)
+        error('Merging or trimming error, please check your input.\n\nERROR: %s' % e)
         sys.exit(5)
     except CRISPRessoShared.BadParameterException as e:
         print_stacktrace_if_debug()
@@ -4769,6 +5175,10 @@ def get_scaffold_len(row, scaffold_start_loc, scaffold_seq):
         print_stacktrace_if_debug()
         error('Filtering error, please check your input.\n\nERROR: %s' % e)
         sys.exit(13)
+    except CRISPRessoShared.PlotException as e:
+        print_stacktrace_if_debug()
+        error(e)
+        sys.exit(14)
     except Exception as e:
         print_stacktrace_if_debug()
         error('Unexpected error, please check your input.\n\nERROR: %s' % e)
diff --git a/CRISPResso2/CRISPRessoCOREResources.c b/CRISPResso2/CRISPRessoCOREResources.c
index 31e6fa6a..56039516 100644
--- a/CRISPResso2/CRISPRessoCOREResources.c
+++ b/CRISPResso2/CRISPRessoCOREResources.c
@@ -1,21 +1,21 @@
-/* Generated by Cython 3.0.0 */
+/* Generated by Cython 3.0.10 */
 
 /* BEGIN: Cython Metadata
 {
     "distutils": {
         "depends": [
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include/numpy/arrayobject.h",
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include/numpy/arrayscalars.h",
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include/numpy/ndarrayobject.h",
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h",
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include/numpy/ufuncobject.h"
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include/numpy/arrayobject.h",
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include/numpy/arrayscalars.h",
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include/numpy/ndarrayobject.h",
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h",
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include/numpy/ufuncobject.h"
         ],
         "extra_compile_args": [
             "-w",
             "-Ofast"
         ],
         "include_dirs": [
-            "/home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/core/include"
+            "/Users/cole/mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/core/include"
         ],
         "name": "CRISPResso2.CRISPRessoCOREResources",
         "sources": [
@@ -45,10 +45,15 @@ END: Cython Metadata */
 #elif PY_VERSION_HEX < 0x02070000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
     #error Cython requires Python 2.7+ or Python 3.3+.
 #else
-#define CYTHON_ABI "3_0_0"
+#if defined(CYTHON_LIMITED_API) && CYTHON_LIMITED_API
+#define __PYX_EXTRA_ABI_MODULE_NAME "limited"
+#else
+#define __PYX_EXTRA_ABI_MODULE_NAME ""
+#endif
+#define CYTHON_ABI "3_0_10" __PYX_EXTRA_ABI_MODULE_NAME
 #define __PYX_ABI_MODULE_NAME "_cython_" CYTHON_ABI
 #define __PYX_TYPE_MODULE_PREFIX __PYX_ABI_MODULE_NAME "."
-#define CYTHON_HEX_VERSION 0x030000F0
+#define CYTHON_HEX_VERSION 0x03000AF0
 #define CYTHON_FUTURE_DIVISION 1
 #include <stddef.h>
 #ifndef offsetof
@@ -81,6 +86,7 @@ END: Cython Metadata */
 #ifndef Py_HUGE_VAL
   #define Py_HUGE_VAL HUGE_VAL
 #endif
+#define __PYX_LIMITED_VERSION_HEX PY_VERSION_HEX
 #if defined(GRAALVM_PYTHON)
   /* For very preliminary testing purposes. Most variables are set the same as PyPy.
      The existence of this section does not imply that anything works or is even tested */
@@ -139,6 +145,8 @@ END: Cython Metadata */
   #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
     #define CYTHON_UPDATE_DESCRIPTOR_DOC 0
   #endif
+  #undef CYTHON_USE_FREELISTS
+  #define CYTHON_USE_FREELISTS 0
 #elif defined(PYPY_VERSION)
   #define CYTHON_COMPILING_IN_PYPY 1
   #define CYTHON_COMPILING_IN_CPYTHON 0
@@ -147,8 +155,9 @@ END: Cython Metadata */
   #define CYTHON_COMPILING_IN_NOGIL 0
   #undef CYTHON_USE_TYPE_SLOTS
   #define CYTHON_USE_TYPE_SLOTS 0
-  #undef CYTHON_USE_TYPE_SPECS
-  #define CYTHON_USE_TYPE_SPECS 0
+  #ifndef CYTHON_USE_TYPE_SPECS
+    #define CYTHON_USE_TYPE_SPECS 0
+  #endif
   #undef CYTHON_USE_PYTYPE_LOOKUP
   #define CYTHON_USE_PYTYPE_LOOKUP 0
   #if PY_VERSION_HEX < 0x03050000
@@ -199,7 +208,13 @@ END: Cython Metadata */
   #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
     #define CYTHON_UPDATE_DESCRIPTOR_DOC 0
   #endif
+  #undef CYTHON_USE_FREELISTS
+  #define CYTHON_USE_FREELISTS 0
 #elif defined(CYTHON_LIMITED_API)
+  #ifdef Py_LIMITED_API
+    #undef __PYX_LIMITED_VERSION_HEX
+    #define __PYX_LIMITED_VERSION_HEX Py_LIMITED_API
+  #endif
   #define CYTHON_COMPILING_IN_PYPY 0
   #define CYTHON_COMPILING_IN_CPYTHON 0
   #define CYTHON_COMPILING_IN_LIMITED_API 1
@@ -247,7 +262,7 @@ END: Cython Metadata */
   #undef CYTHON_USE_MODULE_STATE
   #define CYTHON_USE_MODULE_STATE 1
   #ifndef CYTHON_USE_TP_FINALIZE
-    #define CYTHON_USE_TP_FINALIZE 1
+    #define CYTHON_USE_TP_FINALIZE 0
   #endif
   #undef CYTHON_USE_DICT_VERSIONS
   #define CYTHON_USE_DICT_VERSIONS 0
@@ -256,7 +271,9 @@ END: Cython Metadata */
   #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
     #define CYTHON_UPDATE_DESCRIPTOR_DOC 0
   #endif
-#elif defined(PY_NOGIL)
+  #undef CYTHON_USE_FREELISTS
+  #define CYTHON_USE_FREELISTS 0
+#elif defined(Py_GIL_DISABLED) || defined(Py_NOGIL)
   #define CYTHON_COMPILING_IN_PYPY 0
   #define CYTHON_COMPILING_IN_CPYTHON 0
   #define CYTHON_COMPILING_IN_LIMITED_API 0
@@ -265,11 +282,17 @@ END: Cython Metadata */
   #ifndef CYTHON_USE_TYPE_SLOTS
     #define CYTHON_USE_TYPE_SLOTS 1
   #endif
+  #ifndef CYTHON_USE_TYPE_SPECS
+    #define CYTHON_USE_TYPE_SPECS 0
+  #endif
   #undef CYTHON_USE_PYTYPE_LOOKUP
   #define CYTHON_USE_PYTYPE_LOOKUP 0
   #ifndef CYTHON_USE_ASYNC_SLOTS
     #define CYTHON_USE_ASYNC_SLOTS 1
   #endif
+  #ifndef CYTHON_USE_PYLONG_INTERNALS
+    #define CYTHON_USE_PYLONG_INTERNALS 0
+  #endif
   #undef CYTHON_USE_PYLIST_INTERNALS
   #define CYTHON_USE_PYLIST_INTERNALS 0
   #ifndef CYTHON_USE_UNICODE_INTERNALS
@@ -277,8 +300,6 @@ END: Cython Metadata */
   #endif
   #undef CYTHON_USE_UNICODE_WRITER
   #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
   #ifndef CYTHON_AVOID_BORROWED_REFS
     #define CYTHON_AVOID_BORROWED_REFS 0
   #endif
@@ -290,11 +311,22 @@ END: Cython Metadata */
   #endif
   #undef CYTHON_FAST_THREAD_STATE
   #define CYTHON_FAST_THREAD_STATE 0
+  #undef CYTHON_FAST_GIL
+  #define CYTHON_FAST_GIL 0
+  #ifndef CYTHON_METH_FASTCALL
+    #define CYTHON_METH_FASTCALL 1
+  #endif
   #undef CYTHON_FAST_PYCALL
   #define CYTHON_FAST_PYCALL 0
+  #ifndef CYTHON_PEP487_INIT_SUBCLASS
+    #define CYTHON_PEP487_INIT_SUBCLASS 1
+  #endif
   #ifndef CYTHON_PEP489_MULTI_PHASE_INIT
     #define CYTHON_PEP489_MULTI_PHASE_INIT 1
   #endif
+  #ifndef CYTHON_USE_MODULE_STATE
+    #define CYTHON_USE_MODULE_STATE 0
+  #endif
   #ifndef CYTHON_USE_TP_FINALIZE
     #define CYTHON_USE_TP_FINALIZE 1
   #endif
@@ -302,6 +334,12 @@ END: Cython Metadata */
   #define CYTHON_USE_DICT_VERSIONS 0
   #undef CYTHON_USE_EXC_INFO_STACK
   #define CYTHON_USE_EXC_INFO_STACK 0
+  #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
+    #define CYTHON_UPDATE_DESCRIPTOR_DOC 1
+  #endif
+  #ifndef CYTHON_USE_FREELISTS
+    #define CYTHON_USE_FREELISTS 0
+  #endif
 #else
   #define CYTHON_COMPILING_IN_PYPY 0
   #define CYTHON_COMPILING_IN_CPYTHON 1
@@ -392,6 +430,9 @@ END: Cython Metadata */
   #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
     #define CYTHON_UPDATE_DESCRIPTOR_DOC 1
   #endif
+  #ifndef CYTHON_USE_FREELISTS
+    #define CYTHON_USE_FREELISTS 1
+  #endif
 #endif
 #if !defined(CYTHON_FAST_PYCCALL)
 #define CYTHON_FAST_PYCCALL  (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1)
@@ -469,6 +510,14 @@ END: Cython Metadata */
 #  define CYTHON_NCP_UNUSED CYTHON_UNUSED
 # endif
 #endif
+#ifndef CYTHON_USE_CPP_STD_MOVE
+  #if defined(__cplusplus) && (\
+    __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600))
+    #define CYTHON_USE_CPP_STD_MOVE 1
+  #else
+    #define CYTHON_USE_CPP_STD_MOVE 0
+  #endif
+#endif
 #define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None)
 #ifdef _MSC_VER
     #ifndef _MSC_STDINT_H_
@@ -568,59 +617,91 @@ END: Cython Metadata */
 #else
   #define __Pyx_BUILTIN_MODULE_NAME "builtins"
   #define __Pyx_DefaultClassType PyType_Type
-#if PY_VERSION_HEX >= 0x030B00A1
-    static CYTHON_INLINE PyCodeObject* __Pyx_PyCode_New(int a, int p, int k, int l, int s, int f,
+#if CYTHON_COMPILING_IN_LIMITED_API
+    static CYTHON_INLINE PyObject* __Pyx_PyCode_New(int a, int p, int k, int l, int s, int f,
                                                     PyObject *code, PyObject *c, PyObject* n, PyObject *v,
                                                     PyObject *fv, PyObject *cell, PyObject* fn,
                                                     PyObject *name, int fline, PyObject *lnos) {
-        PyObject *kwds=NULL, *argcount=NULL, *posonlyargcount=NULL, *kwonlyargcount=NULL;
-        PyObject *nlocals=NULL, *stacksize=NULL, *flags=NULL, *replace=NULL, *empty=NULL;
-        const char *fn_cstr=NULL;
-        const char *name_cstr=NULL;
-        PyCodeObject *co=NULL, *result=NULL;
+        PyObject *exception_table = NULL;
+        PyObject *types_module=NULL, *code_type=NULL, *result=NULL;
+        #if __PYX_LIMITED_VERSION_HEX < 0x030B0000
+        PyObject *version_info;
+        PyObject *py_minor_version = NULL;
+        #endif
+        long minor_version = 0;
         PyObject *type, *value, *traceback;
         PyErr_Fetch(&type, &value, &traceback);
-        if (!(kwds=PyDict_New())) goto end;
-        if (!(argcount=PyLong_FromLong(a))) goto end;
-        if (PyDict_SetItemString(kwds, "co_argcount", argcount) != 0) goto end;
-        if (!(posonlyargcount=PyLong_FromLong(p))) goto end;
-        if (PyDict_SetItemString(kwds, "co_posonlyargcount", posonlyargcount) != 0) goto end;
-        if (!(kwonlyargcount=PyLong_FromLong(k))) goto end;
-        if (PyDict_SetItemString(kwds, "co_kwonlyargcount", kwonlyargcount) != 0) goto end;
-        if (!(nlocals=PyLong_FromLong(l))) goto end;
-        if (PyDict_SetItemString(kwds, "co_nlocals", nlocals) != 0) goto end;
-        if (!(stacksize=PyLong_FromLong(s))) goto end;
-        if (PyDict_SetItemString(kwds, "co_stacksize", stacksize) != 0) goto end;
-        if (!(flags=PyLong_FromLong(f))) goto end;
-        if (PyDict_SetItemString(kwds, "co_flags", flags) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_code", code) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_consts", c) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_names", n) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_varnames", v) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_freevars", fv) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_cellvars", cell) != 0) goto end;
-        if (PyDict_SetItemString(kwds, "co_linetable", lnos) != 0) goto end;
-        if (!(fn_cstr=PyUnicode_AsUTF8AndSize(fn, NULL))) goto end;
-        if (!(name_cstr=PyUnicode_AsUTF8AndSize(name, NULL))) goto end;
-        if (!(co = PyCode_NewEmpty(fn_cstr, name_cstr, fline))) goto end;
-        if (!(replace = PyObject_GetAttrString((PyObject*)co, "replace"))) goto end;
-        if (!(empty = PyTuple_New(0))) goto end;
-        result = (PyCodeObject*) PyObject_Call(replace, empty, kwds);
+        #if __PYX_LIMITED_VERSION_HEX >= 0x030B0000
+        minor_version = 11;
+        #else
+        if (!(version_info = PySys_GetObject("version_info"))) goto end;
+        if (!(py_minor_version = PySequence_GetItem(version_info, 1))) goto end;
+        minor_version = PyLong_AsLong(py_minor_version);
+        Py_DECREF(py_minor_version);
+        if (minor_version == -1 && PyErr_Occurred()) goto end;
+        #endif
+        if (!(types_module = PyImport_ImportModule("types"))) goto end;
+        if (!(code_type = PyObject_GetAttrString(types_module, "CodeType"))) goto end;
+        if (minor_version <= 7) {
+            (void)p;
+            result = PyObject_CallFunction(code_type, "iiiiiOOOOOOiOO", a, k, l, s, f, code,
+                          c, n, v, fn, name, fline, lnos, fv, cell);
+        } else if (minor_version <= 10) {
+            result = PyObject_CallFunction(code_type, "iiiiiiOOOOOOiOO", a,p, k, l, s, f, code,
+                          c, n, v, fn, name, fline, lnos, fv, cell);
+        } else {
+            if (!(exception_table = PyBytes_FromStringAndSize(NULL, 0))) goto end;
+            result = PyObject_CallFunction(code_type, "iiiiiiOOOOOOOiOO", a,p, k, l, s, f, code,
+                          c, n, v, fn, name, name, fline, lnos, exception_table, fv, cell);
+        }
     end:
-        Py_XDECREF((PyObject*) co);
-        Py_XDECREF(kwds);
-        Py_XDECREF(argcount);
-        Py_XDECREF(posonlyargcount);
-        Py_XDECREF(kwonlyargcount);
-        Py_XDECREF(nlocals);
-        Py_XDECREF(stacksize);
-        Py_XDECREF(replace);
-        Py_XDECREF(empty);
+        Py_XDECREF(code_type);
+        Py_XDECREF(exception_table);
+        Py_XDECREF(types_module);
         if (type) {
             PyErr_Restore(type, value, traceback);
         }
         return result;
     }
+    #ifndef CO_OPTIMIZED
+    #define CO_OPTIMIZED 0x0001
+    #endif
+    #ifndef CO_NEWLOCALS
+    #define CO_NEWLOCALS 0x0002
+    #endif
+    #ifndef CO_VARARGS
+    #define CO_VARARGS 0x0004
+    #endif
+    #ifndef CO_VARKEYWORDS
+    #define CO_VARKEYWORDS 0x0008
+    #endif
+    #ifndef CO_ASYNC_GENERATOR
+    #define CO_ASYNC_GENERATOR 0x0200
+    #endif
+    #ifndef CO_GENERATOR
+    #define CO_GENERATOR 0x0020
+    #endif
+    #ifndef CO_COROUTINE
+    #define CO_COROUTINE 0x0080
+    #endif
+#elif PY_VERSION_HEX >= 0x030B0000
+  static CYTHON_INLINE PyCodeObject* __Pyx_PyCode_New(int a, int p, int k, int l, int s, int f,
+                                                    PyObject *code, PyObject *c, PyObject* n, PyObject *v,
+                                                    PyObject *fv, PyObject *cell, PyObject* fn,
+                                                    PyObject *name, int fline, PyObject *lnos) {
+    PyCodeObject *result;
+    PyObject *empty_bytes = PyBytes_FromStringAndSize("", 0);
+    if (!empty_bytes) return NULL;
+    result =
+      #if PY_VERSION_HEX >= 0x030C0000
+        PyUnstable_Code_NewWithPosOnlyArgs
+      #else
+        PyCode_NewWithPosOnlyArgs
+      #endif
+        (a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, name, fline, lnos, empty_bytes);
+    Py_DECREF(empty_bytes);
+    return result;
+  }
 #elif PY_VERSION_HEX >= 0x030800B2 && !CYTHON_COMPILING_IN_PYPY
   #define __Pyx_PyCode_New(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\
           PyCode_NewWithPosOnlyArgs(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
@@ -695,8 +776,13 @@ END: Cython Metadata */
   typedef PyObject *(*__Pyx_PyCFunctionFastWithKeywords) (PyObject *self, PyObject *const *args,
                                                           Py_ssize_t nargs, PyObject *kwnames);
 #else
-  #define __Pyx_PyCFunctionFast _PyCFunctionFast
-  #define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords
+  #if PY_VERSION_HEX >= 0x030d00A4
+  #  define __Pyx_PyCFunctionFast PyCFunctionFast
+  #  define __Pyx_PyCFunctionFastWithKeywords PyCFunctionFastWithKeywords
+  #else
+  #  define __Pyx_PyCFunctionFast _PyCFunctionFast
+  #  define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords
+  #endif
 #endif
 #if CYTHON_METH_FASTCALL
   #define __Pyx_METH_FASTCALL METH_FASTCALL
@@ -720,7 +806,32 @@ END: Cython Metadata */
   #define __Pyx_PY_VECTORCALL_ARGUMENTS_OFFSET  0
   #define __Pyx_PyVectorcall_NARGS(n)  ((Py_ssize_t)(n))
 #endif
-#if PY_VERSION_HEX < 0x030900B1
+#if PY_MAJOR_VERSION >= 0x030900B1
+#define __Pyx_PyCFunction_CheckExact(func)  PyCFunction_CheckExact(func)
+#else
+#define __Pyx_PyCFunction_CheckExact(func)  PyCFunction_Check(func)
+#endif
+#define __Pyx_CyOrPyCFunction_Check(func)  PyCFunction_Check(func)
+#if CYTHON_COMPILING_IN_CPYTHON
+#define __Pyx_CyOrPyCFunction_GET_FUNCTION(func)  (((PyCFunctionObject*)(func))->m_ml->ml_meth)
+#elif !CYTHON_COMPILING_IN_LIMITED_API
+#define __Pyx_CyOrPyCFunction_GET_FUNCTION(func)  PyCFunction_GET_FUNCTION(func)
+#endif
+#if CYTHON_COMPILING_IN_CPYTHON
+#define __Pyx_CyOrPyCFunction_GET_FLAGS(func)  (((PyCFunctionObject*)(func))->m_ml->ml_flags)
+static CYTHON_INLINE PyObject* __Pyx_CyOrPyCFunction_GET_SELF(PyObject *func) {
+    return (__Pyx_CyOrPyCFunction_GET_FLAGS(func) & METH_STATIC) ? NULL : ((PyCFunctionObject*)func)->m_self;
+}
+#endif
+static CYTHON_INLINE int __Pyx__IsSameCFunction(PyObject *func, void *cfunc) {
+#if CYTHON_COMPILING_IN_LIMITED_API
+    return PyCFunction_Check(func) && PyCFunction_GetFunction(func) == (PyCFunction) cfunc;
+#else
+    return PyCFunction_Check(func) && PyCFunction_GET_FUNCTION(func) == (PyCFunction) cfunc;
+#endif
+}
+#define __Pyx_IsSameCFunction(func, cfunc)   __Pyx__IsSameCFunction(func, cfunc)
+#if __PYX_LIMITED_VERSION_HEX < 0x030900B1
   #define __Pyx_PyType_FromModuleAndSpec(m, s, b)  ((void)m, PyType_FromSpecWithBases(s, b))
   typedef PyObject *(*__Pyx_PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *, size_t, PyObject *);
 #else
@@ -746,6 +857,8 @@ END: Cython Metadata */
   #define __Pyx_PyThreadState_Current PyThreadState_Get()
 #elif !CYTHON_FAST_THREAD_STATE
   #define __Pyx_PyThreadState_Current PyThreadState_GET()
+#elif PY_VERSION_HEX >= 0x030d00A1
+  #define __Pyx_PyThreadState_Current PyThreadState_GetUnchecked()
 #elif PY_VERSION_HEX >= 0x03060000
   #define __Pyx_PyThreadState_Current _PyThreadState_UncheckedGet()
 #elif PY_VERSION_HEX >= 0x03000000
@@ -821,7 +934,7 @@ static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
         }
     #endif
 #endif
-#if CYTHON_COMPILING_IN_CPYTHON || defined(_PyDict_NewPresized)
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030d0000 || defined(_PyDict_NewPresized)
 #define __Pyx_PyDict_NewPresized(n)  ((n <= 8) ? PyDict_New() : _PyDict_NewPresized(n))
 #else
 #define __Pyx_PyDict_NewPresized(n)  PyDict_New()
@@ -833,7 +946,7 @@ static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
   #define __Pyx_PyNumber_Divide(x,y)         PyNumber_Divide(x,y)
   #define __Pyx_PyNumber_InPlaceDivide(x,y)  PyNumber_InPlaceDivide(x,y)
 #endif
-#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX > 0x030600B4 && CYTHON_USE_UNICODE_INTERNALS
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX > 0x030600B4 && PY_VERSION_HEX < 0x030d0000 && CYTHON_USE_UNICODE_INTERNALS
 #define __Pyx_PyDict_GetItemStrWithError(dict, name)  _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash)
 static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStr(PyObject *dict, PyObject *name) {
     PyObject *res = __Pyx_PyDict_GetItemStrWithError(dict, name);
@@ -870,9 +983,14 @@ static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStrWithError(PyObject *dict,
   #define __Pyx_PyType_HasFeature(type, feature)  PyType_HasFeature(type, feature)
   #define __Pyx_PyObject_GetIterNextFunc(obj)  PyIter_Next
 #endif
+#if CYTHON_COMPILING_IN_LIMITED_API
+  #define __Pyx_SetItemOnTypeDict(tp, k, v) PyObject_GenericSetAttr((PyObject*)tp, k, v)
+#else
+  #define __Pyx_SetItemOnTypeDict(tp, k, v) PyDict_SetItem(tp->tp_dict, k, v)
+#endif
 #if CYTHON_USE_TYPE_SPECS && PY_VERSION_HEX >= 0x03080000
 #define __Pyx_PyHeapTypeObject_GC_Del(obj)  {\
-    PyTypeObject *type = Py_TYPE(obj);\
+    PyTypeObject *type = Py_TYPE((PyObject*)obj);\
     assert(__Pyx_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE));\
     PyObject_GC_Del(obj);\
     Py_DECREF(type);\
@@ -996,9 +1114,34 @@ static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStrWithError(PyObject *dict,
   #define __Pyx_SET_SIZE(obj, size) Py_SIZE(obj) = (size)
 #endif
 #if CYTHON_ASSUME_SAFE_MACROS
+  #define __Pyx_PySequence_ITEM(o, i) PySequence_ITEM(o, i)
   #define __Pyx_PySequence_SIZE(seq)  Py_SIZE(seq)
-#else
+  #define __Pyx_PyTuple_SET_ITEM(o, i, v) (PyTuple_SET_ITEM(o, i, v), (0))
+  #define __Pyx_PyList_SET_ITEM(o, i, v) (PyList_SET_ITEM(o, i, v), (0))
+  #define __Pyx_PyTuple_GET_SIZE(o) PyTuple_GET_SIZE(o)
+  #define __Pyx_PyList_GET_SIZE(o) PyList_GET_SIZE(o)
+  #define __Pyx_PySet_GET_SIZE(o) PySet_GET_SIZE(o)
+  #define __Pyx_PyBytes_GET_SIZE(o) PyBytes_GET_SIZE(o)
+  #define __Pyx_PyByteArray_GET_SIZE(o) PyByteArray_GET_SIZE(o)
+#else
+  #define __Pyx_PySequence_ITEM(o, i) PySequence_GetItem(o, i)
   #define __Pyx_PySequence_SIZE(seq)  PySequence_Size(seq)
+  #define __Pyx_PyTuple_SET_ITEM(o, i, v) PyTuple_SetItem(o, i, v)
+  #define __Pyx_PyList_SET_ITEM(o, i, v) PyList_SetItem(o, i, v)
+  #define __Pyx_PyTuple_GET_SIZE(o) PyTuple_Size(o)
+  #define __Pyx_PyList_GET_SIZE(o) PyList_Size(o)
+  #define __Pyx_PySet_GET_SIZE(o) PySet_Size(o)
+  #define __Pyx_PyBytes_GET_SIZE(o) PyBytes_Size(o)
+  #define __Pyx_PyByteArray_GET_SIZE(o) PyByteArray_Size(o)
+#endif
+#if __PYX_LIMITED_VERSION_HEX >= 0x030d00A1
+  #define __Pyx_PyImport_AddModuleRef(name) PyImport_AddModuleRef(name)
+#else
+  static CYTHON_INLINE PyObject *__Pyx_PyImport_AddModuleRef(const char *name) {
+      PyObject *module = PyImport_AddModule(name);
+      Py_XINCREF(module);
+      return module;
+  }
 #endif
 #if PY_MAJOR_VERSION >= 3
   #define PyIntObject                  PyLongObject
@@ -1078,7 +1221,7 @@ static CYTHON_INLINE float __PYX_NAN() {
 #endif
 
 #define __PYX_MARK_ERR_POS(f_index, lineno) \
-    { __pyx_filename = __pyx_f[f_index]; (void)__pyx_filename; __pyx_lineno = lineno; (void)__pyx_lineno; __pyx_clineno = __LINE__; (void)__pyx_clineno; }
+    { __pyx_filename = __pyx_f[f_index]; (void)__pyx_filename; __pyx_lineno = lineno; (void)__pyx_lineno; __pyx_clineno = __LINE__;  (void)__pyx_clineno; }
 #define __PYX_ERR(f_index, lineno, Ln_error) \
     { __PYX_MARK_ERR_POS(f_index, lineno) goto Ln_error; }
 
@@ -1161,9 +1304,10 @@ static CYTHON_INLINE int __Pyx_is_valid_index(Py_ssize_t i, Py_ssize_t limit) {
 #else
     #define __Pyx_sst_abs(value) ((value<0) ? -value : value)
 #endif
+static CYTHON_INLINE Py_ssize_t __Pyx_ssize_strlen(const char *s);
 static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject*);
 static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject*, Py_ssize_t* length);
-#define __Pyx_PyByteArray_FromString(s) PyByteArray_FromStringAndSize((const char*)s, strlen((const char*)s))
+static CYTHON_INLINE PyObject* __Pyx_PyByteArray_FromString(const char*);
 #define __Pyx_PyByteArray_FromStringAndSize(s, l) PyByteArray_FromStringAndSize((const char*)s, l)
 #define __Pyx_PyBytes_FromString        PyBytes_FromString
 #define __Pyx_PyBytes_FromStringAndSize PyBytes_FromStringAndSize
@@ -1191,24 +1335,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char*);
 #define __Pyx_PyByteArray_FromCString(s)   __Pyx_PyByteArray_FromString((const char*)s)
 #define __Pyx_PyStr_FromCString(s)     __Pyx_PyStr_FromString((const char*)s)
 #define __Pyx_PyUnicode_FromCString(s) __Pyx_PyUnicode_FromString((const char*)s)
-#if CYTHON_COMPILING_IN_LIMITED_API
-static CYTHON_INLINE size_t __Pyx_Py_UNICODE_strlen(const wchar_t *u)
-{
-    const wchar_t *u_end = u;
-    while (*u_end++) ;
-    return (size_t)(u_end - u - 1);
-}
-#else
-static CYTHON_INLINE size_t __Pyx_Py_UNICODE_strlen(const Py_UNICODE *u)
-{
-    const Py_UNICODE *u_end = u;
-    while (*u_end++) ;
-    return (size_t)(u_end - u - 1);
-}
-#endif
 #define __Pyx_PyUnicode_FromOrdinal(o)       PyUnicode_FromOrdinal((int)o)
-#define __Pyx_PyUnicode_FromUnicode(u)       PyUnicode_FromUnicode(u, __Pyx_Py_UNICODE_strlen(u))
-#define __Pyx_PyUnicode_FromUnicodeAndLength PyUnicode_FromUnicode
 #define __Pyx_PyUnicode_AsUnicode            PyUnicode_AsUnicode
 #define __Pyx_NewRef(obj) (Py_INCREF(obj), obj)
 #define __Pyx_Owned_Py_None(b) __Pyx_NewRef(Py_None)
@@ -1258,7 +1385,7 @@ static CYTHON_INLINE Py_hash_t __Pyx_PyIndex_AsHash_t(PyObject*);
   #endif
   typedef Py_ssize_t  __Pyx_compact_pylong;
   typedef size_t  __Pyx_compact_upylong;
-  #else  // Py < 3.12
+  #else
   #define __Pyx_PyLong_IsNeg(x)  (Py_SIZE(x) < 0)
   #define __Pyx_PyLong_IsNonNeg(x)  (Py_SIZE(x) >= 0)
   #define __Pyx_PyLong_IsZero(x)  (Py_SIZE(x) == 0)
@@ -1279,6 +1406,7 @@ static CYTHON_INLINE Py_hash_t __Pyx_PyIndex_AsHash_t(PyObject*);
   #endif
 #endif
 #if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII
+#include <string.h>
 static int __Pyx_sys_getdefaultencoding_not_ascii;
 static int __Pyx_init_sys_getdefaultencoding_params(void) {
     PyObject* sys;
@@ -1329,6 +1457,7 @@ static int __Pyx_init_sys_getdefaultencoding_params(void) {
 #else
 #define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_Decode(c_str, size, __PYX_DEFAULT_STRING_ENCODING, NULL)
 #if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT
+#include <string.h>
 static char* __PYX_DEFAULT_STRING_ENCODING;
 static int __Pyx_init_sys_getdefaultencoding_params(void) {
     PyObject* sys;
@@ -1376,7 +1505,7 @@ static const char *__pyx_filename;
 #if !defined(CYTHON_CCOMPLEX)
   #if defined(__cplusplus)
     #define CYTHON_CCOMPLEX 1
-  #elif (defined(_Complex_I) && !defined(_MSC_VER)) || ((defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_COMPLEX__))
+  #elif (defined(_Complex_I) && !defined(_MSC_VER)) || ((defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_COMPLEX__) && !defined(_MSC_VER))
     #define CYTHON_CCOMPLEX 1
   #else
     #define CYTHON_CCOMPLEX 0
@@ -1402,9 +1531,14 @@ static const char *__pyx_f[] = {
   "type.pxd",
 };
 /* #### Code section: utility_code_proto_before_types ### */
+/* ForceInitThreads.proto */
+#ifndef __PYX_FORCE_INIT_THREADS
+  #define __PYX_FORCE_INIT_THREADS 0
+#endif
+
 /* #### Code section: numeric_typedefs ### */
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":730
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":730
  * # in Cython to enable them only on the right systems.
  * 
  * ctypedef npy_int8       int8_t             # <<<<<<<<<<<<<<
@@ -1413,7 +1547,7 @@ static const char *__pyx_f[] = {
  */
 typedef npy_int8 __pyx_t_5numpy_int8_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":731
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":731
  * 
  * ctypedef npy_int8       int8_t
  * ctypedef npy_int16      int16_t             # <<<<<<<<<<<<<<
@@ -1422,7 +1556,7 @@ typedef npy_int8 __pyx_t_5numpy_int8_t;
  */
 typedef npy_int16 __pyx_t_5numpy_int16_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":732
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":732
  * ctypedef npy_int8       int8_t
  * ctypedef npy_int16      int16_t
  * ctypedef npy_int32      int32_t             # <<<<<<<<<<<<<<
@@ -1431,7 +1565,7 @@ typedef npy_int16 __pyx_t_5numpy_int16_t;
  */
 typedef npy_int32 __pyx_t_5numpy_int32_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":733
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":733
  * ctypedef npy_int16      int16_t
  * ctypedef npy_int32      int32_t
  * ctypedef npy_int64      int64_t             # <<<<<<<<<<<<<<
@@ -1440,7 +1574,7 @@ typedef npy_int32 __pyx_t_5numpy_int32_t;
  */
 typedef npy_int64 __pyx_t_5numpy_int64_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":737
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":737
  * #ctypedef npy_int128     int128_t
  * 
  * ctypedef npy_uint8      uint8_t             # <<<<<<<<<<<<<<
@@ -1449,7 +1583,7 @@ typedef npy_int64 __pyx_t_5numpy_int64_t;
  */
 typedef npy_uint8 __pyx_t_5numpy_uint8_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":738
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":738
  * 
  * ctypedef npy_uint8      uint8_t
  * ctypedef npy_uint16     uint16_t             # <<<<<<<<<<<<<<
@@ -1458,7 +1592,7 @@ typedef npy_uint8 __pyx_t_5numpy_uint8_t;
  */
 typedef npy_uint16 __pyx_t_5numpy_uint16_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":739
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":739
  * ctypedef npy_uint8      uint8_t
  * ctypedef npy_uint16     uint16_t
  * ctypedef npy_uint32     uint32_t             # <<<<<<<<<<<<<<
@@ -1467,7 +1601,7 @@ typedef npy_uint16 __pyx_t_5numpy_uint16_t;
  */
 typedef npy_uint32 __pyx_t_5numpy_uint32_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":740
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":740
  * ctypedef npy_uint16     uint16_t
  * ctypedef npy_uint32     uint32_t
  * ctypedef npy_uint64     uint64_t             # <<<<<<<<<<<<<<
@@ -1476,7 +1610,7 @@ typedef npy_uint32 __pyx_t_5numpy_uint32_t;
  */
 typedef npy_uint64 __pyx_t_5numpy_uint64_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":744
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":744
  * #ctypedef npy_uint128    uint128_t
  * 
  * ctypedef npy_float32    float32_t             # <<<<<<<<<<<<<<
@@ -1485,7 +1619,7 @@ typedef npy_uint64 __pyx_t_5numpy_uint64_t;
  */
 typedef npy_float32 __pyx_t_5numpy_float32_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":745
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":745
  * 
  * ctypedef npy_float32    float32_t
  * ctypedef npy_float64    float64_t             # <<<<<<<<<<<<<<
@@ -1494,7 +1628,7 @@ typedef npy_float32 __pyx_t_5numpy_float32_t;
  */
 typedef npy_float64 __pyx_t_5numpy_float64_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":754
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":754
  * # The int types are mapped a bit surprising --
  * # numpy.int corresponds to 'l' and numpy.long to 'q'
  * ctypedef npy_long       int_t             # <<<<<<<<<<<<<<
@@ -1503,7 +1637,7 @@ typedef npy_float64 __pyx_t_5numpy_float64_t;
  */
 typedef npy_long __pyx_t_5numpy_int_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":755
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":755
  * # numpy.int corresponds to 'l' and numpy.long to 'q'
  * ctypedef npy_long       int_t
  * ctypedef npy_longlong   longlong_t             # <<<<<<<<<<<<<<
@@ -1512,7 +1646,7 @@ typedef npy_long __pyx_t_5numpy_int_t;
  */
 typedef npy_longlong __pyx_t_5numpy_longlong_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":757
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":757
  * ctypedef npy_longlong   longlong_t
  * 
  * ctypedef npy_ulong      uint_t             # <<<<<<<<<<<<<<
@@ -1521,7 +1655,7 @@ typedef npy_longlong __pyx_t_5numpy_longlong_t;
  */
 typedef npy_ulong __pyx_t_5numpy_uint_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":758
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":758
  * 
  * ctypedef npy_ulong      uint_t
  * ctypedef npy_ulonglong  ulonglong_t             # <<<<<<<<<<<<<<
@@ -1530,7 +1664,7 @@ typedef npy_ulong __pyx_t_5numpy_uint_t;
  */
 typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":760
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":760
  * ctypedef npy_ulonglong  ulonglong_t
  * 
  * ctypedef npy_intp       intp_t             # <<<<<<<<<<<<<<
@@ -1539,7 +1673,7 @@ typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t;
  */
 typedef npy_intp __pyx_t_5numpy_intp_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":761
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":761
  * 
  * ctypedef npy_intp       intp_t
  * ctypedef npy_uintp      uintp_t             # <<<<<<<<<<<<<<
@@ -1548,7 +1682,7 @@ typedef npy_intp __pyx_t_5numpy_intp_t;
  */
 typedef npy_uintp __pyx_t_5numpy_uintp_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":763
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":763
  * ctypedef npy_uintp      uintp_t
  * 
  * ctypedef npy_double     float_t             # <<<<<<<<<<<<<<
@@ -1557,7 +1691,7 @@ typedef npy_uintp __pyx_t_5numpy_uintp_t;
  */
 typedef npy_double __pyx_t_5numpy_float_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":764
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":764
  * 
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t             # <<<<<<<<<<<<<<
@@ -1566,7 +1700,7 @@ typedef npy_double __pyx_t_5numpy_float_t;
  */
 typedef npy_double __pyx_t_5numpy_double_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":765
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":765
  * ctypedef npy_double     float_t
  * ctypedef npy_double     double_t
  * ctypedef npy_longdouble longdouble_t             # <<<<<<<<<<<<<<
@@ -1603,7 +1737,7 @@ static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(do
 
 /*--- Type declarations ---*/
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":767
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":767
  * ctypedef npy_longdouble longdouble_t
  * 
  * ctypedef npy_cfloat      cfloat_t             # <<<<<<<<<<<<<<
@@ -1612,7 +1746,7 @@ static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(do
  */
 typedef npy_cfloat __pyx_t_5numpy_cfloat_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":768
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":768
  * 
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t             # <<<<<<<<<<<<<<
@@ -1621,7 +1755,7 @@ typedef npy_cfloat __pyx_t_5numpy_cfloat_t;
  */
 typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":769
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":769
  * ctypedef npy_cfloat      cfloat_t
  * ctypedef npy_cdouble     cdouble_t
  * ctypedef npy_clongdouble clongdouble_t             # <<<<<<<<<<<<<<
@@ -1630,7 +1764,7 @@ typedef npy_cdouble __pyx_t_5numpy_cdouble_t;
  */
 typedef npy_clongdouble __pyx_t_5numpy_clongdouble_t;
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":771
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":771
  * ctypedef npy_clongdouble clongdouble_t
  * 
  * ctypedef npy_cdouble     complex_t             # <<<<<<<<<<<<<<
@@ -1834,7 +1968,20 @@ static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2, int eq
 static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int equals);
 
 /* fastcall.proto */
-#define __Pyx_Arg_VARARGS(args, i) PyTuple_GET_ITEM(args, i)
+#if CYTHON_AVOID_BORROWED_REFS
+    #define __Pyx_Arg_VARARGS(args, i) PySequence_GetItem(args, i)
+#elif CYTHON_ASSUME_SAFE_MACROS
+    #define __Pyx_Arg_VARARGS(args, i) PyTuple_GET_ITEM(args, i)
+#else
+    #define __Pyx_Arg_VARARGS(args, i) PyTuple_GetItem(args, i)
+#endif
+#if CYTHON_AVOID_BORROWED_REFS
+    #define __Pyx_Arg_NewRef_VARARGS(arg) __Pyx_NewRef(arg)
+    #define __Pyx_Arg_XDECREF_VARARGS(arg) Py_XDECREF(arg)
+#else
+    #define __Pyx_Arg_NewRef_VARARGS(arg) arg
+    #define __Pyx_Arg_XDECREF_VARARGS(arg)
+#endif
 #define __Pyx_NumKwargs_VARARGS(kwds) PyDict_Size(kwds)
 #define __Pyx_KwValues_VARARGS(args, nargs) NULL
 #define __Pyx_GetKwValue_VARARGS(kw, kwvalues, s) __Pyx_PyDict_GetItemStrWithError(kw, s)
@@ -1844,15 +1991,24 @@ static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int
     #define __Pyx_NumKwargs_FASTCALL(kwds) PyTuple_GET_SIZE(kwds)
     #define __Pyx_KwValues_FASTCALL(args, nargs) ((args) + (nargs))
     static CYTHON_INLINE PyObject * __Pyx_GetKwValue_FASTCALL(PyObject *kwnames, PyObject *const *kwvalues, PyObject *s);
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030d0000
+    CYTHON_UNUSED static PyObject *__Pyx_KwargsAsDict_FASTCALL(PyObject *kwnames, PyObject *const *kwvalues);
+  #else
     #define __Pyx_KwargsAsDict_FASTCALL(kw, kwvalues) _PyStack_AsDict(kwvalues, kw)
+  #endif
+    #define __Pyx_Arg_NewRef_FASTCALL(arg) arg  /* no-op, __Pyx_Arg_FASTCALL is direct and this needs
+                                                   to have the same reference counting */
+    #define __Pyx_Arg_XDECREF_FASTCALL(arg)
 #else
     #define __Pyx_Arg_FASTCALL __Pyx_Arg_VARARGS
     #define __Pyx_NumKwargs_FASTCALL __Pyx_NumKwargs_VARARGS
     #define __Pyx_KwValues_FASTCALL __Pyx_KwValues_VARARGS
     #define __Pyx_GetKwValue_FASTCALL __Pyx_GetKwValue_VARARGS
     #define __Pyx_KwargsAsDict_FASTCALL __Pyx_KwargsAsDict_VARARGS
+    #define __Pyx_Arg_NewRef_FASTCALL(arg) __Pyx_Arg_NewRef_VARARGS(arg)
+    #define __Pyx_Arg_XDECREF_FASTCALL(arg) __Pyx_Arg_XDECREF_VARARGS(arg)
 #endif
-#if CYTHON_COMPILING_IN_CPYTHON
+#if CYTHON_COMPILING_IN_CPYTHON && CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
 #define __Pyx_ArgsSlice_VARARGS(args, start, stop) __Pyx_PyTuple_FromArray(&__Pyx_Arg_VARARGS(args, start), stop - start)
 #define __Pyx_ArgsSlice_FASTCALL(args, start, stop) __Pyx_PyTuple_FromArray(&__Pyx_Arg_FASTCALL(args, start), stop - start)
 #else
@@ -1880,7 +2036,11 @@ static CYTHON_INLINE int __Pyx_PyList_Append(PyObject* list, PyObject* x) {
     Py_ssize_t len = Py_SIZE(list);
     if (likely(L->allocated > len) & likely(len > (L->allocated >> 1))) {
         Py_INCREF(x);
+        #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030d0000
+        L->ob_item[len] = x;
+        #else
         PyList_SET_ITEM(list, len, x);
+        #endif
         __Pyx_SET_SIZE(list, len + 1);
         return 0;
     }
@@ -1926,7 +2086,7 @@ static CYTHON_INLINE int __Pyx_PySet_ContainsTF(PyObject* key, PyObject* set, in
 
 /* ListExtend.proto */
 static CYTHON_INLINE int __Pyx_PyList_Extend(PyObject* L, PyObject* v) {
-#if CYTHON_COMPILING_IN_CPYTHON
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030d0000
     PyObject* none = _PyList_Extend((PyListObject*)L, v);
     if (unlikely(!none))
         return -1;
@@ -1969,7 +2129,7 @@ static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args,
 #if !CYTHON_VECTORCALL
 #if PY_VERSION_HEX >= 0x03080000
   #include "frameobject.h"
-#if PY_VERSION_HEX >= 0x030b00a6
+#if PY_VERSION_HEX >= 0x030b00a6 && !CYTHON_COMPILING_IN_LIMITED_API
   #ifndef Py_BUILD_CORE
     #define Py_BUILD_CORE 1
   #endif
@@ -2061,22 +2221,22 @@ static CYTHON_INLINE int __Pyx_IterFinish(void);
 static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected);
 
 /* TypeImport.proto */
-#ifndef __PYX_HAVE_RT_ImportType_proto_3_0_0
-#define __PYX_HAVE_RT_ImportType_proto_3_0_0
+#ifndef __PYX_HAVE_RT_ImportType_proto_3_0_10
+#define __PYX_HAVE_RT_ImportType_proto_3_0_10
 #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
 #include <stdalign.h>
 #endif
 #if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || __cplusplus >= 201103L
-#define __PYX_GET_STRUCT_ALIGNMENT_3_0_0(s) alignof(s)
+#define __PYX_GET_STRUCT_ALIGNMENT_3_0_10(s) alignof(s)
 #else
-#define __PYX_GET_STRUCT_ALIGNMENT_3_0_0(s) sizeof(void*)
+#define __PYX_GET_STRUCT_ALIGNMENT_3_0_10(s) sizeof(void*)
 #endif
-enum __Pyx_ImportType_CheckSize_3_0_0 {
-   __Pyx_ImportType_CheckSize_Error_3_0_0 = 0,
-   __Pyx_ImportType_CheckSize_Warn_3_0_0 = 1,
-   __Pyx_ImportType_CheckSize_Ignore_3_0_0 = 2
+enum __Pyx_ImportType_CheckSize_3_0_10 {
+   __Pyx_ImportType_CheckSize_Error_3_0_10 = 0,
+   __Pyx_ImportType_CheckSize_Warn_3_0_10 = 1,
+   __Pyx_ImportType_CheckSize_Ignore_3_0_10 = 2
 };
-static PyTypeObject *__Pyx_ImportType_3_0_0(PyObject* module, const char *module_name, const char *class_name, size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_0_0 check_size);
+static PyTypeObject *__Pyx_ImportType_3_0_10(PyObject* module, const char *module_name, const char *class_name, size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_0_10 check_size);
 #endif
 
 /* Import.proto */
@@ -2107,7 +2267,22 @@ static PyTypeObject* __Pyx_FetchCommonTypeFromSpec(PyObject *module, PyType_Spec
 #endif
 
 /* PyMethodNew.proto */
-#if PY_MAJOR_VERSION >= 3
+#if CYTHON_COMPILING_IN_LIMITED_API
+static PyObject *__Pyx_PyMethod_New(PyObject *func, PyObject *self, PyObject *typ) {
+    PyObject *typesModule=NULL, *methodType=NULL, *result=NULL;
+    CYTHON_UNUSED_VAR(typ);
+    if (!self)
+        return __Pyx_NewRef(func);
+    typesModule = PyImport_ImportModule("types");
+    if (!typesModule) return NULL;
+    methodType = PyObject_GetAttrString(typesModule, "MethodType");
+    Py_DECREF(typesModule);
+    if (!methodType) return NULL;
+    result = PyObject_CallFunctionObjArgs(methodType, func, self, NULL);
+    Py_DECREF(methodType);
+    return result;
+}
+#elif PY_MAJOR_VERSION >= 3
 static PyObject *__Pyx_PyMethod_New(PyObject *func, PyObject *self, PyObject *typ) {
     CYTHON_UNUSED_VAR(typ);
     if (!self)
@@ -2131,7 +2306,7 @@ static CYTHON_INLINE PyObject *__Pyx_PyVectorcall_FastCallDict(PyObject *func, _
 #define __Pyx_CYFUNCTION_COROUTINE     0x08
 #define __Pyx_CyFunction_GetClosure(f)\
     (((__pyx_CyFunctionObject *) (f))->func_closure)
-#if PY_VERSION_HEX < 0x030900B1
+#if PY_VERSION_HEX < 0x030900B1 || CYTHON_COMPILING_IN_LIMITED_API
   #define __Pyx_CyFunction_GetClassObj(f)\
       (((__pyx_CyFunctionObject *) (f))->func_classobj)
 #else
@@ -2145,7 +2320,10 @@ static CYTHON_INLINE PyObject *__Pyx_PyVectorcall_FastCallDict(PyObject *func, _
 #define __Pyx_CyFunction_SetDefaultsGetter(f, g)\
     ((__pyx_CyFunctionObject *) (f))->defaults_getter = (g)
 typedef struct {
-#if PY_VERSION_HEX < 0x030900B1
+#if CYTHON_COMPILING_IN_LIMITED_API
+    PyObject_HEAD
+    PyObject *func;
+#elif PY_VERSION_HEX < 0x030900B1
     PyCFunctionObject func;
 #else
     PyCMethodObject func;
@@ -2153,7 +2331,7 @@ typedef struct {
 #if CYTHON_BACKPORT_VECTORCALL
     __pyx_vectorcallfunc func_vectorcall;
 #endif
-#if PY_VERSION_HEX < 0x030500A0
+#if PY_VERSION_HEX < 0x030500A0 || CYTHON_COMPILING_IN_LIMITED_API
     PyObject *func_weakreflist;
 #endif
     PyObject *func_dict;
@@ -2163,12 +2341,12 @@ typedef struct {
     PyObject *func_globals;
     PyObject *func_code;
     PyObject *func_closure;
-#if PY_VERSION_HEX < 0x030900B1
+#if PY_VERSION_HEX < 0x030900B1 || CYTHON_COMPILING_IN_LIMITED_API
     PyObject *func_classobj;
 #endif
     void *defaults;
     int defaults_pyobjects;
-    size_t defaults_size;  // used by FusedFunction for copying defaults
+    size_t defaults_size;
     int flags;
     PyObject *defaults_tuple;
     PyObject *defaults_kwdict;
@@ -2176,9 +2354,13 @@ typedef struct {
     PyObject *func_annotations;
     PyObject *func_is_coroutine;
 } __pyx_CyFunctionObject;
+#undef __Pyx_CyOrPyCFunction_Check
 #define __Pyx_CyFunction_Check(obj)  __Pyx_TypeCheck(obj, __pyx_CyFunctionType)
-#define __Pyx_IsCyOrPyCFunction(obj)  __Pyx_TypeCheck2(obj, __pyx_CyFunctionType, &PyCFunction_Type)
+#define __Pyx_CyOrPyCFunction_Check(obj)  __Pyx_TypeCheck2(obj, __pyx_CyFunctionType, &PyCFunction_Type)
 #define __Pyx_CyFunction_CheckExact(obj)  __Pyx_IS_TYPE(obj, __pyx_CyFunctionType)
+static CYTHON_INLINE int __Pyx__IsSameCyOrCFunction(PyObject *func, void *cfunc);
+#undef __Pyx_IsSameCFunction
+#define __Pyx_IsSameCFunction(func, cfunc)   __Pyx__IsSameCyOrCFunction(func, cfunc)
 static PyObject *__Pyx_CyFunction_Init(__pyx_CyFunctionObject* op, PyMethodDef *ml,
                                       int flags, PyObject* qualname,
                                       PyObject *closure,
@@ -2391,7 +2573,8 @@ static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches2(PyObject *err, PyObj
 #define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception)
 
 /* CheckBinaryVersion.proto */
-static int __Pyx_check_binary_version(void);
+static unsigned long __Pyx_get_runtime_version(void);
+static int __Pyx_check_binary_version(unsigned long ct_version, unsigned long rt_version, int allow_newer);
 
 /* InitStrings.proto */
 static int __Pyx_InitStrings(__Pyx_StringTabEntry *t);
@@ -2514,6 +2697,7 @@ static const char __pyx_k_current_insertion_size[] = "current_insertion_size";
 static const char __pyx_k_substitution_positions[] = "substitution_positions";
 static const char __pyx_k_all_insertion_positions[] = "all_insertion_positions";
 static const char __pyx_k_all_substitution_values[] = "all_substitution_values";
+static const char __pyx_k_all_deletion_coordinates[] = "all_deletion_coordinates";
 static const char __pyx_k_find_indels_substitutions[] = "find_indels_substitutions";
 static const char __pyx_k_all_substitution_positions[] = "all_substitution_positions";
 static const char __pyx_k_all_insertion_left_positions[] = "all_insertion_left_positions";
@@ -2606,6 +2790,8 @@ typedef struct {
   PyObject *__pyx_kp_u__5;
   PyObject *__pyx_n_s_a;
   PyObject *__pyx_n_s_al;
+  PyObject *__pyx_n_s_all_deletion_coordinates;
+  PyObject *__pyx_n_u_all_deletion_coordinates;
   PyObject *__pyx_n_s_all_deletion_positions;
   PyObject *__pyx_n_u_all_deletion_positions;
   PyObject *__pyx_n_s_all_insertion_left_positions;
@@ -2775,6 +2961,8 @@ static int __pyx_m_clear(PyObject *m) {
   Py_CLEAR(clear_module_state->__pyx_kp_u__5);
   Py_CLEAR(clear_module_state->__pyx_n_s_a);
   Py_CLEAR(clear_module_state->__pyx_n_s_al);
+  Py_CLEAR(clear_module_state->__pyx_n_s_all_deletion_coordinates);
+  Py_CLEAR(clear_module_state->__pyx_n_u_all_deletion_coordinates);
   Py_CLEAR(clear_module_state->__pyx_n_s_all_deletion_positions);
   Py_CLEAR(clear_module_state->__pyx_n_u_all_deletion_positions);
   Py_CLEAR(clear_module_state->__pyx_n_s_all_insertion_left_positions);
@@ -2922,6 +3110,8 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
   Py_VISIT(traverse_module_state->__pyx_kp_u__5);
   Py_VISIT(traverse_module_state->__pyx_n_s_a);
   Py_VISIT(traverse_module_state->__pyx_n_s_al);
+  Py_VISIT(traverse_module_state->__pyx_n_s_all_deletion_coordinates);
+  Py_VISIT(traverse_module_state->__pyx_n_u_all_deletion_coordinates);
   Py_VISIT(traverse_module_state->__pyx_n_s_all_deletion_positions);
   Py_VISIT(traverse_module_state->__pyx_n_u_all_deletion_positions);
   Py_VISIT(traverse_module_state->__pyx_n_s_all_insertion_left_positions);
@@ -3099,6 +3289,8 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
 #define __pyx_kp_u__5 __pyx_mstate_global->__pyx_kp_u__5
 #define __pyx_n_s_a __pyx_mstate_global->__pyx_n_s_a
 #define __pyx_n_s_al __pyx_mstate_global->__pyx_n_s_al
+#define __pyx_n_s_all_deletion_coordinates __pyx_mstate_global->__pyx_n_s_all_deletion_coordinates
+#define __pyx_n_u_all_deletion_coordinates __pyx_mstate_global->__pyx_n_u_all_deletion_coordinates
 #define __pyx_n_s_all_deletion_positions __pyx_mstate_global->__pyx_n_s_all_deletion_positions
 #define __pyx_n_u_all_deletion_positions __pyx_mstate_global->__pyx_n_u_all_deletion_positions
 #define __pyx_n_s_all_insertion_left_positions __pyx_mstate_global->__pyx_n_s_all_insertion_left_positions
@@ -3198,7 +3390,7 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
 #define __pyx_codeobj__12 __pyx_mstate_global->__pyx_codeobj__12
 /* #### Code section: module_code ### */
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":245
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":245
  * 
  *         @property
  *         cdef inline PyObject* base(self) nogil:             # <<<<<<<<<<<<<<
@@ -3209,7 +3401,7 @@ static int __pyx_m_traverse(PyObject *m, visitproc visit, void *arg) {
 static CYTHON_INLINE PyObject *__pyx_f_5numpy_7ndarray_4base_base(PyArrayObject *__pyx_v_self) {
   PyObject *__pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":248
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":248
  *             """Returns a borrowed reference to the object owning the data/memory.
  *             """
  *             return PyArray_BASE(self)             # <<<<<<<<<<<<<<
@@ -3219,7 +3411,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_7ndarray_4base_base(PyArrayObject
   __pyx_r = PyArray_BASE(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":245
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":245
  * 
  *         @property
  *         cdef inline PyObject* base(self) nogil:             # <<<<<<<<<<<<<<
@@ -3232,7 +3424,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_7ndarray_4base_base(PyArrayObject
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":251
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":251
  * 
  *         @property
  *         cdef inline dtype descr(self):             # <<<<<<<<<<<<<<
@@ -3244,9 +3436,9 @@ static CYTHON_INLINE PyArray_Descr *__pyx_f_5numpy_7ndarray_5descr_descr(PyArray
   PyArray_Descr *__pyx_r = NULL;
   __Pyx_RefNannyDeclarations
   PyArray_Descr *__pyx_t_1;
-  __Pyx_RefNannySetupContext("descr", 0);
+  __Pyx_RefNannySetupContext("descr", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":254
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":254
  *             """Returns an owned reference to the dtype of the array.
  *             """
  *             return <dtype>PyArray_DESCR(self)             # <<<<<<<<<<<<<<
@@ -3259,7 +3451,7 @@ static CYTHON_INLINE PyArray_Descr *__pyx_f_5numpy_7ndarray_5descr_descr(PyArray
   __pyx_r = ((PyArray_Descr *)__pyx_t_1);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":251
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":251
  * 
  *         @property
  *         cdef inline dtype descr(self):             # <<<<<<<<<<<<<<
@@ -3274,7 +3466,7 @@ static CYTHON_INLINE PyArray_Descr *__pyx_f_5numpy_7ndarray_5descr_descr(PyArray
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":257
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":257
  * 
  *         @property
  *         cdef inline int ndim(self) nogil:             # <<<<<<<<<<<<<<
@@ -3285,7 +3477,7 @@ static CYTHON_INLINE PyArray_Descr *__pyx_f_5numpy_7ndarray_5descr_descr(PyArray
 static CYTHON_INLINE int __pyx_f_5numpy_7ndarray_4ndim_ndim(PyArrayObject *__pyx_v_self) {
   int __pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":260
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":260
  *             """Returns the number of dimensions in the array.
  *             """
  *             return PyArray_NDIM(self)             # <<<<<<<<<<<<<<
@@ -3295,7 +3487,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_7ndarray_4ndim_ndim(PyArrayObject *__pyx
   __pyx_r = PyArray_NDIM(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":257
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":257
  * 
  *         @property
  *         cdef inline int ndim(self) nogil:             # <<<<<<<<<<<<<<
@@ -3308,7 +3500,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_7ndarray_4ndim_ndim(PyArrayObject *__pyx
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":263
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":263
  * 
  *         @property
  *         cdef inline npy_intp *shape(self) nogil:             # <<<<<<<<<<<<<<
@@ -3319,7 +3511,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_7ndarray_4ndim_ndim(PyArrayObject *__pyx
 static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_5shape_shape(PyArrayObject *__pyx_v_self) {
   npy_intp *__pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":268
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":268
  *             Can return NULL for 0-dimensional arrays.
  *             """
  *             return PyArray_DIMS(self)             # <<<<<<<<<<<<<<
@@ -3329,7 +3521,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_5shape_shape(PyArrayObjec
   __pyx_r = PyArray_DIMS(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":263
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":263
  * 
  *         @property
  *         cdef inline npy_intp *shape(self) nogil:             # <<<<<<<<<<<<<<
@@ -3342,7 +3534,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_5shape_shape(PyArrayObjec
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":271
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":271
  * 
  *         @property
  *         cdef inline npy_intp *strides(self) nogil:             # <<<<<<<<<<<<<<
@@ -3353,7 +3545,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_5shape_shape(PyArrayObjec
 static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_7strides_strides(PyArrayObject *__pyx_v_self) {
   npy_intp *__pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":275
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":275
  *             The number of elements matches the number of dimensions of the array (ndim).
  *             """
  *             return PyArray_STRIDES(self)             # <<<<<<<<<<<<<<
@@ -3363,7 +3555,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_7strides_strides(PyArrayO
   __pyx_r = PyArray_STRIDES(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":271
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":271
  * 
  *         @property
  *         cdef inline npy_intp *strides(self) nogil:             # <<<<<<<<<<<<<<
@@ -3376,7 +3568,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_7strides_strides(PyArrayO
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":278
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":278
  * 
  *         @property
  *         cdef inline npy_intp size(self) nogil:             # <<<<<<<<<<<<<<
@@ -3387,7 +3579,7 @@ static CYTHON_INLINE npy_intp *__pyx_f_5numpy_7ndarray_7strides_strides(PyArrayO
 static CYTHON_INLINE npy_intp __pyx_f_5numpy_7ndarray_4size_size(PyArrayObject *__pyx_v_self) {
   npy_intp __pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":281
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":281
  *             """Returns the total size (in number of elements) of the array.
  *             """
  *             return PyArray_SIZE(self)             # <<<<<<<<<<<<<<
@@ -3397,7 +3589,7 @@ static CYTHON_INLINE npy_intp __pyx_f_5numpy_7ndarray_4size_size(PyArrayObject *
   __pyx_r = PyArray_SIZE(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":278
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":278
  * 
  *         @property
  *         cdef inline npy_intp size(self) nogil:             # <<<<<<<<<<<<<<
@@ -3410,7 +3602,7 @@ static CYTHON_INLINE npy_intp __pyx_f_5numpy_7ndarray_4size_size(PyArrayObject *
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":284
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":284
  * 
  *         @property
  *         cdef inline char* data(self) nogil:             # <<<<<<<<<<<<<<
@@ -3421,7 +3613,7 @@ static CYTHON_INLINE npy_intp __pyx_f_5numpy_7ndarray_4size_size(PyArrayObject *
 static CYTHON_INLINE char *__pyx_f_5numpy_7ndarray_4data_data(PyArrayObject *__pyx_v_self) {
   char *__pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":290
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":290
  *             of `PyArray_DATA()` instead, which returns a 'void*'.
  *             """
  *             return PyArray_BYTES(self)             # <<<<<<<<<<<<<<
@@ -3431,7 +3623,7 @@ static CYTHON_INLINE char *__pyx_f_5numpy_7ndarray_4data_data(PyArrayObject *__p
   __pyx_r = PyArray_BYTES(__pyx_v_self);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":284
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":284
  * 
  *         @property
  *         cdef inline char* data(self) nogil:             # <<<<<<<<<<<<<<
@@ -3444,7 +3636,7 @@ static CYTHON_INLINE char *__pyx_f_5numpy_7ndarray_4data_data(PyArrayObject *__p
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":773
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":773
  * ctypedef npy_cdouble     complex_t
  * 
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
@@ -3459,9 +3651,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 0);
+  __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":774
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":774
  * 
  * cdef inline object PyArray_MultiIterNew1(a):
  *     return PyArray_MultiIterNew(1, <void*>a)             # <<<<<<<<<<<<<<
@@ -3475,7 +3667,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":773
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":773
  * ctypedef npy_cdouble     complex_t
  * 
  * cdef inline object PyArray_MultiIterNew1(a):             # <<<<<<<<<<<<<<
@@ -3494,7 +3686,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":776
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":776
  *     return PyArray_MultiIterNew(1, <void*>a)
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
@@ -3509,9 +3701,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 0);
+  __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":777
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":777
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)             # <<<<<<<<<<<<<<
@@ -3525,7 +3717,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":776
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":776
  *     return PyArray_MultiIterNew(1, <void*>a)
  * 
  * cdef inline object PyArray_MultiIterNew2(a, b):             # <<<<<<<<<<<<<<
@@ -3544,7 +3736,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":779
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":779
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
@@ -3559,9 +3751,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 0);
+  __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":780
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":780
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)             # <<<<<<<<<<<<<<
@@ -3575,7 +3767,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":779
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":779
  *     return PyArray_MultiIterNew(2, <void*>a, <void*>b)
  * 
  * cdef inline object PyArray_MultiIterNew3(a, b, c):             # <<<<<<<<<<<<<<
@@ -3594,7 +3786,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":782
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":782
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
@@ -3609,9 +3801,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 0);
+  __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":783
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":783
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)             # <<<<<<<<<<<<<<
@@ -3625,7 +3817,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":782
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":782
  *     return PyArray_MultiIterNew(3, <void*>a, <void*>b, <void*> c)
  * 
  * cdef inline object PyArray_MultiIterNew4(a, b, c, d):             # <<<<<<<<<<<<<<
@@ -3644,7 +3836,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":785
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":785
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
@@ -3659,9 +3851,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 0);
+  __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":786
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":786
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)             # <<<<<<<<<<<<<<
@@ -3675,7 +3867,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":785
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":785
  *     return PyArray_MultiIterNew(4, <void*>a, <void*>b, <void*>c, <void*> d)
  * 
  * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e):             # <<<<<<<<<<<<<<
@@ -3694,7 +3886,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":788
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":788
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
@@ -3706,9 +3898,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   PyObject *__pyx_r = NULL;
   __Pyx_RefNannyDeclarations
   int __pyx_t_1;
-  __Pyx_RefNannySetupContext("PyDataType_SHAPE", 0);
+  __Pyx_RefNannySetupContext("PyDataType_SHAPE", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":789
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":789
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
@@ -3718,7 +3910,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   __pyx_t_1 = PyDataType_HASSUBARRAY(__pyx_v_d);
   if (__pyx_t_1) {
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":790
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":790
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):
  *         return <tuple>d.subarray.shape             # <<<<<<<<<<<<<<
@@ -3730,7 +3922,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
     __pyx_r = ((PyObject*)__pyx_v_d->subarray->shape);
     goto __pyx_L0;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":789
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":789
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):
  *     if PyDataType_HASSUBARRAY(d):             # <<<<<<<<<<<<<<
@@ -3739,7 +3931,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
  */
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":792
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":792
  *         return <tuple>d.subarray.shape
  *     else:
  *         return ()             # <<<<<<<<<<<<<<
@@ -3753,7 +3945,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
     goto __pyx_L0;
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":788
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":788
  *     return PyArray_MultiIterNew(5, <void*>a, <void*>b, <void*>c, <void*> d, <void*> e)
  * 
  * cdef inline tuple PyDataType_SHAPE(dtype d):             # <<<<<<<<<<<<<<
@@ -3768,7 +3960,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":967
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":968
  *     int _import_umath() except -1
  * 
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
@@ -3777,14 +3969,12 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__
  */
 
 static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_arr, PyObject *__pyx_v_base) {
-  __Pyx_RefNannyDeclarations
   int __pyx_t_1;
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("set_array_base", 0);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":968
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":969
  * 
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!             # <<<<<<<<<<<<<<
@@ -3793,16 +3983,16 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
  */
   Py_INCREF(__pyx_v_base);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":969
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":970
  * cdef inline void set_array_base(ndarray arr, object base):
  *     Py_INCREF(base) # important to do this before stealing the reference below!
  *     PyArray_SetBaseObject(arr, base)             # <<<<<<<<<<<<<<
  * 
  * cdef inline object get_array_base(ndarray arr):
  */
-  __pyx_t_1 = PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base); if (unlikely(__pyx_t_1 == ((int)-1))) __PYX_ERR(1, 969, __pyx_L1_error)
+  __pyx_t_1 = PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base); if (unlikely(__pyx_t_1 == ((int)-1))) __PYX_ERR(1, 970, __pyx_L1_error)
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":967
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":968
  *     int _import_umath() except -1
  * 
  * cdef inline void set_array_base(ndarray arr, object base):             # <<<<<<<<<<<<<<
@@ -3815,10 +4005,9 @@ static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_a
   __pyx_L1_error:;
   __Pyx_AddTraceback("numpy.set_array_base", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __pyx_L0:;
-  __Pyx_RefNannyFinishContext();
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":971
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":972
  *     PyArray_SetBaseObject(arr, base)
  * 
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
@@ -3831,9 +4020,9 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   PyObject *__pyx_r = NULL;
   __Pyx_RefNannyDeclarations
   int __pyx_t_1;
-  __Pyx_RefNannySetupContext("get_array_base", 0);
+  __Pyx_RefNannySetupContext("get_array_base", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":972
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":973
  * 
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)             # <<<<<<<<<<<<<<
@@ -3842,7 +4031,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  */
   __pyx_v_base = PyArray_BASE(__pyx_v_arr);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":973
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":974
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)
  *     if base is NULL:             # <<<<<<<<<<<<<<
@@ -3852,7 +4041,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   __pyx_t_1 = (__pyx_v_base == NULL);
   if (__pyx_t_1) {
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":974
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":975
  *     base = PyArray_BASE(arr)
  *     if base is NULL:
  *         return None             # <<<<<<<<<<<<<<
@@ -3863,7 +4052,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
     __pyx_r = Py_None; __Pyx_INCREF(Py_None);
     goto __pyx_L0;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":973
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":974
  * cdef inline object get_array_base(ndarray arr):
  *     base = PyArray_BASE(arr)
  *     if base is NULL:             # <<<<<<<<<<<<<<
@@ -3872,7 +4061,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
  */
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":975
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":976
  *     if base is NULL:
  *         return None
  *     return <object>base             # <<<<<<<<<<<<<<
@@ -3884,7 +4073,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   __pyx_r = ((PyObject *)__pyx_v_base);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":971
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":972
  *     PyArray_SetBaseObject(arr, base)
  * 
  * cdef inline object get_array_base(ndarray arr):             # <<<<<<<<<<<<<<
@@ -3899,7 +4088,7 @@ static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__py
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":979
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":980
  * # Versions of the import_* functions which are more suitable for
  * # Cython code.
  * cdef inline int import_array() except -1:             # <<<<<<<<<<<<<<
@@ -3921,9 +4110,9 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("import_array", 0);
+  __Pyx_RefNannySetupContext("import_array", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":980
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":981
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3939,16 +4128,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":981
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":982
  * cdef inline int import_array() except -1:
  *     try:
  *         __pyx_import_array()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")
  */
-      __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 981, __pyx_L3_error)
+      __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 982, __pyx_L3_error)
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":980
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":981
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -3962,7 +4151,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":982
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":983
  *     try:
  *         __pyx_import_array()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -3972,27 +4161,27 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 982, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 983, __pyx_L5_except_error)
       __Pyx_XGOTREF(__pyx_t_5);
       __Pyx_XGOTREF(__pyx_t_6);
       __Pyx_XGOTREF(__pyx_t_7);
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":983
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":984
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_umath() except -1:
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 983, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 984, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 983, __pyx_L5_except_error)
+      __PYX_ERR(1, 984, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":980
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":981
  * # Cython code.
  * cdef inline int import_array() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4008,7 +4197,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":979
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":980
  * # Versions of the import_* functions which are more suitable for
  * # Cython code.
  * cdef inline int import_array() except -1:             # <<<<<<<<<<<<<<
@@ -4031,7 +4220,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) {
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":985
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":986
  *         raise ImportError("numpy.core.multiarray failed to import")
  * 
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
@@ -4053,9 +4242,9 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("import_umath", 0);
+  __Pyx_RefNannySetupContext("import_umath", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":986
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":987
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4071,16 +4260,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":987
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":988
  * cdef inline int import_umath() except -1:
  *     try:
  *         _import_umath()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")
  */
-      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 987, __pyx_L3_error)
+      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 988, __pyx_L3_error)
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":986
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":987
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4094,7 +4283,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":988
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":989
  *     try:
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -4104,27 +4293,27 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 988, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 989, __pyx_L5_except_error)
       __Pyx_XGOTREF(__pyx_t_5);
       __Pyx_XGOTREF(__pyx_t_6);
       __Pyx_XGOTREF(__pyx_t_7);
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":989
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":990
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_ufunc() except -1:
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 989, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 990, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 989, __pyx_L5_except_error)
+      __PYX_ERR(1, 990, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":986
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":987
  * 
  * cdef inline int import_umath() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4140,7 +4329,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":985
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":986
  *         raise ImportError("numpy.core.multiarray failed to import")
  * 
  * cdef inline int import_umath() except -1:             # <<<<<<<<<<<<<<
@@ -4163,7 +4352,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) {
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":991
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":992
  *         raise ImportError("numpy.core.umath failed to import")
  * 
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
@@ -4185,9 +4374,9 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("import_ufunc", 0);
+  __Pyx_RefNannySetupContext("import_ufunc", 1);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":992
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":993
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4203,16 +4392,16 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __Pyx_XGOTREF(__pyx_t_3);
     /*try:*/ {
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":993
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":994
  * cdef inline int import_ufunc() except -1:
  *     try:
  *         _import_umath()             # <<<<<<<<<<<<<<
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")
  */
-      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 993, __pyx_L3_error)
+      __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 994, __pyx_L3_error)
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":992
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":993
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4226,7 +4415,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     goto __pyx_L8_try_end;
     __pyx_L3_error:;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":994
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":995
  *     try:
  *         _import_umath()
  *     except Exception:             # <<<<<<<<<<<<<<
@@ -4236,27 +4425,27 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0])));
     if (__pyx_t_4) {
       __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 994, __pyx_L5_except_error)
+      if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 995, __pyx_L5_except_error)
       __Pyx_XGOTREF(__pyx_t_5);
       __Pyx_XGOTREF(__pyx_t_6);
       __Pyx_XGOTREF(__pyx_t_7);
 
-      /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":995
+      /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":996
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * 
  */
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 995, __pyx_L5_except_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 996, __pyx_L5_except_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_Raise(__pyx_t_8, 0, 0, 0);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __PYX_ERR(1, 995, __pyx_L5_except_error)
+      __PYX_ERR(1, 996, __pyx_L5_except_error)
     }
     goto __pyx_L5_except_error;
 
-    /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":992
+    /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":993
  * 
  * cdef inline int import_ufunc() except -1:
  *     try:             # <<<<<<<<<<<<<<
@@ -4272,7 +4461,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
     __pyx_L8_try_end:;
   }
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":991
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":992
  *         raise ImportError("numpy.core.umath failed to import")
  * 
  * cdef inline int import_ufunc() except -1:             # <<<<<<<<<<<<<<
@@ -4295,7 +4484,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":998
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":999
  * 
  * 
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4305,10 +4494,8 @@ static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) {
 
 static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_obj) {
   int __pyx_r;
-  __Pyx_RefNannyDeclarations
-  __Pyx_RefNannySetupContext("is_timedelta64_object", 0);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1010
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1011
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyTimedeltaArrType_Type)             # <<<<<<<<<<<<<<
@@ -4318,7 +4505,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyTimedeltaArrType_Type));
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":998
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":999
  * 
  * 
  * cdef inline bint is_timedelta64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4328,11 +4515,10 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
 
   /* function exit code */
   __pyx_L0:;
-  __Pyx_RefNannyFinishContext();
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1013
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1014
  * 
  * 
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4342,10 +4528,8 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_timedelta64_object(PyObject *__pyx_v_
 
 static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_obj) {
   int __pyx_r;
-  __Pyx_RefNannyDeclarations
-  __Pyx_RefNannySetupContext("is_datetime64_object", 0);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1025
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1026
  *     bool
  *     """
  *     return PyObject_TypeCheck(obj, &PyDatetimeArrType_Type)             # <<<<<<<<<<<<<<
@@ -4355,7 +4539,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
   __pyx_r = PyObject_TypeCheck(__pyx_v_obj, (&PyDatetimeArrType_Type));
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1013
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1014
  * 
  * 
  * cdef inline bint is_datetime64_object(object obj):             # <<<<<<<<<<<<<<
@@ -4365,11 +4549,10 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
 
   /* function exit code */
   __pyx_L0:;
-  __Pyx_RefNannyFinishContext();
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1028
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1029
  * 
  * 
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4380,7 +4563,7 @@ static CYTHON_INLINE int __pyx_f_5numpy_is_datetime64_object(PyObject *__pyx_v_o
 static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *__pyx_v_obj) {
   npy_datetime __pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1035
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1036
  *     also needed.  That can be found using `get_datetime64_unit`.
  *     """
  *     return (<PyDatetimeScalarObject*>obj).obval             # <<<<<<<<<<<<<<
@@ -4390,7 +4573,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
   __pyx_r = ((PyDatetimeScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1028
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1029
  * 
  * 
  * cdef inline npy_datetime get_datetime64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4403,7 +4586,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1038
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1039
  * 
  * 
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4414,7 +4597,7 @@ static CYTHON_INLINE npy_datetime __pyx_f_5numpy_get_datetime64_value(PyObject *
 static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject *__pyx_v_obj) {
   npy_timedelta __pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1042
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1043
  *     returns the int64 value underlying scalar numpy timedelta64 object
  *     """
  *     return (<PyTimedeltaScalarObject*>obj).obval             # <<<<<<<<<<<<<<
@@ -4424,7 +4607,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
   __pyx_r = ((PyTimedeltaScalarObject *)__pyx_v_obj)->obval;
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1038
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1039
  * 
  * 
  * cdef inline npy_timedelta get_timedelta64_value(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4437,7 +4620,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
   return __pyx_r;
 }
 
-/* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1045
+/* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1046
  * 
  * 
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4448,7 +4631,7 @@ static CYTHON_INLINE npy_timedelta __pyx_f_5numpy_get_timedelta64_value(PyObject
 static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObject *__pyx_v_obj) {
   NPY_DATETIMEUNIT __pyx_r;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1049
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1050
  *     returns the unit part of the dtype for a numpy datetime64 object.
  *     """
  *     return <NPY_DATETIMEUNIT>(<PyDatetimeScalarObject*>obj).obmeta.base             # <<<<<<<<<<<<<<
@@ -4456,7 +4639,7 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
   __pyx_r = ((NPY_DATETIMEUNIT)((PyDatetimeScalarObject *)__pyx_v_obj)->obmeta.base);
   goto __pyx_L0;
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1045
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":1046
  * 
  * 
  * cdef inline NPY_DATETIMEUNIT get_datetime64_unit(object obj) nogil:             # <<<<<<<<<<<<<<
@@ -4469,7 +4652,7 @@ static CYTHON_INLINE NPY_DATETIMEUNIT __pyx_f_5numpy_get_datetime64_unit(PyObjec
   return __pyx_r;
 }
 
-/* "CRISPResso2/CRISPRessoCOREResources.pyx":19
+/* "CRISPResso2/CRISPRessoCOREResources.pyx":18
  * re_find_indels = re.compile("(-*-)")
  * 
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
@@ -4497,18 +4680,26 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   PyObject *__pyx_v_ref_seq_al = 0;
   PyObject *__pyx_v__include_indx = 0;
   #if !CYTHON_METH_FASTCALL
-  CYTHON_UNUSED const Py_ssize_t __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
   #endif
-  CYTHON_UNUSED PyObject *const *__pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
+  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
+  PyObject* values[3] = {0,0,0};
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
   PyObject *__pyx_r = 0;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("find_indels_substitutions (wrapper)", 0);
+  #if !CYTHON_METH_FASTCALL
+  #if CYTHON_ASSUME_SAFE_MACROS
+  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  #else
+  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
+  #endif
+  #endif
+  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
   {
     PyObject **__pyx_pyargnames[] = {&__pyx_n_s_read_seq_al,&__pyx_n_s_ref_seq_al,&__pyx_n_s_include_indx,0};
-    PyObject* values[3] = {0,0,0};
     if (__pyx_kwds) {
       Py_ssize_t kw_args;
       switch (__pyx_nargs) {
@@ -4524,27 +4715,36 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
       kw_args = __Pyx_NumKwargs_FASTCALL(__pyx_kwds);
       switch (__pyx_nargs) {
         case  0:
-        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_read_seq_al)) != 0)) kw_args--;
-        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 19, __pyx_L3_error)
+        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_read_seq_al)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[0]);
+          kw_args--;
+        }
+        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 18, __pyx_L3_error)
         else goto __pyx_L5_argtuple_error;
         CYTHON_FALLTHROUGH;
         case  1:
-        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_ref_seq_al)) != 0)) kw_args--;
-        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 19, __pyx_L3_error)
+        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_ref_seq_al)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[1]);
+          kw_args--;
+        }
+        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 18, __pyx_L3_error)
         else {
-          __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, 1); __PYX_ERR(0, 19, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, 1); __PYX_ERR(0, 18, __pyx_L3_error)
         }
         CYTHON_FALLTHROUGH;
         case  2:
-        if (likely((values[2] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_include_indx)) != 0)) kw_args--;
-        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 19, __pyx_L3_error)
+        if (likely((values[2] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_include_indx)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[2]);
+          kw_args--;
+        }
+        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 18, __pyx_L3_error)
         else {
-          __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, 2); __PYX_ERR(0, 19, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, 2); __PYX_ERR(0, 18, __pyx_L3_error)
         }
       }
       if (unlikely(kw_args > 0)) {
         const Py_ssize_t kwd_pos_args = __pyx_nargs;
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values + 0, kwd_pos_args, "find_indels_substitutions") < 0)) __PYX_ERR(0, 19, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values + 0, kwd_pos_args, "find_indels_substitutions") < 0)) __PYX_ERR(0, 18, __pyx_L3_error)
       }
     } else if (unlikely(__pyx_nargs != 3)) {
       goto __pyx_L5_argtuple_error;
@@ -4557,10 +4757,18 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
     __pyx_v_ref_seq_al = values[1];
     __pyx_v__include_indx = values[2];
   }
-  goto __pyx_L4_argument_unpacking_done;
+  goto __pyx_L6_skip;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, __pyx_nargs); __PYX_ERR(0, 19, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("find_indels_substitutions", 1, 3, 3, __pyx_nargs); __PYX_ERR(0, 18, __pyx_L3_error)
+  __pyx_L6_skip:;
+  goto __pyx_L4_argument_unpacking_done;
   __pyx_L3_error:;
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_AddTraceback("CRISPResso2.CRISPRessoCOREResources.find_indels_substitutions", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return NULL;
@@ -4568,6 +4776,12 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   __pyx_r = __pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_substitutions(__pyx_self, __pyx_v_read_seq_al, __pyx_v_ref_seq_al, __pyx_v__include_indx);
 
   /* function exit code */
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_RefNannyFinishContext();
   return __pyx_r;
 }
@@ -4579,6 +4793,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   PyObject *__pyx_v_all_substitution_values = NULL;
   PyObject *__pyx_v_substitution_values = NULL;
   PyObject *__pyx_v_all_deletion_positions = NULL;
+  PyObject *__pyx_v_all_deletion_coordinates = NULL;
   PyObject *__pyx_v_deletion_positions = NULL;
   PyObject *__pyx_v_deletion_coordinates = NULL;
   PyObject *__pyx_v_deletion_sizes = NULL;
@@ -4617,83 +4832,95 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("find_indels_substitutions", 0);
+  __Pyx_RefNannySetupContext("find_indels_substitutions", 1);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":33
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":32
  *     # aln A - T T T G G C C
  *     #     1 2 3 4-4 5 6 7 8 <ref positions. Note that the negative values/indices represent places that don't map back to the original reference
  *     ref_positions=[]             # <<<<<<<<<<<<<<
  *     all_substitution_positions=[]
  *     substitution_positions=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 33, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 32, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_ref_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":34
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":33
  *     #     1 2 3 4-4 5 6 7 8 <ref positions. Note that the negative values/indices represent places that don't map back to the original reference
  *     ref_positions=[]
  *     all_substitution_positions=[]             # <<<<<<<<<<<<<<
  *     substitution_positions=[]
  *     all_substitution_values=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 34, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 33, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_substitution_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":35
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":34
  *     ref_positions=[]
  *     all_substitution_positions=[]
  *     substitution_positions=[]             # <<<<<<<<<<<<<<
  *     all_substitution_values=[]
  *     substitution_values=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 35, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 34, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_substitution_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":36
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":35
  *     all_substitution_positions=[]
  *     substitution_positions=[]
  *     all_substitution_values=[]             # <<<<<<<<<<<<<<
  *     substitution_values=[]
  * 
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 36, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 35, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_substitution_values = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":37
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":36
  *     substitution_positions=[]
  *     all_substitution_values=[]
  *     substitution_values=[]             # <<<<<<<<<<<<<<
  * 
  *     all_deletion_positions = []
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 37, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 36, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_substitution_values = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":39
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":38
  *     substitution_values=[]
  * 
  *     all_deletion_positions = []             # <<<<<<<<<<<<<<
+ *     all_deletion_coordinates = []
+ *     deletion_positions = []
+ */
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_v_all_deletion_positions = ((PyObject*)__pyx_t_1);
+  __pyx_t_1 = 0;
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":39
+ * 
+ *     all_deletion_positions = []
+ *     all_deletion_coordinates = []             # <<<<<<<<<<<<<<
  *     deletion_positions = []
  *     deletion_coordinates = []
  */
   __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 39, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_v_all_deletion_positions = ((PyObject*)__pyx_t_1);
+  __pyx_v_all_deletion_coordinates = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":40
- * 
  *     all_deletion_positions = []
+ *     all_deletion_coordinates = []
  *     deletion_positions = []             # <<<<<<<<<<<<<<
  *     deletion_coordinates = []
  *     deletion_sizes = []
@@ -4704,7 +4931,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   __pyx_t_1 = 0;
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":41
- *     all_deletion_positions = []
+ *     all_deletion_coordinates = []
  *     deletion_positions = []
  *     deletion_coordinates = []             # <<<<<<<<<<<<<<
  *     deletion_sizes = []
@@ -4871,7 +5098,8 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
  */
   __pyx_t_3 = 0;
   if (likely(PyList_CheckExact(__pyx_v_ref_seq_al)) || PyTuple_CheckExact(__pyx_v_ref_seq_al)) {
-    __pyx_t_1 = __pyx_v_ref_seq_al; __Pyx_INCREF(__pyx_t_1); __pyx_t_2 = 0;
+    __pyx_t_1 = __pyx_v_ref_seq_al; __Pyx_INCREF(__pyx_t_1);
+    __pyx_t_2 = 0;
     __pyx_t_4 = NULL;
   } else {
     __pyx_t_2 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_v_ref_seq_al); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 58, __pyx_L1_error)
@@ -4881,19 +5109,31 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   for (;;) {
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
-        if (__pyx_t_2 >= PyList_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 58, __pyx_L1_error)
+          #endif
+          if (__pyx_t_2 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
         __pyx_t_5 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_5); __pyx_t_2++; if (unlikely((0 < 0))) __PYX_ERR(0, 58, __pyx_L1_error)
         #else
-        __pyx_t_5 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
         #endif
       } else {
-        if (__pyx_t_2 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyTuple_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 58, __pyx_L1_error)
+          #endif
+          if (__pyx_t_2 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
         __pyx_t_5 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_5); __pyx_t_2++; if (unlikely((0 < 0))) __PYX_ERR(0, 58, __pyx_L1_error)
         #else
-        __pyx_t_5 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
         #endif
       }
@@ -5137,9 +5377,9 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
           __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 72, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_5);
           __Pyx_GIVEREF(__pyx_t_9);
-          PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_9);
+          if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_9)) __PYX_ERR(0, 72, __pyx_L1_error);
           __Pyx_GIVEREF(__pyx_t_8);
-          PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_t_8);
+          if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_t_8)) __PYX_ERR(0, 72, __pyx_L1_error);
           __pyx_t_9 = 0;
           __pyx_t_8 = 0;
           __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_coordinates, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 72, __pyx_L1_error)
@@ -5427,7 +5667,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
  *         elif read_seq_al[idx_c] != '-' and start_deletion != -1:  # this is the end of a deletion
  *             end_deletion = ref_positions[idx_c]             # <<<<<<<<<<<<<<
  *             all_deletion_positions.extend(range(start_deletion, end_deletion))
- *             if include_indx_set.intersection(range(start_deletion, end_deletion)):
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))
  */
       __pyx_t_5 = PyList_GET_ITEM(__pyx_v_ref_positions, __pyx_v_idx_c);
       __Pyx_INCREF(__pyx_t_5);
@@ -5438,18 +5678,18 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
  *         elif read_seq_al[idx_c] != '-' and start_deletion != -1:  # this is the end of a deletion
  *             end_deletion = ref_positions[idx_c]
  *             all_deletion_positions.extend(range(start_deletion, end_deletion))             # <<<<<<<<<<<<<<
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))
  *             if include_indx_set.intersection(range(start_deletion, end_deletion)):
- *                 deletion_positions.extend(range(start_deletion, end_deletion))
  */
       __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 95, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
       __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 95, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_GIVEREF(__pyx_t_5);
-      PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5)) __PYX_ERR(0, 95, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_end_deletion);
       __Pyx_GIVEREF(__pyx_v_end_deletion);
-      PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 95, __pyx_L1_error);
       __pyx_t_5 = 0;
       __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 95, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
@@ -5460,98 +5700,118 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
       /* "CRISPResso2/CRISPRessoCOREResources.pyx":96
  *             end_deletion = ref_positions[idx_c]
  *             all_deletion_positions.extend(range(start_deletion, end_deletion))
- *             if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))             # <<<<<<<<<<<<<<
+ *             if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *                 deletion_positions.extend(range(start_deletion, end_deletion))
- *                 deletion_coordinates.append((start_deletion, end_deletion))
  */
       __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 96, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
       __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 96, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_GIVEREF(__pyx_t_5);
-      PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5)) __PYX_ERR(0, 96, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_end_deletion);
       __Pyx_GIVEREF(__pyx_v_end_deletion);
-      PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 96, __pyx_L1_error);
       __pyx_t_5 = 0;
-      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 96, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_5);
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_deletion_coordinates, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 96, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __pyx_t_8 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_5); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 96, __pyx_L1_error)
+
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":97
+ *             all_deletion_positions.extend(range(start_deletion, end_deletion))
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))
+ *             if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
+ *                 deletion_positions.extend(range(start_deletion, end_deletion))
+ *                 deletion_coordinates.append((start_deletion, end_deletion))
+ */
+      __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 97, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_8);
+      __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 97, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_GIVEREF(__pyx_t_8);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_8)) __PYX_ERR(0, 97, __pyx_L1_error);
+      __Pyx_INCREF(__pyx_v_end_deletion);
+      __Pyx_GIVEREF(__pyx_v_end_deletion);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 97, __pyx_L1_error);
+      __pyx_t_8 = 0;
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 97, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_8); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 96, __pyx_L1_error)
+      __pyx_t_5 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_8); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 97, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+      __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_5); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 97, __pyx_L1_error)
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
       if (__pyx_t_6) {
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":97
- *             all_deletion_positions.extend(range(start_deletion, end_deletion))
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":98
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))
  *             if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *                 deletion_positions.extend(range(start_deletion, end_deletion))             # <<<<<<<<<<<<<<
  *                 deletion_coordinates.append((start_deletion, end_deletion))
  *                 deletion_sizes.append(end_deletion - start_deletion)
  */
-        __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 97, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 97, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
-        __Pyx_GIVEREF(__pyx_t_8);
-        PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_8);
+        __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 98, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_8);
+        __Pyx_GIVEREF(__pyx_t_5);
+        if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error);
         __Pyx_INCREF(__pyx_v_end_deletion);
         __Pyx_GIVEREF(__pyx_v_end_deletion);
-        PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion);
-        __pyx_t_8 = 0;
-        __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 97, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-        __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 97, __pyx_L1_error)
+        if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 98, __pyx_L1_error);
+        __pyx_t_5 = 0;
+        __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_5);
         __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+        __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 98, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":98
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":99
  *             if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *                 deletion_positions.extend(range(start_deletion, end_deletion))
  *                 deletion_coordinates.append((start_deletion, end_deletion))             # <<<<<<<<<<<<<<
  *                 deletion_sizes.append(end_deletion - start_deletion)
  *             start_deletion = -1
  */
-        __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 99, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
-        __Pyx_GIVEREF(__pyx_t_8);
-        PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_8);
+        __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 99, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_8);
+        __Pyx_GIVEREF(__pyx_t_5);
+        if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5)) __PYX_ERR(0, 99, __pyx_L1_error);
         __Pyx_INCREF(__pyx_v_end_deletion);
         __Pyx_GIVEREF(__pyx_v_end_deletion);
-        PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion);
-        __pyx_t_8 = 0;
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+        if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 99, __pyx_L1_error);
+        __pyx_t_5 = 0;
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 99, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":99
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":100
  *                 deletion_positions.extend(range(start_deletion, end_deletion))
  *                 deletion_coordinates.append((start_deletion, end_deletion))
  *                 deletion_sizes.append(end_deletion - start_deletion)             # <<<<<<<<<<<<<<
  *             start_deletion = -1
  * 
  */
-        __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 99, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_5);
-        __pyx_t_8 = PyNumber_Subtract(__pyx_v_end_deletion, __pyx_t_5); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 99, __pyx_L1_error)
+        __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 100, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 99, __pyx_L1_error)
+        __pyx_t_5 = PyNumber_Subtract(__pyx_v_end_deletion, __pyx_t_8); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 100, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_5);
         __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 100, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":96
- *             end_deletion = ref_positions[idx_c]
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":97
  *             all_deletion_positions.extend(range(start_deletion, end_deletion))
+ *             all_deletion_coordinates.append((start_deletion, end_deletion))
  *             if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
  *                 deletion_positions.extend(range(start_deletion, end_deletion))
  *                 deletion_coordinates.append((start_deletion, end_deletion))
  */
       }
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":100
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":101
  *                 deletion_coordinates.append((start_deletion, end_deletion))
  *                 deletion_sizes.append(end_deletion - start_deletion)
  *             start_deletion = -1             # <<<<<<<<<<<<<<
@@ -5580,7 +5840,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   }
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":102
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":103
  *             start_deletion = -1
  * 
  *     if start_deletion != -1:             # <<<<<<<<<<<<<<
@@ -5590,12 +5850,12 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   __pyx_t_6 = (__pyx_v_start_deletion != -1L);
   if (__pyx_t_6) {
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":103
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":104
  * 
  *     if start_deletion != -1:
  *         end_deletion = ref_positions[seq_len - 1]             # <<<<<<<<<<<<<<
  *         all_deletion_positions.extend(range(start_deletion, end_deletion))
- *         if include_indx_set.intersection(range(start_deletion, end_deletion)):
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))
  */
     __pyx_t_12 = (__pyx_v_seq_len - 1);
     __pyx_t_1 = PyList_GET_ITEM(__pyx_v_ref_positions, __pyx_t_12);
@@ -5603,124 +5863,144 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
     __Pyx_XDECREF_SET(__pyx_v_end_deletion, __pyx_t_1);
     __pyx_t_1 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":104
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":105
  *     if start_deletion != -1:
  *         end_deletion = ref_positions[seq_len - 1]
  *         all_deletion_positions.extend(range(start_deletion, end_deletion))             # <<<<<<<<<<<<<<
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))
  *         if include_indx_set.intersection(range(start_deletion, end_deletion)):
- *             deletion_positions.extend(range(start_deletion, end_deletion))
  */
-    __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 104, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 104, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_8);
+    __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 105, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
     __Pyx_GIVEREF(__pyx_t_1);
-    PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_1);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error);
     __Pyx_INCREF(__pyx_v_end_deletion);
     __Pyx_GIVEREF(__pyx_v_end_deletion);
-    PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 105, __pyx_L1_error);
     __pyx_t_1 = 0;
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_8, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 104, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_all_deletion_positions, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 104, __pyx_L1_error)
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_all_deletion_positions, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 105, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":105
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":106
  *         end_deletion = ref_positions[seq_len - 1]
  *         all_deletion_positions.extend(range(start_deletion, end_deletion))
- *         if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))             # <<<<<<<<<<<<<<
+ *         if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *             deletion_positions.extend(range(start_deletion, end_deletion))
- *             deletion_coordinates.append((start_deletion, end_deletion))
  */
-    __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 105, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_8);
+    __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 106, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
     __Pyx_GIVEREF(__pyx_t_1);
-    PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_1);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error);
     __Pyx_INCREF(__pyx_v_end_deletion);
     __Pyx_GIVEREF(__pyx_v_end_deletion);
-    PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_end_deletion);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 106, __pyx_L1_error);
     __pyx_t_1 = 0;
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_8, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_deletion_coordinates, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 106, __pyx_L1_error)
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":107
+ *         all_deletion_positions.extend(range(start_deletion, end_deletion))
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))
+ *         if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
+ *             deletion_positions.extend(range(start_deletion, end_deletion))
+ *             deletion_coordinates.append((start_deletion, end_deletion))
+ */
+    __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 107, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 107, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    __pyx_t_8 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 105, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_8);
+    __Pyx_GIVEREF(__pyx_t_5);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_5)) __PYX_ERR(0, 107, __pyx_L1_error);
+    __Pyx_INCREF(__pyx_v_end_deletion);
+    __Pyx_GIVEREF(__pyx_v_end_deletion);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 107, __pyx_L1_error);
+    __pyx_t_5 = 0;
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 107, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 107, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_1); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 107, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_8); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 105, __pyx_L1_error)
-    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":106
- *         all_deletion_positions.extend(range(start_deletion, end_deletion))
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":108
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))
  *         if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *             deletion_positions.extend(range(start_deletion, end_deletion))             # <<<<<<<<<<<<<<
  *             deletion_coordinates.append((start_deletion, end_deletion))
  *             deletion_sizes.append(end_deletion - start_deletion)
  */
-      __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 106, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_8);
-      __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
+      __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 108, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __Pyx_GIVEREF(__pyx_t_8);
-      PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_8);
+      __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 108, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_GIVEREF(__pyx_t_1);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1)) __PYX_ERR(0, 108, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_end_deletion);
       __Pyx_GIVEREF(__pyx_v_end_deletion);
-      PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_end_deletion);
-      __pyx_t_8 = 0;
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_1, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 106, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_8);
-      __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-      __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 106, __pyx_L1_error)
-      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 108, __pyx_L1_error);
+      __pyx_t_1 = 0;
+      __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 108, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_1);
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+      __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 108, __pyx_L1_error)
+      __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":107
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":109
  *         if include_indx_set.intersection(range(start_deletion, end_deletion)):
  *             deletion_positions.extend(range(start_deletion, end_deletion))
  *             deletion_coordinates.append((start_deletion, end_deletion))             # <<<<<<<<<<<<<<
  *             deletion_sizes.append(end_deletion - start_deletion)
  * 
  */
-      __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 107, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_8);
-      __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 107, __pyx_L1_error)
+      __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 109, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __Pyx_GIVEREF(__pyx_t_8);
-      PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_8);
+      __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 109, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_GIVEREF(__pyx_t_1);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1)) __PYX_ERR(0, 109, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_end_deletion);
       __Pyx_GIVEREF(__pyx_v_end_deletion);
-      PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_end_deletion);
-      __pyx_t_8 = 0;
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 107, __pyx_L1_error)
-      __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_end_deletion)) __PYX_ERR(0, 109, __pyx_L1_error);
+      __pyx_t_1 = 0;
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 109, __pyx_L1_error)
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":108
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":110
  *             deletion_positions.extend(range(start_deletion, end_deletion))
  *             deletion_coordinates.append((start_deletion, end_deletion))
  *             deletion_sizes.append(end_deletion - start_deletion)             # <<<<<<<<<<<<<<
  * 
  *     cdef size_t substitution_n = len(substitution_positions)
  */
-      __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 108, __pyx_L1_error)
+      __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_start_deletion); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 110, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __pyx_t_1 = PyNumber_Subtract(__pyx_v_end_deletion, __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 110, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __pyx_t_8 = PyNumber_Subtract(__pyx_v_end_deletion, __pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 108, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 110, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 108, __pyx_L1_error)
-      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":105
- *         end_deletion = ref_positions[seq_len - 1]
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":107
  *         all_deletion_positions.extend(range(start_deletion, end_deletion))
+ *         all_deletion_coordinates.append((start_deletion, end_deletion))
  *         if include_indx_set.intersection(range(start_deletion, end_deletion)):             # <<<<<<<<<<<<<<
  *             deletion_positions.extend(range(start_deletion, end_deletion))
  *             deletion_coordinates.append((start_deletion, end_deletion))
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":102
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":103
  *             start_deletion = -1
  * 
  *     if start_deletion != -1:             # <<<<<<<<<<<<<<
@@ -5729,43 +6009,43 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
  */
   }
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":110
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":112
  *             deletion_sizes.append(end_deletion - start_deletion)
  * 
  *     cdef size_t substitution_n = len(substitution_positions)             # <<<<<<<<<<<<<<
  *     cdef size_t deletion_n = sum(deletion_sizes)
  *     cdef size_t insertion_n = sum(insertion_sizes)
  */
-  __pyx_t_2 = PyList_GET_SIZE(__pyx_v_substitution_positions); if (unlikely(__pyx_t_2 == ((Py_ssize_t)-1))) __PYX_ERR(0, 110, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyList_GET_SIZE(__pyx_v_substitution_positions); if (unlikely(__pyx_t_2 == ((Py_ssize_t)-1))) __PYX_ERR(0, 112, __pyx_L1_error)
   __pyx_v_substitution_n = __pyx_t_2;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":111
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":113
  * 
  *     cdef size_t substitution_n = len(substitution_positions)
  *     cdef size_t deletion_n = sum(deletion_sizes)             # <<<<<<<<<<<<<<
  *     cdef size_t insertion_n = sum(insertion_sizes)
  * 
  */
-  __pyx_t_8 = __Pyx_PyObject_CallOneArg(__pyx_builtin_sum, __pyx_v_deletion_sizes); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 111, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_8);
-  __pyx_t_12 = __Pyx_PyInt_As_size_t(__pyx_t_8); if (unlikely((__pyx_t_12 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 111, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+  __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_sum, __pyx_v_deletion_sizes); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 113, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_t_12 = __Pyx_PyInt_As_size_t(__pyx_t_1); if (unlikely((__pyx_t_12 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 113, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_v_deletion_n = __pyx_t_12;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":112
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":114
  *     cdef size_t substitution_n = len(substitution_positions)
  *     cdef size_t deletion_n = sum(deletion_sizes)
  *     cdef size_t insertion_n = sum(insertion_sizes)             # <<<<<<<<<<<<<<
  * 
  *     return {
  */
-  __pyx_t_8 = __Pyx_PyObject_CallOneArg(__pyx_builtin_sum, __pyx_v_insertion_sizes); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 112, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_8);
-  __pyx_t_12 = __Pyx_PyInt_As_size_t(__pyx_t_8); if (unlikely((__pyx_t_12 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 112, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+  __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_sum, __pyx_v_insertion_sizes); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 114, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_t_12 = __Pyx_PyInt_As_size_t(__pyx_t_1); if (unlikely((__pyx_t_12 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 114, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_v_insertion_n = __pyx_t_12;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":114
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":116
  *     cdef size_t insertion_n = sum(insertion_sizes)
  * 
  *     return {             # <<<<<<<<<<<<<<
@@ -5774,226 +6054,239 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
  */
   __Pyx_XDECREF(__pyx_r);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":115
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":117
  * 
  *     return {
  *         'all_insertion_positions': all_insertion_positions,             # <<<<<<<<<<<<<<
  *         'all_insertion_left_positions': all_insertion_left_positions,
  *         'insertion_positions': insertion_positions,
  */
-  __pyx_t_8 = __Pyx_PyDict_NewPresized(17); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_8);
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_all_insertion_positions, __pyx_v_all_insertion_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyDict_NewPresized(18); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_positions, __pyx_v_all_insertion_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":116
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":118
  *     return {
  *         'all_insertion_positions': all_insertion_positions,
  *         'all_insertion_left_positions': all_insertion_left_positions,             # <<<<<<<<<<<<<<
  *         'insertion_positions': insertion_positions,
  *         'insertion_coordinates': insertion_coordinates,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_all_insertion_left_positions, __pyx_v_all_insertion_left_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_left_positions, __pyx_v_all_insertion_left_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":117
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":119
  *         'all_insertion_positions': all_insertion_positions,
  *         'all_insertion_left_positions': all_insertion_left_positions,
  *         'insertion_positions': insertion_positions,             # <<<<<<<<<<<<<<
  *         'insertion_coordinates': insertion_coordinates,
  *         'insertion_sizes': insertion_sizes,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_insertion_positions, __pyx_v_insertion_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_positions, __pyx_v_insertion_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":118
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":120
  *         'all_insertion_left_positions': all_insertion_left_positions,
  *         'insertion_positions': insertion_positions,
  *         'insertion_coordinates': insertion_coordinates,             # <<<<<<<<<<<<<<
  *         'insertion_sizes': insertion_sizes,
  *         'insertion_n': insertion_n,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_insertion_coordinates, __pyx_v_insertion_coordinates) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_coordinates, __pyx_v_insertion_coordinates) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":119
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":121
  *         'insertion_positions': insertion_positions,
  *         'insertion_coordinates': insertion_coordinates,
  *         'insertion_sizes': insertion_sizes,             # <<<<<<<<<<<<<<
  *         'insertion_n': insertion_n,
  * 
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_insertion_sizes, __pyx_v_insertion_sizes) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_sizes, __pyx_v_insertion_sizes) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":120
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":122
  *         'insertion_coordinates': insertion_coordinates,
  *         'insertion_sizes': insertion_sizes,
  *         'insertion_n': insertion_n,             # <<<<<<<<<<<<<<
  * 
  *         'all_deletion_positions': all_deletion_positions,
  */
-  __pyx_t_1 = __Pyx_PyInt_FromSize_t(__pyx_v_insertion_n); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 120, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_insertion_n, __pyx_t_1) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+  __pyx_t_5 = __Pyx_PyInt_FromSize_t(__pyx_v_insertion_n); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 122, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_n, __pyx_t_5) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":122
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":124
  *         'insertion_n': insertion_n,
  * 
  *         'all_deletion_positions': all_deletion_positions,             # <<<<<<<<<<<<<<
+ *         'all_deletion_coordinates': all_deletion_coordinates,
  *         'deletion_positions': deletion_positions,
- *         'deletion_coordinates': deletion_coordinates,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_all_deletion_positions, __pyx_v_all_deletion_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_deletion_positions, __pyx_v_all_deletion_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":123
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":125
  * 
  *         'all_deletion_positions': all_deletion_positions,
+ *         'all_deletion_coordinates': all_deletion_coordinates,             # <<<<<<<<<<<<<<
+ *         'deletion_positions': deletion_positions,
+ *         'deletion_coordinates': deletion_coordinates,
+ */
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_deletion_coordinates, __pyx_v_all_deletion_coordinates) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":126
+ *         'all_deletion_positions': all_deletion_positions,
+ *         'all_deletion_coordinates': all_deletion_coordinates,
  *         'deletion_positions': deletion_positions,             # <<<<<<<<<<<<<<
  *         'deletion_coordinates': deletion_coordinates,
  *         'deletion_sizes': deletion_sizes,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_deletion_positions, __pyx_v_deletion_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_positions, __pyx_v_deletion_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":124
- *         'all_deletion_positions': all_deletion_positions,
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":127
+ *         'all_deletion_coordinates': all_deletion_coordinates,
  *         'deletion_positions': deletion_positions,
  *         'deletion_coordinates': deletion_coordinates,             # <<<<<<<<<<<<<<
  *         'deletion_sizes': deletion_sizes,
  *         'deletion_n': deletion_n,
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_deletion_coordinates, __pyx_v_deletion_coordinates) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_coordinates, __pyx_v_deletion_coordinates) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":125
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":128
  *         'deletion_positions': deletion_positions,
  *         'deletion_coordinates': deletion_coordinates,
  *         'deletion_sizes': deletion_sizes,             # <<<<<<<<<<<<<<
  *         'deletion_n': deletion_n,
  * 
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_deletion_sizes, __pyx_v_deletion_sizes) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_sizes, __pyx_v_deletion_sizes) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":126
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":129
  *         'deletion_coordinates': deletion_coordinates,
  *         'deletion_sizes': deletion_sizes,
  *         'deletion_n': deletion_n,             # <<<<<<<<<<<<<<
  * 
  *         'all_substitution_positions': all_substitution_positions,
  */
-  __pyx_t_1 = __Pyx_PyInt_FromSize_t(__pyx_v_deletion_n); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 126, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_deletion_n, __pyx_t_1) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+  __pyx_t_5 = __Pyx_PyInt_FromSize_t(__pyx_v_deletion_n); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 129, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_n, __pyx_t_5) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":128
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":131
  *         'deletion_n': deletion_n,
  * 
  *         'all_substitution_positions': all_substitution_positions,             # <<<<<<<<<<<<<<
  *         'substitution_positions': substitution_positions,
  *         'all_substitution_values': np.array(all_substitution_values),
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_all_substitution_positions, __pyx_v_all_substitution_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_positions, __pyx_v_all_substitution_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":129
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":132
  * 
  *         'all_substitution_positions': all_substitution_positions,
  *         'substitution_positions': substitution_positions,             # <<<<<<<<<<<<<<
  *         'all_substitution_values': np.array(all_substitution_values),
  *         'substitution_values': np.array(substitution_values),
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_substitution_positions, __pyx_v_substitution_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_positions, __pyx_v_substitution_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":130
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":133
  *         'all_substitution_positions': all_substitution_positions,
  *         'substitution_positions': substitution_positions,
  *         'all_substitution_values': np.array(all_substitution_values),             # <<<<<<<<<<<<<<
  *         'substitution_values': np.array(substitution_values),
  *         'substitution_n': substitution_n,
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 130, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_5);
-  __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_array); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 130, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_np); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 133, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_8);
+  __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_t_8, __pyx_n_s_array); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 133, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-  __pyx_t_5 = NULL;
+  __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+  __pyx_t_8 = NULL;
   __pyx_t_3 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_9))) {
-    __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_9);
-    if (likely(__pyx_t_5)) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_9))) {
+    __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_9);
+    if (likely(__pyx_t_8)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_9);
-      __Pyx_INCREF(__pyx_t_5);
+      __Pyx_INCREF(__pyx_t_8);
       __Pyx_INCREF(function);
       __Pyx_DECREF_SET(__pyx_t_9, function);
       __pyx_t_3 = 1;
     }
   }
+  #endif
   {
-    PyObject *__pyx_callargs[2] = {__pyx_t_5, __pyx_v_all_substitution_values};
-    __pyx_t_1 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_3, 1+__pyx_t_3);
-    __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 130, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_1);
+    PyObject *__pyx_callargs[2] = {__pyx_t_8, __pyx_v_all_substitution_values};
+    __pyx_t_5 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_3, 1+__pyx_t_3);
+    __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
+    if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 133, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
     __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   }
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_all_substitution_values, __pyx_t_1) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_values, __pyx_t_5) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":131
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":134
  *         'substitution_positions': substitution_positions,
  *         'all_substitution_values': np.array(all_substitution_values),
  *         'substitution_values': np.array(substitution_values),             # <<<<<<<<<<<<<<
  *         'substitution_n': substitution_n,
- * 
+ *         'ref_positions': ref_positions,
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_np); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 131, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_np); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 134, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_array); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 131, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_array); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 134, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_8);
   __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   __pyx_t_9 = NULL;
   __pyx_t_3 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_5))) {
-    __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_5);
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_8))) {
+    __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_8);
     if (likely(__pyx_t_9)) {
-      PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
+      PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
       __Pyx_INCREF(__pyx_t_9);
       __Pyx_INCREF(function);
-      __Pyx_DECREF_SET(__pyx_t_5, function);
+      __Pyx_DECREF_SET(__pyx_t_8, function);
       __pyx_t_3 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_9, __pyx_v_substitution_values};
-    __pyx_t_1 = __Pyx_PyObject_FastCall(__pyx_t_5, __pyx_callargs+1-__pyx_t_3, 1+__pyx_t_3);
+    __pyx_t_5 = __Pyx_PyObject_FastCall(__pyx_t_8, __pyx_callargs+1-__pyx_t_3, 1+__pyx_t_3);
     __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 131, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 134, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   }
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_substitution_values, __pyx_t_1) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_values, __pyx_t_5) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":132
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":135
  *         'all_substitution_values': np.array(all_substitution_values),
  *         'substitution_values': np.array(substitution_values),
  *         'substitution_n': substitution_n,             # <<<<<<<<<<<<<<
- * 
  *         'ref_positions': ref_positions,
+ *     }
  */
-  __pyx_t_1 = __Pyx_PyInt_FromSize_t(__pyx_v_substitution_n); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 132, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_substitution_n, __pyx_t_1) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+  __pyx_t_5 = __Pyx_PyInt_FromSize_t(__pyx_v_substitution_n); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 135, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_n, __pyx_t_5) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":134
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":136
+ *         'substitution_values': np.array(substitution_values),
  *         'substitution_n': substitution_n,
- * 
  *         'ref_positions': ref_positions,             # <<<<<<<<<<<<<<
  *     }
- * 
+ * @cython.boundscheck(False)
  */
-  if (PyDict_SetItem(__pyx_t_8, __pyx_n_u_ref_positions, __pyx_v_ref_positions) < 0) __PYX_ERR(0, 115, __pyx_L1_error)
-  __pyx_r = __pyx_t_8;
-  __pyx_t_8 = 0;
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_ref_positions, __pyx_v_ref_positions) < 0) __PYX_ERR(0, 117, __pyx_L1_error)
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
   goto __pyx_L0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":19
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":18
  * re_find_indels = re.compile("(-*-)")
  * 
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
@@ -6016,6 +6309,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
   __Pyx_XDECREF(__pyx_v_all_substitution_values);
   __Pyx_XDECREF(__pyx_v_substitution_values);
   __Pyx_XDECREF(__pyx_v_all_deletion_positions);
+  __Pyx_XDECREF(__pyx_v_all_deletion_coordinates);
   __Pyx_XDECREF(__pyx_v_deletion_positions);
   __Pyx_XDECREF(__pyx_v_deletion_coordinates);
   __Pyx_XDECREF(__pyx_v_deletion_sizes);
@@ -6034,8 +6328,8 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_find_indels_su
 }
 
 /* "CRISPResso2/CRISPRessoCOREResources.pyx":138
- * 
- * 
+ *         'ref_positions': ref_positions,
+ *     }
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
@@ -6061,18 +6355,26 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   PyObject *__pyx_v_ref_seq_al = 0;
   PyObject *__pyx_v__include_indx = 0;
   #if !CYTHON_METH_FASTCALL
-  CYTHON_UNUSED const Py_ssize_t __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
   #endif
-  CYTHON_UNUSED PyObject *const *__pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
+  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
+  PyObject* values[3] = {0,0,0};
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
   PyObject *__pyx_r = 0;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("find_indels_substitutions_legacy (wrapper)", 0);
+  #if !CYTHON_METH_FASTCALL
+  #if CYTHON_ASSUME_SAFE_MACROS
+  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  #else
+  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
+  #endif
+  #endif
+  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
   {
     PyObject **__pyx_pyargnames[] = {&__pyx_n_s_read_seq_al,&__pyx_n_s_ref_seq_al,&__pyx_n_s_include_indx,0};
-    PyObject* values[3] = {0,0,0};
     if (__pyx_kwds) {
       Py_ssize_t kw_args;
       switch (__pyx_nargs) {
@@ -6088,19 +6390,28 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
       kw_args = __Pyx_NumKwargs_FASTCALL(__pyx_kwds);
       switch (__pyx_nargs) {
         case  0:
-        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_read_seq_al)) != 0)) kw_args--;
+        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_read_seq_al)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[0]);
+          kw_args--;
+        }
         else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 138, __pyx_L3_error)
         else goto __pyx_L5_argtuple_error;
         CYTHON_FALLTHROUGH;
         case  1:
-        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_ref_seq_al)) != 0)) kw_args--;
+        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_ref_seq_al)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[1]);
+          kw_args--;
+        }
         else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 138, __pyx_L3_error)
         else {
           __Pyx_RaiseArgtupleInvalid("find_indels_substitutions_legacy", 1, 3, 3, 1); __PYX_ERR(0, 138, __pyx_L3_error)
         }
         CYTHON_FALLTHROUGH;
         case  2:
-        if (likely((values[2] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_include_indx)) != 0)) kw_args--;
+        if (likely((values[2] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_include_indx)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[2]);
+          kw_args--;
+        }
         else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 138, __pyx_L3_error)
         else {
           __Pyx_RaiseArgtupleInvalid("find_indels_substitutions_legacy", 1, 3, 3, 2); __PYX_ERR(0, 138, __pyx_L3_error)
@@ -6121,10 +6432,18 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
     __pyx_v_ref_seq_al = values[1];
     __pyx_v__include_indx = values[2];
   }
-  goto __pyx_L4_argument_unpacking_done;
+  goto __pyx_L6_skip;
   __pyx_L5_argtuple_error:;
   __Pyx_RaiseArgtupleInvalid("find_indels_substitutions_legacy", 1, 3, 3, __pyx_nargs); __PYX_ERR(0, 138, __pyx_L3_error)
+  __pyx_L6_skip:;
+  goto __pyx_L4_argument_unpacking_done;
   __pyx_L3_error:;
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_AddTraceback("CRISPResso2.CRISPRessoCOREResources.find_indels_substitutions_legacy", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return NULL;
@@ -6132,6 +6451,12 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   __pyx_r = __pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_substitutions_legacy(__pyx_self, __pyx_v_read_seq_al, __pyx_v_ref_seq_al, __pyx_v__include_indx);
 
   /* function exit code */
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_RefNannyFinishContext();
   return __pyx_r;
 }
@@ -6153,6 +6478,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   PyObject *__pyx_v_all_deletion_positions = NULL;
   PyObject *__pyx_v_deletion_positions = NULL;
   PyObject *__pyx_v_deletion_coordinates = NULL;
+  PyObject *__pyx_v_all_deletion_coordinates = NULL;
   PyObject *__pyx_v_deletion_sizes = NULL;
   PyObject *__pyx_v_all_insertion_positions = NULL;
   PyObject *__pyx_v_all_insertion_left_positions = NULL;
@@ -6187,9 +6513,9 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("find_indels_substitutions_legacy", 0);
+  __Pyx_RefNannySetupContext("find_indels_substitutions_legacy", 1);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":143
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":145
  * def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
  * 
  *     cdef char* sub_seq=''             # <<<<<<<<<<<<<<
@@ -6198,85 +6524,85 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
   __pyx_v_sub_seq = ((char *)"");
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":160
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":162
  *     # aln A - T T T G G C C
  *     #     1 2 3 4-4 5 6 7 8 <ref positions. Note that the negative values/indices represent places that don't map back to the original reference
  *     ref_positions=[]             # <<<<<<<<<<<<<<
  *     all_substitution_positions=[]
  *     substitution_positions=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 160, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 162, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_ref_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":161
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":163
  *     #     1 2 3 4-4 5 6 7 8 <ref positions. Note that the negative values/indices represent places that don't map back to the original reference
  *     ref_positions=[]
  *     all_substitution_positions=[]             # <<<<<<<<<<<<<<
  *     substitution_positions=[]
  *     all_substitution_values=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 161, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 163, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_substitution_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":162
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":164
  *     ref_positions=[]
  *     all_substitution_positions=[]
  *     substitution_positions=[]             # <<<<<<<<<<<<<<
  *     all_substitution_values=[]
  *     substitution_values=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 162, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_substitution_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":163
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":165
  *     all_substitution_positions=[]
  *     substitution_positions=[]
  *     all_substitution_values=[]             # <<<<<<<<<<<<<<
  *     substitution_values=[]
- * 
+ *     nucSet = set(['A', 'T', 'C', 'G', 'N'])
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 163, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 165, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_substitution_values = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":164
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":166
  *     substitution_positions=[]
  *     all_substitution_values=[]
  *     substitution_values=[]             # <<<<<<<<<<<<<<
- * 
  *     nucSet = set(['A', 'T', 'C', 'G', 'N'])
+ *     idx=0
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 166, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_substitution_values = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":166
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":167
+ *     all_substitution_values=[]
  *     substitution_values=[]
- * 
  *     nucSet = set(['A', 'T', 'C', 'G', 'N'])             # <<<<<<<<<<<<<<
  *     idx=0
  *     for idx_c, c in enumerate(ref_seq_al):
  */
-  __pyx_t_1 = PySet_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 166, __pyx_L1_error)
+  __pyx_t_1 = PySet_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 167, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PySet_Add(__pyx_t_1, __pyx_n_u_A) < 0) __PYX_ERR(0, 166, __pyx_L1_error)
-  if (PySet_Add(__pyx_t_1, __pyx_n_u_T) < 0) __PYX_ERR(0, 166, __pyx_L1_error)
-  if (PySet_Add(__pyx_t_1, __pyx_n_u_C) < 0) __PYX_ERR(0, 166, __pyx_L1_error)
-  if (PySet_Add(__pyx_t_1, __pyx_n_u_G) < 0) __PYX_ERR(0, 166, __pyx_L1_error)
-  if (PySet_Add(__pyx_t_1, __pyx_n_u_N) < 0) __PYX_ERR(0, 166, __pyx_L1_error)
+  if (PySet_Add(__pyx_t_1, __pyx_n_u_A) < 0) __PYX_ERR(0, 167, __pyx_L1_error)
+  if (PySet_Add(__pyx_t_1, __pyx_n_u_T) < 0) __PYX_ERR(0, 167, __pyx_L1_error)
+  if (PySet_Add(__pyx_t_1, __pyx_n_u_C) < 0) __PYX_ERR(0, 167, __pyx_L1_error)
+  if (PySet_Add(__pyx_t_1, __pyx_n_u_G) < 0) __PYX_ERR(0, 167, __pyx_L1_error)
+  if (PySet_Add(__pyx_t_1, __pyx_n_u_N) < 0) __PYX_ERR(0, 167, __pyx_L1_error)
   __pyx_v_nucSet = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":167
- * 
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":168
+ *     substitution_values=[]
  *     nucSet = set(['A', 'T', 'C', 'G', 'N'])
  *     idx=0             # <<<<<<<<<<<<<<
  *     for idx_c, c in enumerate(ref_seq_al):
@@ -6284,7 +6610,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
   __pyx_v_idx = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":168
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":169
  *     nucSet = set(['A', 'T', 'C', 'G', 'N'])
  *     idx=0
  *     for idx_c, c in enumerate(ref_seq_al):             # <<<<<<<<<<<<<<
@@ -6293,29 +6619,42 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
   __pyx_t_2 = 0;
   if (likely(PyList_CheckExact(__pyx_v_ref_seq_al)) || PyTuple_CheckExact(__pyx_v_ref_seq_al)) {
-    __pyx_t_1 = __pyx_v_ref_seq_al; __Pyx_INCREF(__pyx_t_1); __pyx_t_3 = 0;
+    __pyx_t_1 = __pyx_v_ref_seq_al; __Pyx_INCREF(__pyx_t_1);
+    __pyx_t_3 = 0;
     __pyx_t_4 = NULL;
   } else {
-    __pyx_t_3 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_v_ref_seq_al); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 168, __pyx_L1_error)
+    __pyx_t_3 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_v_ref_seq_al); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 169, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 168, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 169, __pyx_L1_error)
   }
   for (;;) {
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
-        if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 169, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_5 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_5); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 168, __pyx_L1_error)
+        __pyx_t_5 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_5); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 169, __pyx_L1_error)
         #else
-        __pyx_t_5 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 168, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 169, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
         #endif
       } else {
-        if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyTuple_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 169, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_5 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_5); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 168, __pyx_L1_error)
+        __pyx_t_5 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_5); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 169, __pyx_L1_error)
         #else
-        __pyx_t_5 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 168, __pyx_L1_error)
+        __pyx_t_5 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 169, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_5);
         #endif
       }
@@ -6325,7 +6664,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         PyObject* exc_type = PyErr_Occurred();
         if (exc_type) {
           if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
-          else __PYX_ERR(0, 168, __pyx_L1_error)
+          else __PYX_ERR(0, 169, __pyx_L1_error)
         }
         break;
       }
@@ -6336,128 +6675,128 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __pyx_v_idx_c = __pyx_t_2;
     __pyx_t_2 = (__pyx_t_2 + 1);
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":169
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":170
  *     idx=0
  *     for idx_c, c in enumerate(ref_seq_al):
  *         if c in nucSet:             # <<<<<<<<<<<<<<
  *             ref_positions.append(idx)
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':
  */
-    __pyx_t_6 = (__Pyx_PySet_ContainsTF(__pyx_v_c, __pyx_v_nucSet, Py_EQ)); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 169, __pyx_L1_error)
+    __pyx_t_6 = (__Pyx_PySet_ContainsTF(__pyx_v_c, __pyx_v_nucSet, Py_EQ)); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 170, __pyx_L1_error)
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":170
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":171
  *     for idx_c, c in enumerate(ref_seq_al):
  *         if c in nucSet:
  *             ref_positions.append(idx)             # <<<<<<<<<<<<<<
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':
  *                 all_substitution_positions.append(idx)
  */
-      __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 170, __pyx_L1_error)
+      __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 171, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 170, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 171, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":171
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":172
  *         if c in nucSet:
  *             ref_positions.append(idx)
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':             # <<<<<<<<<<<<<<
  *                 all_substitution_positions.append(idx)
  *                 all_substitution_values.append(read_seq_al[idx_c])
  */
-      __pyx_t_5 = __Pyx_GetItemInt(__pyx_v_ref_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_5 = __Pyx_GetItemInt(__pyx_v_ref_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
-      __pyx_t_8 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_8 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
-      __pyx_t_9 = PyObject_RichCompare(__pyx_t_5, __pyx_t_8, Py_NE); __Pyx_XGOTREF(__pyx_t_9); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_9 = PyObject_RichCompare(__pyx_t_5, __pyx_t_8, Py_NE); __Pyx_XGOTREF(__pyx_t_9); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __pyx_t_10 = __Pyx_PyObject_IsTrue(__pyx_t_9); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_10 = __Pyx_PyObject_IsTrue(__pyx_t_9); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       if (__pyx_t_10) {
       } else {
         __pyx_t_6 = __pyx_t_10;
         goto __pyx_L7_bool_binop_done;
       }
-      __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
-      __pyx_t_10 = (__Pyx_PyUnicode_Equals(__pyx_t_9, __pyx_kp_u__3, Py_NE)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_10 = (__Pyx_PyUnicode_Equals(__pyx_t_9, __pyx_kp_u__3, Py_NE)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       if (__pyx_t_10) {
       } else {
         __pyx_t_6 = __pyx_t_10;
         goto __pyx_L7_bool_binop_done;
       }
-      __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
-      __pyx_t_10 = (__Pyx_PyUnicode_Equals(__pyx_t_9, __pyx_n_u_N, Py_NE)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 171, __pyx_L1_error)
+      __pyx_t_10 = (__Pyx_PyUnicode_Equals(__pyx_t_9, __pyx_n_u_N, Py_NE)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 172, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       __pyx_t_6 = __pyx_t_10;
       __pyx_L7_bool_binop_done:;
       if (__pyx_t_6) {
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":172
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":173
  *             ref_positions.append(idx)
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':
  *                 all_substitution_positions.append(idx)             # <<<<<<<<<<<<<<
  *                 all_substitution_values.append(read_seq_al[idx_c])
  *                 if idx in _include_indx:
  */
-        __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 172, __pyx_L1_error)
+        __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 173, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_9);
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_substitution_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 172, __pyx_L1_error)
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_substitution_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 173, __pyx_L1_error)
         __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":173
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":174
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':
  *                 all_substitution_positions.append(idx)
  *                 all_substitution_values.append(read_seq_al[idx_c])             # <<<<<<<<<<<<<<
  *                 if idx in _include_indx:
  *                     substitution_positions.append(idx)
  */
-        __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 173, __pyx_L1_error)
+        __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 174, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_9);
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_substitution_values, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 173, __pyx_L1_error)
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_substitution_values, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 174, __pyx_L1_error)
         __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":174
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":175
  *                 all_substitution_positions.append(idx)
  *                 all_substitution_values.append(read_seq_al[idx_c])
  *                 if idx in _include_indx:             # <<<<<<<<<<<<<<
  *                     substitution_positions.append(idx)
  *                     substitution_values.append(read_seq_al[idx_c])
  */
-        __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 174, __pyx_L1_error)
+        __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 175, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_9);
-        __pyx_t_6 = (__Pyx_PySequence_ContainsTF(__pyx_t_9, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 174, __pyx_L1_error)
+        __pyx_t_6 = (__Pyx_PySequence_ContainsTF(__pyx_t_9, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_6 < 0))) __PYX_ERR(0, 175, __pyx_L1_error)
         __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
         if (__pyx_t_6) {
 
-          /* "CRISPResso2/CRISPRessoCOREResources.pyx":175
+          /* "CRISPResso2/CRISPRessoCOREResources.pyx":176
  *                 all_substitution_values.append(read_seq_al[idx_c])
  *                 if idx in _include_indx:
  *                     substitution_positions.append(idx)             # <<<<<<<<<<<<<<
  *                     substitution_values.append(read_seq_al[idx_c])
  * 
  */
-          __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 175, __pyx_L1_error)
+          __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_idx); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 176, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_9);
-          __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_substitution_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 175, __pyx_L1_error)
+          __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_substitution_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 176, __pyx_L1_error)
           __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-          /* "CRISPResso2/CRISPRessoCOREResources.pyx":176
+          /* "CRISPResso2/CRISPRessoCOREResources.pyx":177
  *                 if idx in _include_indx:
  *                     substitution_positions.append(idx)
  *                     substitution_values.append(read_seq_al[idx_c])             # <<<<<<<<<<<<<<
  * 
  *             idx+=1
  */
-          __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 176, __pyx_L1_error)
+          __pyx_t_9 = __Pyx_GetItemInt(__pyx_v_read_seq_al, __pyx_v_idx_c, int, 1, __Pyx_PyInt_From_int, 0, 0, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 177, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_9);
-          __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_substitution_values, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 176, __pyx_L1_error)
+          __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_substitution_values, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 177, __pyx_L1_error)
           __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-          /* "CRISPResso2/CRISPRessoCOREResources.pyx":174
+          /* "CRISPResso2/CRISPRessoCOREResources.pyx":175
  *                 all_substitution_positions.append(idx)
  *                 all_substitution_values.append(read_seq_al[idx_c])
  *                 if idx in _include_indx:             # <<<<<<<<<<<<<<
@@ -6466,7 +6805,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
         }
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":171
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":172
  *         if c in nucSet:
  *             ref_positions.append(idx)
  *             if ref_seq_al[idx_c]!=read_seq_al[idx_c] and read_seq_al[idx_c] != '-' and read_seq_al[idx_c] != 'N':             # <<<<<<<<<<<<<<
@@ -6475,7 +6814,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
       }
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":178
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":179
  *                     substitution_values.append(read_seq_al[idx_c])
  * 
  *             idx+=1             # <<<<<<<<<<<<<<
@@ -6484,7 +6823,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
       __pyx_v_idx = (__pyx_v_idx + 1);
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":169
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":170
  *     idx=0
  *     for idx_c, c in enumerate(ref_seq_al):
  *         if c in nucSet:             # <<<<<<<<<<<<<<
@@ -6494,7 +6833,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       goto __pyx_L5;
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":181
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":182
  * 
  *         else:
  *             if idx==0:             # <<<<<<<<<<<<<<
@@ -6505,16 +6844,16 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_6 = (__pyx_v_idx == 0);
       if (__pyx_t_6) {
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":182
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":183
  *         else:
  *             if idx==0:
  *                 ref_positions.append(-1)             # <<<<<<<<<<<<<<
  *             else:
  *                 ref_positions.append(-idx)
  */
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_int_neg_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 182, __pyx_L1_error)
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_int_neg_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 183, __pyx_L1_error)
 
-        /* "CRISPResso2/CRISPRessoCOREResources.pyx":181
+        /* "CRISPResso2/CRISPRessoCOREResources.pyx":182
  * 
  *         else:
  *             if idx==0:             # <<<<<<<<<<<<<<
@@ -6524,7 +6863,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         goto __pyx_L11;
       }
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":184
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":185
  *                 ref_positions.append(-1)
  *             else:
  *                 ref_positions.append(-idx)             # <<<<<<<<<<<<<<
@@ -6532,16 +6871,16 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  *     substitution_n = len(substitution_positions)
  */
       /*else*/ {
-        __pyx_t_9 = __Pyx_PyInt_From_int((-__pyx_v_idx)); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 184, __pyx_L1_error)
+        __pyx_t_9 = __Pyx_PyInt_From_int((-__pyx_v_idx)); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 185, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_9);
-        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 184, __pyx_L1_error)
+        __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_ref_positions, __pyx_t_9); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 185, __pyx_L1_error)
         __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       }
       __pyx_L11:;
     }
     __pyx_L5:;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":168
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":169
  *     nucSet = set(['A', 'T', 'C', 'G', 'N'])
  *     idx=0
  *     for idx_c, c in enumerate(ref_seq_al):             # <<<<<<<<<<<<<<
@@ -6551,151 +6890,164 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   }
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":186
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":187
  *                 ref_positions.append(-idx)
  * 
  *     substitution_n = len(substitution_positions)             # <<<<<<<<<<<<<<
  * 
  *     #the remainder of positions are with reference to the original reference sequence indexes we calculated above
  */
-  __pyx_t_3 = PyList_GET_SIZE(__pyx_v_substitution_positions); if (unlikely(__pyx_t_3 == ((Py_ssize_t)-1))) __PYX_ERR(0, 186, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyList_GET_SIZE(__pyx_v_substitution_positions); if (unlikely(__pyx_t_3 == ((Py_ssize_t)-1))) __PYX_ERR(0, 187, __pyx_L1_error)
   __pyx_v_substitution_n = __pyx_t_3;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":189
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":190
  * 
  *     #the remainder of positions are with reference to the original reference sequence indexes we calculated above
  *     all_deletion_positions=[]             # <<<<<<<<<<<<<<
  *     deletion_positions=[]
  *     deletion_coordinates=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 189, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 190, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_deletion_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":190
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":191
  *     #the remainder of positions are with reference to the original reference sequence indexes we calculated above
  *     all_deletion_positions=[]
  *     deletion_positions=[]             # <<<<<<<<<<<<<<
  *     deletion_coordinates=[]
- *     deletion_sizes=[]
+ *     all_deletion_coordinates=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 190, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 191, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_deletion_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":191
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":192
  *     all_deletion_positions=[]
  *     deletion_positions=[]
  *     deletion_coordinates=[]             # <<<<<<<<<<<<<<
+ *     all_deletion_coordinates=[]
  *     deletion_sizes=[]
- * 
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 191, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 192, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_deletion_coordinates = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":192
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":193
  *     deletion_positions=[]
  *     deletion_coordinates=[]
+ *     all_deletion_coordinates=[]             # <<<<<<<<<<<<<<
+ *     deletion_sizes=[]
+ * 
+ */
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 193, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_v_all_deletion_coordinates = ((PyObject*)__pyx_t_1);
+  __pyx_t_1 = 0;
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":194
+ *     deletion_coordinates=[]
+ *     all_deletion_coordinates=[]
  *     deletion_sizes=[]             # <<<<<<<<<<<<<<
  * 
  *     all_insertion_positions=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 192, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 194, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_deletion_sizes = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":194
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":196
  *     deletion_sizes=[]
  * 
  *     all_insertion_positions=[]             # <<<<<<<<<<<<<<
  *     all_insertion_left_positions=[]
  *     insertion_positions=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 194, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 196, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_insertion_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":195
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":197
  * 
  *     all_insertion_positions=[]
  *     all_insertion_left_positions=[]             # <<<<<<<<<<<<<<
  *     insertion_positions=[]
  *     insertion_coordinates = []
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 195, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 197, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_all_insertion_left_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":196
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":198
  *     all_insertion_positions=[]
  *     all_insertion_left_positions=[]
  *     insertion_positions=[]             # <<<<<<<<<<<<<<
  *     insertion_coordinates = []
  *     insertion_sizes=[]
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 196, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 198, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_insertion_positions = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":197
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":199
  *     all_insertion_left_positions=[]
  *     insertion_positions=[]
  *     insertion_coordinates = []             # <<<<<<<<<<<<<<
  *     insertion_sizes=[]
  * 
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 197, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 199, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_insertion_coordinates = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":198
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":200
  *     insertion_positions=[]
  *     insertion_coordinates = []
  *     insertion_sizes=[]             # <<<<<<<<<<<<<<
  * 
  *     include_indx_set = set(_include_indx)
  */
-  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 198, __pyx_L1_error)
+  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 200, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_insertion_sizes = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":200
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":202
  *     insertion_sizes=[]
  * 
  *     include_indx_set = set(_include_indx)             # <<<<<<<<<<<<<<
  *     for p in re_find_indels.finditer(read_seq_al):
  *         st,en=p.span()
  */
-  __pyx_t_1 = PySet_New(__pyx_v__include_indx); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 200, __pyx_L1_error)
+  __pyx_t_1 = PySet_New(__pyx_v__include_indx); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 202, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_v_include_indx_set = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":201
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":203
  * 
  *     include_indx_set = set(_include_indx)
  *     for p in re_find_indels.finditer(read_seq_al):             # <<<<<<<<<<<<<<
  *         st,en=p.span()
  *         ref_st = 0
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_re_find_indels); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 201, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_re_find_indels); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 203, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_finditer); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 201, __pyx_L1_error)
+  __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_finditer); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 203, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_8);
   __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   __pyx_t_9 = NULL;
   __pyx_t_2 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_8))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_8))) {
     __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_8);
     if (likely(__pyx_t_9)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
@@ -6705,39 +7057,53 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_2 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_9, __pyx_v_read_seq_al};
     __pyx_t_1 = __Pyx_PyObject_FastCall(__pyx_t_8, __pyx_callargs+1-__pyx_t_2, 1+__pyx_t_2);
     __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 201, __pyx_L1_error)
+    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 203, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   }
   if (likely(PyList_CheckExact(__pyx_t_1)) || PyTuple_CheckExact(__pyx_t_1)) {
-    __pyx_t_8 = __pyx_t_1; __Pyx_INCREF(__pyx_t_8); __pyx_t_3 = 0;
+    __pyx_t_8 = __pyx_t_1; __Pyx_INCREF(__pyx_t_8);
+    __pyx_t_3 = 0;
     __pyx_t_4 = NULL;
   } else {
-    __pyx_t_3 = -1; __pyx_t_8 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 201, __pyx_L1_error)
+    __pyx_t_3 = -1; __pyx_t_8 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 203, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
-    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_8); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 201, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_8); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 203, __pyx_L1_error)
   }
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   for (;;) {
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_8))) {
-        if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_8)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_t_8);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 203, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_1 = PyList_GET_ITEM(__pyx_t_8, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 201, __pyx_L1_error)
+        __pyx_t_1 = PyList_GET_ITEM(__pyx_t_8, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 203, __pyx_L1_error)
         #else
-        __pyx_t_1 = PySequence_ITEM(__pyx_t_8, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 201, __pyx_L1_error)
+        __pyx_t_1 = __Pyx_PySequence_ITEM(__pyx_t_8, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 203, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         #endif
       } else {
-        if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_8)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyTuple_GET_SIZE(__pyx_t_8);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 203, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_8, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 201, __pyx_L1_error)
+        __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_8, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 203, __pyx_L1_error)
         #else
-        __pyx_t_1 = PySequence_ITEM(__pyx_t_8, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 201, __pyx_L1_error)
+        __pyx_t_1 = __Pyx_PySequence_ITEM(__pyx_t_8, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 203, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         #endif
       }
@@ -6747,7 +7113,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         PyObject* exc_type = PyErr_Occurred();
         if (exc_type) {
           if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
-          else __PYX_ERR(0, 201, __pyx_L1_error)
+          else __PYX_ERR(0, 203, __pyx_L1_error)
         }
         break;
       }
@@ -6756,18 +7122,19 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __Pyx_XDECREF_SET(__pyx_v_p, __pyx_t_1);
     __pyx_t_1 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":202
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":204
  *     include_indx_set = set(_include_indx)
  *     for p in re_find_indels.finditer(read_seq_al):
  *         st,en=p.span()             # <<<<<<<<<<<<<<
  *         ref_st = 0
  *         if st-1 > 0:
  */
-    __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_p, __pyx_n_s_span); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 202, __pyx_L1_error)
+    __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_v_p, __pyx_n_s_span); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 204, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_9);
     __pyx_t_5 = NULL;
     __pyx_t_2 = 0;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_9))) {
+    #if CYTHON_UNPACK_METHODS
+    if (likely(PyMethod_Check(__pyx_t_9))) {
       __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_9);
       if (likely(__pyx_t_5)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_9);
@@ -6777,11 +7144,12 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         __pyx_t_2 = 1;
       }
     }
+    #endif
     {
-      PyObject *__pyx_callargs[1] = {__pyx_t_5, };
+      PyObject *__pyx_callargs[2] = {__pyx_t_5, NULL};
       __pyx_t_1 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_2, 0+__pyx_t_2);
       __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-      if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 202, __pyx_L1_error)
+      if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 204, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
@@ -6791,7 +7159,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       if (unlikely(size != 2)) {
         if (size > 2) __Pyx_RaiseTooManyValuesError(2);
         else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
-        __PYX_ERR(0, 202, __pyx_L1_error)
+        __PYX_ERR(0, 204, __pyx_L1_error)
       }
       #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
       if (likely(PyTuple_CheckExact(sequence))) {
@@ -6804,15 +7172,15 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_INCREF(__pyx_t_9);
       __Pyx_INCREF(__pyx_t_5);
       #else
-      __pyx_t_9 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 202, __pyx_L1_error)
+      __pyx_t_9 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 204, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
-      __pyx_t_5 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 202, __pyx_L1_error)
+      __pyx_t_5 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 204, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
       #endif
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     } else {
       Py_ssize_t index = -1;
-      __pyx_t_11 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 202, __pyx_L1_error)
+      __pyx_t_11 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 204, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_11);
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
       __pyx_t_12 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_11);
@@ -6820,7 +7188,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_GOTREF(__pyx_t_9);
       index = 1; __pyx_t_5 = __pyx_t_12(__pyx_t_11); if (unlikely(!__pyx_t_5)) goto __pyx_L15_unpacking_failed;
       __Pyx_GOTREF(__pyx_t_5);
-      if (__Pyx_IternextUnpackEndCheck(__pyx_t_12(__pyx_t_11), 2) < 0) __PYX_ERR(0, 202, __pyx_L1_error)
+      if (__Pyx_IternextUnpackEndCheck(__pyx_t_12(__pyx_t_11), 2) < 0) __PYX_ERR(0, 204, __pyx_L1_error)
       __pyx_t_12 = NULL;
       __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
       goto __pyx_L16_unpacking_done;
@@ -6828,17 +7196,17 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
       __pyx_t_12 = NULL;
       if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
-      __PYX_ERR(0, 202, __pyx_L1_error)
+      __PYX_ERR(0, 204, __pyx_L1_error)
       __pyx_L16_unpacking_done:;
     }
-    __pyx_t_2 = __Pyx_PyInt_As_int(__pyx_t_9); if (unlikely((__pyx_t_2 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 202, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyInt_As_int(__pyx_t_9); if (unlikely((__pyx_t_2 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 204, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-    __pyx_t_13 = __Pyx_PyInt_As_int(__pyx_t_5); if (unlikely((__pyx_t_13 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 202, __pyx_L1_error)
+    __pyx_t_13 = __Pyx_PyInt_As_int(__pyx_t_5); if (unlikely((__pyx_t_13 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 204, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
     __pyx_v_st = __pyx_t_2;
     __pyx_v_en = __pyx_t_13;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":203
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":205
  *     for p in re_find_indels.finditer(read_seq_al):
  *         st,en=p.span()
  *         ref_st = 0             # <<<<<<<<<<<<<<
@@ -6848,7 +7216,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __Pyx_INCREF(__pyx_int_0);
     __Pyx_XDECREF_SET(__pyx_v_ref_st, __pyx_int_0);
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":204
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":206
  *         st,en=p.span()
  *         ref_st = 0
  *         if st-1 > 0:             # <<<<<<<<<<<<<<
@@ -6858,7 +7226,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __pyx_t_6 = ((__pyx_v_st - 1) > 0);
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":205
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":207
  *         ref_st = 0
  *         if st-1 > 0:
  *           ref_st = ref_positions[st]             # <<<<<<<<<<<<<<
@@ -6870,7 +7238,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_DECREF_SET(__pyx_v_ref_st, __pyx_t_1);
       __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":204
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":206
  *         st,en=p.span()
  *         ref_st = 0
  *         if st-1 > 0:             # <<<<<<<<<<<<<<
@@ -6879,42 +7247,42 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":206
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":208
  *         if st-1 > 0:
  *           ref_st = ref_positions[st]
  *         ref_en = idx-1             # <<<<<<<<<<<<<<
  *         if en < len(ref_positions):
  *           ref_en = ref_positions[en]
  */
-    __pyx_t_1 = __Pyx_PyInt_From_long((__pyx_v_idx - 1)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 206, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyInt_From_long((__pyx_v_idx - 1)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 208, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_XDECREF_SET(__pyx_v_ref_en, __pyx_t_1);
     __pyx_t_1 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":207
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":209
  *           ref_st = ref_positions[st]
  *         ref_en = idx-1
  *         if en < len(ref_positions):             # <<<<<<<<<<<<<<
  *           ref_en = ref_positions[en]
  *         all_deletion_positions.extend(range(ref_st,ref_en))
  */
-    __pyx_t_14 = PyList_GET_SIZE(__pyx_v_ref_positions); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 207, __pyx_L1_error)
+    __pyx_t_14 = __Pyx_PyList_GET_SIZE(__pyx_v_ref_positions); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 209, __pyx_L1_error)
     __pyx_t_6 = (__pyx_v_en < __pyx_t_14);
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":208
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":210
  *         ref_en = idx-1
  *         if en < len(ref_positions):
  *           ref_en = ref_positions[en]             # <<<<<<<<<<<<<<
  *         all_deletion_positions.extend(range(ref_st,ref_en))
- *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
+ *         all_deletion_coordinates.append((ref_st,ref_en))
  */
       __pyx_t_1 = PyList_GET_ITEM(__pyx_v_ref_positions, __pyx_v_en);
       __Pyx_INCREF(__pyx_t_1);
       __Pyx_DECREF_SET(__pyx_v_ref_en, __pyx_t_1);
       __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":207
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":209
  *           ref_st = ref_positions[st]
  *         ref_en = idx-1
  *         if en < len(ref_positions):             # <<<<<<<<<<<<<<
@@ -6923,115 +7291,133 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":209
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":211
  *         if en < len(ref_positions):
  *           ref_en = ref_positions[en]
  *         all_deletion_positions.extend(range(ref_st,ref_en))             # <<<<<<<<<<<<<<
+ *         all_deletion_coordinates.append((ref_st,ref_en))
  *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
- *         if(len(inc_del_pos)>0):
  */
-    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 209, __pyx_L1_error)
+    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 211, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_INCREF(__pyx_v_ref_st);
     __Pyx_GIVEREF(__pyx_v_ref_st);
-    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_v_ref_st);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_v_ref_st)) __PYX_ERR(0, 211, __pyx_L1_error);
     __Pyx_INCREF(__pyx_v_ref_en);
     __Pyx_GIVEREF(__pyx_v_ref_en);
-    PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_ref_en);
-    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 209, __pyx_L1_error)
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_ref_en)) __PYX_ERR(0, 211, __pyx_L1_error);
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 211, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_all_deletion_positions, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 209, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_all_deletion_positions, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 211, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":210
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":212
  *           ref_en = ref_positions[en]
  *         all_deletion_positions.extend(range(ref_st,ref_en))
+ *         all_deletion_coordinates.append((ref_st,ref_en))             # <<<<<<<<<<<<<<
+ *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
+ *         if(len(inc_del_pos)>0):
+ */
+    __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 212, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_INCREF(__pyx_v_ref_st);
+    __Pyx_GIVEREF(__pyx_v_ref_st);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_ref_st)) __PYX_ERR(0, 212, __pyx_L1_error);
+    __Pyx_INCREF(__pyx_v_ref_en);
+    __Pyx_GIVEREF(__pyx_v_ref_en);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_ref_en)) __PYX_ERR(0, 212, __pyx_L1_error);
+    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_deletion_coordinates, __pyx_t_5); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 212, __pyx_L1_error)
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":213
+ *         all_deletion_positions.extend(range(ref_st,ref_en))
+ *         all_deletion_coordinates.append((ref_st,ref_en))
  *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))             # <<<<<<<<<<<<<<
  *         if(len(inc_del_pos)>0):
  *           deletion_positions.extend(range(ref_st,ref_en))
  */
-    __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 210, __pyx_L1_error)
+    __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 213, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __Pyx_INCREF(__pyx_v_ref_st);
     __Pyx_GIVEREF(__pyx_v_ref_st);
-    PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_ref_st);
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_ref_st)) __PYX_ERR(0, 213, __pyx_L1_error);
     __Pyx_INCREF(__pyx_v_ref_en);
     __Pyx_GIVEREF(__pyx_v_ref_en);
-    PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_ref_en);
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 210, __pyx_L1_error)
+    if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_ref_en)) __PYX_ERR(0, 213, __pyx_L1_error);
+    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 213, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    __pyx_t_5 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 210, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_CallUnboundCMethod1(&__pyx_umethod_PySet_Type_intersection, __pyx_v_include_indx_set, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 213, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     __Pyx_XDECREF_SET(__pyx_v_inc_del_pos, __pyx_t_5);
     __pyx_t_5 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":211
- *         all_deletion_positions.extend(range(ref_st,ref_en))
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":214
+ *         all_deletion_coordinates.append((ref_st,ref_en))
  *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
  *         if(len(inc_del_pos)>0):             # <<<<<<<<<<<<<<
  *           deletion_positions.extend(range(ref_st,ref_en))
  *           deletion_coordinates.append((ref_st,ref_en))
  */
-    __pyx_t_14 = PyObject_Length(__pyx_v_inc_del_pos); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 211, __pyx_L1_error)
+    __pyx_t_14 = PyObject_Length(__pyx_v_inc_del_pos); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 214, __pyx_L1_error)
     __pyx_t_6 = (__pyx_t_14 > 0);
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":212
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":215
  *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
  *         if(len(inc_del_pos)>0):
  *           deletion_positions.extend(range(ref_st,ref_en))             # <<<<<<<<<<<<<<
  *           deletion_coordinates.append((ref_st,ref_en))
  *           deletion_sizes.append(en-st)
  */
-      __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 212, __pyx_L1_error)
+      __pyx_t_5 = PyTuple_New(2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 215, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
       __Pyx_INCREF(__pyx_v_ref_st);
       __Pyx_GIVEREF(__pyx_v_ref_st);
-      PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_ref_st);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_ref_st)) __PYX_ERR(0, 215, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_ref_en);
       __Pyx_GIVEREF(__pyx_v_ref_en);
-      PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_ref_en);
-      __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 212, __pyx_L1_error)
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_5, 1, __pyx_v_ref_en)) __PYX_ERR(0, 215, __pyx_L1_error);
+      __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_5, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 215, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 212, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Extend(__pyx_v_deletion_positions, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 215, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":213
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":216
  *         if(len(inc_del_pos)>0):
  *           deletion_positions.extend(range(ref_st,ref_en))
  *           deletion_coordinates.append((ref_st,ref_en))             # <<<<<<<<<<<<<<
  *           deletion_sizes.append(en-st)
  * 
  */
-      __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 213, __pyx_L1_error)
+      __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 216, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
       __Pyx_INCREF(__pyx_v_ref_st);
       __Pyx_GIVEREF(__pyx_v_ref_st);
-      PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_v_ref_st);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_v_ref_st)) __PYX_ERR(0, 216, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_ref_en);
       __Pyx_GIVEREF(__pyx_v_ref_en);
-      PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_ref_en);
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 213, __pyx_L1_error)
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_ref_en)) __PYX_ERR(0, 216, __pyx_L1_error);
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_coordinates, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 216, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":214
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":217
  *           deletion_positions.extend(range(ref_st,ref_en))
  *           deletion_coordinates.append((ref_st,ref_en))
  *           deletion_sizes.append(en-st)             # <<<<<<<<<<<<<<
  * 
  *     deletion_n = np.sum(deletion_sizes)
  */
-      __pyx_t_1 = __Pyx_PyInt_From_int((__pyx_v_en - __pyx_v_st)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 214, __pyx_L1_error)
+      __pyx_t_1 = __Pyx_PyInt_From_int((__pyx_v_en - __pyx_v_st)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 217, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 214, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_deletion_sizes, __pyx_t_1); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 217, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":211
- *         all_deletion_positions.extend(range(ref_st,ref_en))
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":214
+ *         all_deletion_coordinates.append((ref_st,ref_en))
  *         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
  *         if(len(inc_del_pos)>0):             # <<<<<<<<<<<<<<
  *           deletion_positions.extend(range(ref_st,ref_en))
@@ -7039,7 +7425,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":201
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":203
  * 
  *     include_indx_set = set(_include_indx)
  *     for p in re_find_indels.finditer(read_seq_al):             # <<<<<<<<<<<<<<
@@ -7049,21 +7435,22 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   }
   __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":216
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":219
  *           deletion_sizes.append(en-st)
  * 
  *     deletion_n = np.sum(deletion_sizes)             # <<<<<<<<<<<<<<
  * 
  *     for p in re_find_indels.finditer(ref_seq_al):
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 216, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 219, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_sum); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 216, __pyx_L1_error)
+  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_sum); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 219, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_t_1 = NULL;
   __pyx_t_13 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_5))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_5))) {
     __pyx_t_1 = PyMethod_GET_SELF(__pyx_t_5);
     if (likely(__pyx_t_1)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
@@ -7073,32 +7460,34 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_13 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_1, __pyx_v_deletion_sizes};
     __pyx_t_8 = __Pyx_PyObject_FastCall(__pyx_t_5, __pyx_callargs+1-__pyx_t_13, 1+__pyx_t_13);
     __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0;
-    if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 216, __pyx_L1_error)
+    if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 219, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   }
   __pyx_v_deletion_n = __pyx_t_8;
   __pyx_t_8 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":218
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":221
  *     deletion_n = np.sum(deletion_sizes)
  * 
  *     for p in re_find_indels.finditer(ref_seq_al):             # <<<<<<<<<<<<<<
  *         st,en=p.span()
  *         #sometimes insertions run off the end of the reference
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_re_find_indels); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 218, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_re_find_indels); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 221, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
-  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_finditer); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 218, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_finditer); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 221, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __pyx_t_5 = NULL;
   __pyx_t_13 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_1))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_1))) {
     __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_1);
     if (likely(__pyx_t_5)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_1);
@@ -7108,39 +7497,53 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_13 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_5, __pyx_v_ref_seq_al};
     __pyx_t_8 = __Pyx_PyObject_FastCall(__pyx_t_1, __pyx_callargs+1-__pyx_t_13, 1+__pyx_t_13);
     __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-    if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 218, __pyx_L1_error)
+    if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 221, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   }
   if (likely(PyList_CheckExact(__pyx_t_8)) || PyTuple_CheckExact(__pyx_t_8)) {
-    __pyx_t_1 = __pyx_t_8; __Pyx_INCREF(__pyx_t_1); __pyx_t_3 = 0;
+    __pyx_t_1 = __pyx_t_8; __Pyx_INCREF(__pyx_t_1);
+    __pyx_t_3 = 0;
     __pyx_t_4 = NULL;
   } else {
-    __pyx_t_3 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_t_8); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 218, __pyx_L1_error)
+    __pyx_t_3 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_t_8); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 221, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 218, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 221, __pyx_L1_error)
   }
   __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   for (;;) {
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
-        if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 221, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_8 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_8); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 218, __pyx_L1_error)
+        __pyx_t_8 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_8); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 221, __pyx_L1_error)
         #else
-        __pyx_t_8 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 218, __pyx_L1_error)
+        __pyx_t_8 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 221, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_8);
         #endif
       } else {
-        if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+        {
+          Py_ssize_t __pyx_temp = __Pyx_PyTuple_GET_SIZE(__pyx_t_1);
+          #if !CYTHON_ASSUME_SAFE_MACROS
+          if (unlikely((__pyx_temp < 0))) __PYX_ERR(0, 221, __pyx_L1_error)
+          #endif
+          if (__pyx_t_3 >= __pyx_temp) break;
+        }
         #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_8 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_8); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 218, __pyx_L1_error)
+        __pyx_t_8 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_8); __pyx_t_3++; if (unlikely((0 < 0))) __PYX_ERR(0, 221, __pyx_L1_error)
         #else
-        __pyx_t_8 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 218, __pyx_L1_error)
+        __pyx_t_8 = __Pyx_PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 221, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_8);
         #endif
       }
@@ -7150,7 +7553,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         PyObject* exc_type = PyErr_Occurred();
         if (exc_type) {
           if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
-          else __PYX_ERR(0, 218, __pyx_L1_error)
+          else __PYX_ERR(0, 221, __pyx_L1_error)
         }
         break;
       }
@@ -7159,18 +7562,19 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __Pyx_XDECREF_SET(__pyx_v_p, __pyx_t_8);
     __pyx_t_8 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":219
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":222
  * 
  *     for p in re_find_indels.finditer(ref_seq_al):
  *         st,en=p.span()             # <<<<<<<<<<<<<<
  *         #sometimes insertions run off the end of the reference
  *         if st == 0: # if insertion happened before ref
  */
-    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_p, __pyx_n_s_span); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 219, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_p, __pyx_n_s_span); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 222, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
     __pyx_t_9 = NULL;
     __pyx_t_13 = 0;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_5))) {
+    #if CYTHON_UNPACK_METHODS
+    if (likely(PyMethod_Check(__pyx_t_5))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_5);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
@@ -7180,11 +7584,12 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
         __pyx_t_13 = 1;
       }
     }
+    #endif
     {
-      PyObject *__pyx_callargs[1] = {__pyx_t_9, };
+      PyObject *__pyx_callargs[2] = {__pyx_t_9, NULL};
       __pyx_t_8 = __Pyx_PyObject_FastCall(__pyx_t_5, __pyx_callargs+1-__pyx_t_13, 0+__pyx_t_13);
       __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-      if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 219, __pyx_L1_error)
+      if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 222, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
     }
@@ -7194,7 +7599,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       if (unlikely(size != 2)) {
         if (size > 2) __Pyx_RaiseTooManyValuesError(2);
         else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
-        __PYX_ERR(0, 219, __pyx_L1_error)
+        __PYX_ERR(0, 222, __pyx_L1_error)
       }
       #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
       if (likely(PyTuple_CheckExact(sequence))) {
@@ -7207,15 +7612,15 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_INCREF(__pyx_t_5);
       __Pyx_INCREF(__pyx_t_9);
       #else
-      __pyx_t_5 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 219, __pyx_L1_error)
+      __pyx_t_5 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 222, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
-      __pyx_t_9 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 219, __pyx_L1_error)
+      __pyx_t_9 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 222, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
       #endif
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     } else {
       Py_ssize_t index = -1;
-      __pyx_t_11 = PyObject_GetIter(__pyx_t_8); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 219, __pyx_L1_error)
+      __pyx_t_11 = PyObject_GetIter(__pyx_t_8); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 222, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_11);
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
       __pyx_t_12 = __Pyx_PyObject_GetIterNextFunc(__pyx_t_11);
@@ -7223,7 +7628,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_GOTREF(__pyx_t_5);
       index = 1; __pyx_t_9 = __pyx_t_12(__pyx_t_11); if (unlikely(!__pyx_t_9)) goto __pyx_L23_unpacking_failed;
       __Pyx_GOTREF(__pyx_t_9);
-      if (__Pyx_IternextUnpackEndCheck(__pyx_t_12(__pyx_t_11), 2) < 0) __PYX_ERR(0, 219, __pyx_L1_error)
+      if (__Pyx_IternextUnpackEndCheck(__pyx_t_12(__pyx_t_11), 2) < 0) __PYX_ERR(0, 222, __pyx_L1_error)
       __pyx_t_12 = NULL;
       __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
       goto __pyx_L24_unpacking_done;
@@ -7231,17 +7636,17 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
       __pyx_t_12 = NULL;
       if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
-      __PYX_ERR(0, 219, __pyx_L1_error)
+      __PYX_ERR(0, 222, __pyx_L1_error)
       __pyx_L24_unpacking_done:;
     }
-    __pyx_t_13 = __Pyx_PyInt_As_int(__pyx_t_5); if (unlikely((__pyx_t_13 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 219, __pyx_L1_error)
+    __pyx_t_13 = __Pyx_PyInt_As_int(__pyx_t_5); if (unlikely((__pyx_t_13 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 222, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    __pyx_t_2 = __Pyx_PyInt_As_int(__pyx_t_9); if (unlikely((__pyx_t_2 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 219, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyInt_As_int(__pyx_t_9); if (unlikely((__pyx_t_2 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 222, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     __pyx_v_st = __pyx_t_13;
     __pyx_v_en = __pyx_t_2;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":221
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":224
  *         st,en=p.span()
  *         #sometimes insertions run off the end of the reference
  *         if st == 0: # if insertion happened before ref             # <<<<<<<<<<<<<<
@@ -7251,7 +7656,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __pyx_t_6 = (__pyx_v_st == 0);
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":222
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":225
  *         #sometimes insertions run off the end of the reference
  *         if st == 0: # if insertion happened before ref
  *           continue             # <<<<<<<<<<<<<<
@@ -7260,7 +7665,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
       goto __pyx_L21_continue;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":221
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":224
  *         st,en=p.span()
  *         #sometimes insertions run off the end of the reference
  *         if st == 0: # if insertion happened before ref             # <<<<<<<<<<<<<<
@@ -7269,18 +7674,18 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":223
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":226
  *         if st == 0: # if insertion happened before ref
  *           continue
  *         if en == len(ref_seq_al): # if insertion happened after ref             # <<<<<<<<<<<<<<
  *           continue
  *         ref_st = ref_positions[st-1]
  */
-    __pyx_t_14 = PyObject_Length(__pyx_v_ref_seq_al); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 223, __pyx_L1_error)
+    __pyx_t_14 = PyObject_Length(__pyx_v_ref_seq_al); if (unlikely(__pyx_t_14 == ((Py_ssize_t)-1))) __PYX_ERR(0, 226, __pyx_L1_error)
     __pyx_t_6 = (__pyx_v_en == __pyx_t_14);
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":224
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":227
  *           continue
  *         if en == len(ref_seq_al): # if insertion happened after ref
  *           continue             # <<<<<<<<<<<<<<
@@ -7289,7 +7694,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
       goto __pyx_L21_continue;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":223
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":226
  *         if st == 0: # if insertion happened before ref
  *           continue
  *         if en == len(ref_seq_al): # if insertion happened after ref             # <<<<<<<<<<<<<<
@@ -7298,7 +7703,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":225
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":228
  *         if en == len(ref_seq_al): # if insertion happened after ref
  *           continue
  *         ref_st = ref_positions[st-1]             # <<<<<<<<<<<<<<
@@ -7311,7 +7716,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __Pyx_XDECREF_SET(__pyx_v_ref_st, __pyx_t_8);
     __pyx_t_8 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":226
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":229
  *           continue
  *         ref_st = ref_positions[st-1]
  *         ref_en = ref_positions[en]             # <<<<<<<<<<<<<<
@@ -7323,100 +7728,100 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
     __Pyx_XDECREF_SET(__pyx_v_ref_en, __pyx_t_8);
     __pyx_t_8 = 0;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":228
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":231
  *         ref_en = ref_positions[en]
  * 
  *         all_insertion_left_positions.append(ref_st)             # <<<<<<<<<<<<<<
  *         all_insertion_positions.append(ref_st)
  *         all_insertion_positions.append(ref_en)
  */
-    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_left_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 228, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_left_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 231, __pyx_L1_error)
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":229
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":232
  * 
  *         all_insertion_left_positions.append(ref_st)
  *         all_insertion_positions.append(ref_st)             # <<<<<<<<<<<<<<
  *         all_insertion_positions.append(ref_en)
  *         if(ref_st in _include_indx or ref_en in _include_indx):
  */
-    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 229, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 232, __pyx_L1_error)
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":230
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":233
  *         all_insertion_left_positions.append(ref_st)
  *         all_insertion_positions.append(ref_st)
  *         all_insertion_positions.append(ref_en)             # <<<<<<<<<<<<<<
  *         if(ref_st in _include_indx or ref_en in _include_indx):
  *           insertion_coordinates.append((ref_st,ref_en))
  */
-    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_positions, __pyx_v_ref_en); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 230, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_all_insertion_positions, __pyx_v_ref_en); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 233, __pyx_L1_error)
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":231
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":234
  *         all_insertion_positions.append(ref_st)
  *         all_insertion_positions.append(ref_en)
  *         if(ref_st in _include_indx or ref_en in _include_indx):             # <<<<<<<<<<<<<<
  *           insertion_coordinates.append((ref_st,ref_en))
  *           insertion_positions.append(ref_st)
  */
-    __pyx_t_10 = (__Pyx_PySequence_ContainsTF(__pyx_v_ref_st, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 231, __pyx_L1_error)
+    __pyx_t_10 = (__Pyx_PySequence_ContainsTF(__pyx_v_ref_st, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 234, __pyx_L1_error)
     if (!__pyx_t_10) {
     } else {
       __pyx_t_6 = __pyx_t_10;
       goto __pyx_L28_bool_binop_done;
     }
-    __pyx_t_10 = (__Pyx_PySequence_ContainsTF(__pyx_v_ref_en, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 231, __pyx_L1_error)
+    __pyx_t_10 = (__Pyx_PySequence_ContainsTF(__pyx_v_ref_en, __pyx_v__include_indx, Py_EQ)); if (unlikely((__pyx_t_10 < 0))) __PYX_ERR(0, 234, __pyx_L1_error)
     __pyx_t_6 = __pyx_t_10;
     __pyx_L28_bool_binop_done:;
     if (__pyx_t_6) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":232
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":235
  *         all_insertion_positions.append(ref_en)
  *         if(ref_st in _include_indx or ref_en in _include_indx):
  *           insertion_coordinates.append((ref_st,ref_en))             # <<<<<<<<<<<<<<
  *           insertion_positions.append(ref_st)
  *           insertion_positions.append(ref_en)
  */
-      __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 232, __pyx_L1_error)
+      __pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 235, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_INCREF(__pyx_v_ref_st);
       __Pyx_GIVEREF(__pyx_v_ref_st);
-      PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_v_ref_st);
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_v_ref_st)) __PYX_ERR(0, 235, __pyx_L1_error);
       __Pyx_INCREF(__pyx_v_ref_en);
       __Pyx_GIVEREF(__pyx_v_ref_en);
-      PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_ref_en);
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_coordinates, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 232, __pyx_L1_error)
+      if (__Pyx_PyTuple_SET_ITEM(__pyx_t_8, 1, __pyx_v_ref_en)) __PYX_ERR(0, 235, __pyx_L1_error);
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_coordinates, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 235, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":233
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":236
  *         if(ref_st in _include_indx or ref_en in _include_indx):
  *           insertion_coordinates.append((ref_st,ref_en))
  *           insertion_positions.append(ref_st)             # <<<<<<<<<<<<<<
  *           insertion_positions.append(ref_en)
  *           insertion_sizes.append(en-st)
  */
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 233, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_positions, __pyx_v_ref_st); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 236, __pyx_L1_error)
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":234
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":237
  *           insertion_coordinates.append((ref_st,ref_en))
  *           insertion_positions.append(ref_st)
  *           insertion_positions.append(ref_en)             # <<<<<<<<<<<<<<
  *           insertion_sizes.append(en-st)
  * 
  */
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_positions, __pyx_v_ref_en); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 234, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_positions, __pyx_v_ref_en); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 237, __pyx_L1_error)
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":235
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":238
  *           insertion_positions.append(ref_st)
  *           insertion_positions.append(ref_en)
  *           insertion_sizes.append(en-st)             # <<<<<<<<<<<<<<
  * 
  *     insertion_n = np.sum(insertion_sizes)
  */
-      __pyx_t_8 = __Pyx_PyInt_From_int((__pyx_v_en - __pyx_v_st)); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 235, __pyx_L1_error)
+      __pyx_t_8 = __Pyx_PyInt_From_int((__pyx_v_en - __pyx_v_st)); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 238, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
-      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_sizes, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 235, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyList_Append(__pyx_v_insertion_sizes, __pyx_t_8); if (unlikely(__pyx_t_7 == ((int)-1))) __PYX_ERR(0, 238, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":231
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":234
  *         all_insertion_positions.append(ref_st)
  *         all_insertion_positions.append(ref_en)
  *         if(ref_st in _include_indx or ref_en in _include_indx):             # <<<<<<<<<<<<<<
@@ -7425,7 +7830,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
  */
     }
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":218
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":221
  *     deletion_n = np.sum(deletion_sizes)
  * 
  *     for p in re_find_indels.finditer(ref_seq_al):             # <<<<<<<<<<<<<<
@@ -7436,21 +7841,22 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   }
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":237
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":240
  *           insertion_sizes.append(en-st)
  * 
  *     insertion_n = np.sum(insertion_sizes)             # <<<<<<<<<<<<<<
  * 
- * 
+ *     retDict = {
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_np); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 237, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_np); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 240, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_8);
-  __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_t_8, __pyx_n_s_sum); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 237, __pyx_L1_error)
+  __pyx_t_9 = __Pyx_PyObject_GetAttrStr(__pyx_t_8, __pyx_n_s_sum); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 240, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
   __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   __pyx_t_8 = NULL;
   __pyx_t_2 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_9))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_9))) {
     __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_9);
     if (likely(__pyx_t_8)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_9);
@@ -7460,151 +7866,162 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_2 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_8, __pyx_v_insertion_sizes};
     __pyx_t_1 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_2, 1+__pyx_t_2);
     __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 237, __pyx_L1_error)
+    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 240, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   }
   __pyx_v_insertion_n = __pyx_t_1;
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":241
- * 
- *     retDict = {
- * 	    'all_insertion_positions':all_insertion_positions,             # <<<<<<<<<<<<<<
- * 	    'all_insertion_left_positions':all_insertion_left_positions,
- * 	    'insertion_positions':insertion_positions,
- */
-  __pyx_t_1 = __Pyx_PyDict_NewPresized(17); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 241, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_positions, __pyx_v_all_insertion_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
-
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":242
- *     retDict = {
- * 	    'all_insertion_positions':all_insertion_positions,
- * 	    'all_insertion_left_positions':all_insertion_left_positions,             # <<<<<<<<<<<<<<
- * 	    'insertion_positions':insertion_positions,
- * 	    'insertion_coordinates':insertion_coordinates,
- */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_left_positions, __pyx_v_all_insertion_left_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
-
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":243
- * 	    'all_insertion_positions':all_insertion_positions,
- * 	    'all_insertion_left_positions':all_insertion_left_positions,
- * 	    'insertion_positions':insertion_positions,             # <<<<<<<<<<<<<<
- * 	    'insertion_coordinates':insertion_coordinates,
- * 	    'insertion_sizes':insertion_sizes,
+ * 
+ *     retDict = {
+ *         'all_insertion_positions':all_insertion_positions,             # <<<<<<<<<<<<<<
+ *         'all_insertion_left_positions':all_insertion_left_positions,
+ *         'insertion_positions':insertion_positions,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_positions, __pyx_v_insertion_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyDict_NewPresized(18); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 243, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_positions, __pyx_v_all_insertion_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":244
- * 	    'all_insertion_left_positions':all_insertion_left_positions,
- * 	    'insertion_positions':insertion_positions,
- * 	    'insertion_coordinates':insertion_coordinates,             # <<<<<<<<<<<<<<
- * 	    'insertion_sizes':insertion_sizes,
- * 	    'insertion_n':insertion_n,
+ *     retDict = {
+ *         'all_insertion_positions':all_insertion_positions,
+ *         'all_insertion_left_positions':all_insertion_left_positions,             # <<<<<<<<<<<<<<
+ *         'insertion_positions':insertion_positions,
+ *         'insertion_coordinates':insertion_coordinates,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_coordinates, __pyx_v_insertion_coordinates) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_insertion_left_positions, __pyx_v_all_insertion_left_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":245
- * 	    'insertion_positions':insertion_positions,
- * 	    'insertion_coordinates':insertion_coordinates,
- * 	    'insertion_sizes':insertion_sizes,             # <<<<<<<<<<<<<<
- * 	    'insertion_n':insertion_n,
- * 
+ *         'all_insertion_positions':all_insertion_positions,
+ *         'all_insertion_left_positions':all_insertion_left_positions,
+ *         'insertion_positions':insertion_positions,             # <<<<<<<<<<<<<<
+ *         'insertion_coordinates':insertion_coordinates,
+ *         'insertion_sizes':insertion_sizes,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_sizes, __pyx_v_insertion_sizes) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_positions, __pyx_v_insertion_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":246
- * 	    'insertion_coordinates':insertion_coordinates,
- * 	    'insertion_sizes':insertion_sizes,
- * 	    'insertion_n':insertion_n,             # <<<<<<<<<<<<<<
- * 
- * 	    'all_deletion_positions':all_deletion_positions,
+ *         'all_insertion_left_positions':all_insertion_left_positions,
+ *         'insertion_positions':insertion_positions,
+ *         'insertion_coordinates':insertion_coordinates,             # <<<<<<<<<<<<<<
+ *         'insertion_sizes':insertion_sizes,
+ *         'insertion_n':insertion_n,
+ */
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_coordinates, __pyx_v_insertion_coordinates) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":247
+ *         'insertion_positions':insertion_positions,
+ *         'insertion_coordinates':insertion_coordinates,
+ *         'insertion_sizes':insertion_sizes,             # <<<<<<<<<<<<<<
+ *         'insertion_n':insertion_n,
+ *         'all_deletion_positions':all_deletion_positions,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_n, __pyx_v_insertion_n) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_sizes, __pyx_v_insertion_sizes) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":248
- * 	    'insertion_n':insertion_n,
+ *         'insertion_coordinates':insertion_coordinates,
+ *         'insertion_sizes':insertion_sizes,
+ *         'insertion_n':insertion_n,             # <<<<<<<<<<<<<<
+ *         'all_deletion_positions':all_deletion_positions,
  * 
- * 	    'all_deletion_positions':all_deletion_positions,             # <<<<<<<<<<<<<<
- * 	    'deletion_positions':deletion_positions,
- * 	    'deletion_coordinates':deletion_coordinates,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_deletion_positions, __pyx_v_all_deletion_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_insertion_n, __pyx_v_insertion_n) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":249
+ *         'insertion_sizes':insertion_sizes,
+ *         'insertion_n':insertion_n,
+ *         'all_deletion_positions':all_deletion_positions,             # <<<<<<<<<<<<<<
  * 
- * 	    'all_deletion_positions':all_deletion_positions,
- * 	    'deletion_positions':deletion_positions,             # <<<<<<<<<<<<<<
- * 	    'deletion_coordinates':deletion_coordinates,
- * 	    'deletion_sizes':deletion_sizes,
- */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_positions, __pyx_v_deletion_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
-
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":250
- * 	    'all_deletion_positions':all_deletion_positions,
- * 	    'deletion_positions':deletion_positions,
- * 	    'deletion_coordinates':deletion_coordinates,             # <<<<<<<<<<<<<<
- * 	    'deletion_sizes':deletion_sizes,
- * 	    'deletion_n':deletion_n,
+ *         'deletion_positions':deletion_positions,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_coordinates, __pyx_v_deletion_coordinates) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_deletion_positions, __pyx_v_all_deletion_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":251
- * 	    'deletion_positions':deletion_positions,
- * 	    'deletion_coordinates':deletion_coordinates,
- * 	    'deletion_sizes':deletion_sizes,             # <<<<<<<<<<<<<<
- * 	    'deletion_n':deletion_n,
+ *         'all_deletion_positions':all_deletion_positions,
  * 
+ *         'deletion_positions':deletion_positions,             # <<<<<<<<<<<<<<
+ *         'deletion_coordinates':deletion_coordinates,
+ *         'all_deletion_coordinates':all_deletion_coordinates,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_sizes, __pyx_v_deletion_sizes) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_positions, __pyx_v_deletion_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":252
- * 	    'deletion_coordinates':deletion_coordinates,
- * 	    'deletion_sizes':deletion_sizes,
- * 	    'deletion_n':deletion_n,             # <<<<<<<<<<<<<<
  * 
- * 	    'all_substitution_positions':all_substitution_positions,
+ *         'deletion_positions':deletion_positions,
+ *         'deletion_coordinates':deletion_coordinates,             # <<<<<<<<<<<<<<
+ *         'all_deletion_coordinates':all_deletion_coordinates,
+ *         'deletion_sizes':deletion_sizes,
+ */
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_coordinates, __pyx_v_deletion_coordinates) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":253
+ *         'deletion_positions':deletion_positions,
+ *         'deletion_coordinates':deletion_coordinates,
+ *         'all_deletion_coordinates':all_deletion_coordinates,             # <<<<<<<<<<<<<<
+ *         'deletion_sizes':deletion_sizes,
+ *         'deletion_n':deletion_n,
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_n, __pyx_v_deletion_n) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_deletion_coordinates, __pyx_v_all_deletion_coordinates) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":254
- * 	    'deletion_n':deletion_n,
+ *         'deletion_coordinates':deletion_coordinates,
+ *         'all_deletion_coordinates':all_deletion_coordinates,
+ *         'deletion_sizes':deletion_sizes,             # <<<<<<<<<<<<<<
+ *         'deletion_n':deletion_n,
  * 
- * 	    'all_substitution_positions':all_substitution_positions,             # <<<<<<<<<<<<<<
- * 	    'substitution_positions':substitution_positions,
- * 	    'all_substitution_values':np.array(all_substitution_values),
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_positions, __pyx_v_all_substitution_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_sizes, __pyx_v_deletion_sizes) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":255
+ *         'all_deletion_coordinates':all_deletion_coordinates,
+ *         'deletion_sizes':deletion_sizes,
+ *         'deletion_n':deletion_n,             # <<<<<<<<<<<<<<
+ * 
+ *         'all_substitution_positions':all_substitution_positions,
+ */
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_deletion_n, __pyx_v_deletion_n) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":257
+ *         'deletion_n':deletion_n,
+ * 
+ *         'all_substitution_positions':all_substitution_positions,             # <<<<<<<<<<<<<<
+ *         'substitution_positions':substitution_positions,
+ *         'all_substitution_values':np.array(all_substitution_values),
+ */
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_positions, __pyx_v_all_substitution_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
+
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":258
  * 
- * 	    'all_substitution_positions':all_substitution_positions,
- * 	    'substitution_positions':substitution_positions,             # <<<<<<<<<<<<<<
- * 	    'all_substitution_values':np.array(all_substitution_values),
- * 	    'substitution_values':np.array(substitution_values),
+ *         'all_substitution_positions':all_substitution_positions,
+ *         'substitution_positions':substitution_positions,             # <<<<<<<<<<<<<<
+ *         'all_substitution_values':np.array(all_substitution_values),
+ *         'substitution_values':np.array(substitution_values),
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_positions, __pyx_v_substitution_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_positions, __pyx_v_substitution_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":256
- * 	    'all_substitution_positions':all_substitution_positions,
- * 	    'substitution_positions':substitution_positions,
- * 	    'all_substitution_values':np.array(all_substitution_values),             # <<<<<<<<<<<<<<
- * 	    'substitution_values':np.array(substitution_values),
- * 	    'substitution_n':substitution_n,
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":259
+ *         'all_substitution_positions':all_substitution_positions,
+ *         'substitution_positions':substitution_positions,
+ *         'all_substitution_values':np.array(all_substitution_values),             # <<<<<<<<<<<<<<
+ *         'substitution_values':np.array(substitution_values),
+ *         'substitution_n':substitution_n,
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_np); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 256, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_np); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 259, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_8);
-  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_8, __pyx_n_s_array); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 256, __pyx_L1_error)
+  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_8, __pyx_n_s_array); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 259, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   __pyx_t_8 = NULL;
   __pyx_t_2 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_5))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_5))) {
     __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_5);
     if (likely(__pyx_t_8)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
@@ -7614,32 +8031,34 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_2 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_8, __pyx_v_all_substitution_values};
     __pyx_t_9 = __Pyx_PyObject_FastCall(__pyx_t_5, __pyx_callargs+1-__pyx_t_2, 1+__pyx_t_2);
     __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-    if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 256, __pyx_L1_error)
+    if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 259, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_9);
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   }
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_values, __pyx_t_9) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_all_substitution_values, __pyx_t_9) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":257
- * 	    'substitution_positions':substitution_positions,
- * 	    'all_substitution_values':np.array(all_substitution_values),
- * 	    'substitution_values':np.array(substitution_values),             # <<<<<<<<<<<<<<
- * 	    'substitution_n':substitution_n,
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":260
+ *         'substitution_positions':substitution_positions,
+ *         'all_substitution_values':np.array(all_substitution_values),
+ *         'substitution_values':np.array(substitution_values),             # <<<<<<<<<<<<<<
+ *         'substitution_n':substitution_n,
  * 
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 257, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 260, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
-  __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_array); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 257, __pyx_L1_error)
+  __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_array); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 260, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_8);
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __pyx_t_5 = NULL;
   __pyx_t_2 = 0;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_8))) {
+  #if CYTHON_UNPACK_METHODS
+  if (unlikely(PyMethod_Check(__pyx_t_8))) {
     __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_8);
     if (likely(__pyx_t_5)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
@@ -7649,42 +8068,43 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
       __pyx_t_2 = 1;
     }
   }
+  #endif
   {
     PyObject *__pyx_callargs[2] = {__pyx_t_5, __pyx_v_substitution_values};
     __pyx_t_9 = __Pyx_PyObject_FastCall(__pyx_t_8, __pyx_callargs+1-__pyx_t_2, 1+__pyx_t_2);
     __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-    if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 257, __pyx_L1_error)
+    if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 260, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_9);
     __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
   }
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_values, __pyx_t_9) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_values, __pyx_t_9) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":258
- * 	    'all_substitution_values':np.array(all_substitution_values),
- * 	    'substitution_values':np.array(substitution_values),
- * 	    'substitution_n':substitution_n,             # <<<<<<<<<<<<<<
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":261
+ *         'all_substitution_values':np.array(all_substitution_values),
+ *         'substitution_values':np.array(substitution_values),
+ *         'substitution_n':substitution_n,             # <<<<<<<<<<<<<<
  * 
- * 	    'ref_positions':ref_positions,
+ *         'ref_positions':ref_positions,
  */
-  __pyx_t_9 = PyInt_FromSsize_t(__pyx_v_substitution_n); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 258, __pyx_L1_error)
+  __pyx_t_9 = PyInt_FromSsize_t(__pyx_v_substitution_n); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 261, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_n, __pyx_t_9) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_substitution_n, __pyx_t_9) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":260
- * 	    'substitution_n':substitution_n,
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":263
+ *         'substitution_n':substitution_n,
  * 
- * 	    'ref_positions':ref_positions,             # <<<<<<<<<<<<<<
+ *         'ref_positions':ref_positions,             # <<<<<<<<<<<<<<
  *     }
  *     return retDict
  */
-  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_ref_positions, __pyx_v_ref_positions) < 0) __PYX_ERR(0, 241, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_u_ref_positions, __pyx_v_ref_positions) < 0) __PYX_ERR(0, 243, __pyx_L1_error)
   __pyx_v_retDict = ((PyObject*)__pyx_t_1);
   __pyx_t_1 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":262
- * 	    'ref_positions':ref_positions,
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":265
+ *         'ref_positions':ref_positions,
  *     }
  *     return retDict             # <<<<<<<<<<<<<<
  * 
@@ -7696,8 +8116,8 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   goto __pyx_L0;
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":138
- * 
- * 
+ *         'ref_positions': ref_positions,
+ *     }
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
@@ -7723,6 +8143,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   __Pyx_XDECREF(__pyx_v_all_deletion_positions);
   __Pyx_XDECREF(__pyx_v_deletion_positions);
   __Pyx_XDECREF(__pyx_v_deletion_coordinates);
+  __Pyx_XDECREF(__pyx_v_all_deletion_coordinates);
   __Pyx_XDECREF(__pyx_v_deletion_sizes);
   __Pyx_XDECREF(__pyx_v_all_insertion_positions);
   __Pyx_XDECREF(__pyx_v_all_insertion_left_positions);
@@ -7742,7 +8163,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_2find_indels_s
   return __pyx_r;
 }
 
-/* "CRISPResso2/CRISPRessoCOREResources.pyx":265
+/* "CRISPResso2/CRISPRessoCOREResources.pyx":268
  * 
  * 
  * def calculate_homology(a, b):             # <<<<<<<<<<<<<<
@@ -7769,18 +8190,26 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   PyObject *__pyx_v_a = 0;
   PyObject *__pyx_v_b = 0;
   #if !CYTHON_METH_FASTCALL
-  CYTHON_UNUSED const Py_ssize_t __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
   #endif
-  CYTHON_UNUSED PyObject *const *__pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
+  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
+  PyObject* values[2] = {0,0};
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
   PyObject *__pyx_r = 0;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("calculate_homology (wrapper)", 0);
+  #if !CYTHON_METH_FASTCALL
+  #if CYTHON_ASSUME_SAFE_MACROS
+  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
+  #else
+  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
+  #endif
+  #endif
+  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
   {
     PyObject **__pyx_pyargnames[] = {&__pyx_n_s_a,&__pyx_n_s_b,0};
-    PyObject* values[2] = {0,0};
     if (__pyx_kwds) {
       Py_ssize_t kw_args;
       switch (__pyx_nargs) {
@@ -7794,20 +8223,26 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
       kw_args = __Pyx_NumKwargs_FASTCALL(__pyx_kwds);
       switch (__pyx_nargs) {
         case  0:
-        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_a)) != 0)) kw_args--;
-        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 265, __pyx_L3_error)
+        if (likely((values[0] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_a)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[0]);
+          kw_args--;
+        }
+        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 268, __pyx_L3_error)
         else goto __pyx_L5_argtuple_error;
         CYTHON_FALLTHROUGH;
         case  1:
-        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_b)) != 0)) kw_args--;
-        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 265, __pyx_L3_error)
+        if (likely((values[1] = __Pyx_GetKwValue_FASTCALL(__pyx_kwds, __pyx_kwvalues, __pyx_n_s_b)) != 0)) {
+          (void)__Pyx_Arg_NewRef_FASTCALL(values[1]);
+          kw_args--;
+        }
+        else if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 268, __pyx_L3_error)
         else {
-          __Pyx_RaiseArgtupleInvalid("calculate_homology", 1, 2, 2, 1); __PYX_ERR(0, 265, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("calculate_homology", 1, 2, 2, 1); __PYX_ERR(0, 268, __pyx_L3_error)
         }
       }
       if (unlikely(kw_args > 0)) {
         const Py_ssize_t kwd_pos_args = __pyx_nargs;
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values + 0, kwd_pos_args, "calculate_homology") < 0)) __PYX_ERR(0, 265, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values + 0, kwd_pos_args, "calculate_homology") < 0)) __PYX_ERR(0, 268, __pyx_L3_error)
       }
     } else if (unlikely(__pyx_nargs != 2)) {
       goto __pyx_L5_argtuple_error;
@@ -7818,10 +8253,18 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
     __pyx_v_a = values[0];
     __pyx_v_b = values[1];
   }
-  goto __pyx_L4_argument_unpacking_done;
+  goto __pyx_L6_skip;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("calculate_homology", 1, 2, 2, __pyx_nargs); __PYX_ERR(0, 265, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("calculate_homology", 1, 2, 2, __pyx_nargs); __PYX_ERR(0, 268, __pyx_L3_error)
+  __pyx_L6_skip:;
+  goto __pyx_L4_argument_unpacking_done;
   __pyx_L3_error:;
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_AddTraceback("CRISPResso2.CRISPRessoCOREResources.calculate_homology", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return NULL;
@@ -7829,6 +8272,12 @@ PyObject *__pyx_args, PyObject *__pyx_kwds
   __pyx_r = __pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_homology(__pyx_self, __pyx_v_a, __pyx_v_b);
 
   /* function exit code */
+  {
+    Py_ssize_t __pyx_temp;
+    for (__pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
+      __Pyx_Arg_XDECREF_FASTCALL(values[__pyx_temp]);
+    }
+  }
   __Pyx_RefNannyFinishContext();
   return __pyx_r;
 }
@@ -7850,29 +8299,29 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
-  __Pyx_RefNannySetupContext("calculate_homology", 0);
+  __Pyx_RefNannySetupContext("calculate_homology", 1);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":266
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":269
  * 
  * def calculate_homology(a, b):
  *     cdef char *al = a             # <<<<<<<<<<<<<<
  *     cdef char *bl = b
  *     cdef size_t l = strlen(al)
  */
-  __pyx_t_1 = __Pyx_PyObject_AsWritableString(__pyx_v_a); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 266, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_AsWritableString(__pyx_v_a); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 269, __pyx_L1_error)
   __pyx_v_al = __pyx_t_1;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":267
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":270
  * def calculate_homology(a, b):
  *     cdef char *al = a
  *     cdef char *bl = b             # <<<<<<<<<<<<<<
  *     cdef size_t l = strlen(al)
  *     cdef float score = 0.0
  */
-  __pyx_t_1 = __Pyx_PyObject_AsWritableString(__pyx_v_b); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 267, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_AsWritableString(__pyx_v_b); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 270, __pyx_L1_error)
   __pyx_v_bl = __pyx_t_1;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":268
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":271
  *     cdef char *al = a
  *     cdef char *bl = b
  *     cdef size_t l = strlen(al)             # <<<<<<<<<<<<<<
@@ -7881,7 +8330,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
  */
   __pyx_v_l = strlen(__pyx_v_al);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":269
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":272
  *     cdef char *bl = b
  *     cdef size_t l = strlen(al)
  *     cdef float score = 0.0             # <<<<<<<<<<<<<<
@@ -7890,7 +8339,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
  */
   __pyx_v_score = 0.0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":271
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":274
  *     cdef float score = 0.0
  * 
  *     for i in range(l):             # <<<<<<<<<<<<<<
@@ -7902,7 +8351,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
   for (__pyx_t_4 = 0; __pyx_t_4 < __pyx_t_3; __pyx_t_4+=1) {
     __pyx_v_i = __pyx_t_4;
 
-    /* "CRISPResso2/CRISPRessoCOREResources.pyx":272
+    /* "CRISPResso2/CRISPRessoCOREResources.pyx":275
  * 
  *     for i in range(l):
  *         if al[i] == bl[i]:             # <<<<<<<<<<<<<<
@@ -7912,7 +8361,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
     __pyx_t_5 = ((__pyx_v_al[__pyx_v_i]) == (__pyx_v_bl[__pyx_v_i]));
     if (__pyx_t_5) {
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":273
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":276
  *     for i in range(l):
  *         if al[i] == bl[i]:
  *             score+=1             # <<<<<<<<<<<<<<
@@ -7920,7 +8369,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
  */
       __pyx_v_score = (__pyx_v_score + 1.0);
 
-      /* "CRISPResso2/CRISPRessoCOREResources.pyx":272
+      /* "CRISPResso2/CRISPRessoCOREResources.pyx":275
  * 
  *     for i in range(l):
  *         if al[i] == bl[i]:             # <<<<<<<<<<<<<<
@@ -7930,7 +8379,7 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
     }
   }
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":274
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":277
  *         if al[i] == bl[i]:
  *             score+=1
  *     return score/l             # <<<<<<<<<<<<<<
@@ -7938,15 +8387,15 @@ static PyObject *__pyx_pf_11CRISPResso2_23CRISPRessoCOREResources_4calculate_hom
   __Pyx_XDECREF(__pyx_r);
   if (unlikely(__pyx_v_l == 0)) {
     PyErr_SetString(PyExc_ZeroDivisionError, "float division");
-    __PYX_ERR(0, 274, __pyx_L1_error)
+    __PYX_ERR(0, 277, __pyx_L1_error)
   }
-  __pyx_t_6 = PyFloat_FromDouble((__pyx_v_score / ((float)__pyx_v_l))); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 274, __pyx_L1_error)
+  __pyx_t_6 = PyFloat_FromDouble((__pyx_v_score / ((float)__pyx_v_l))); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 277, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_6);
   __pyx_r = __pyx_t_6;
   __pyx_t_6 = 0;
   goto __pyx_L0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":265
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":268
  * 
  * 
  * def calculate_homology(a, b):             # <<<<<<<<<<<<<<
@@ -7995,6 +8444,8 @@ static int __Pyx_CreateStringTabAndInitStrings(void) {
     {&__pyx_kp_u__5, __pyx_k__5, sizeof(__pyx_k__5), 0, 1, 0, 0},
     {&__pyx_n_s_a, __pyx_k_a, sizeof(__pyx_k_a), 0, 0, 1, 1},
     {&__pyx_n_s_al, __pyx_k_al, sizeof(__pyx_k_al), 0, 0, 1, 1},
+    {&__pyx_n_s_all_deletion_coordinates, __pyx_k_all_deletion_coordinates, sizeof(__pyx_k_all_deletion_coordinates), 0, 0, 1, 1},
+    {&__pyx_n_u_all_deletion_coordinates, __pyx_k_all_deletion_coordinates, sizeof(__pyx_k_all_deletion_coordinates), 0, 1, 0, 1},
     {&__pyx_n_s_all_deletion_positions, __pyx_k_all_deletion_positions, sizeof(__pyx_k_all_deletion_positions), 0, 0, 1, 1},
     {&__pyx_n_u_all_deletion_positions, __pyx_k_all_deletion_positions, sizeof(__pyx_k_all_deletion_positions), 0, 1, 0, 1},
     {&__pyx_n_s_all_insertion_left_positions, __pyx_k_all_insertion_left_positions, sizeof(__pyx_k_all_insertion_left_positions), 0, 0, 1, 1},
@@ -8089,8 +8540,8 @@ static int __Pyx_CreateStringTabAndInitStrings(void) {
 static CYTHON_SMALL_CODE int __Pyx_InitCachedBuiltins(void) {
   __pyx_builtin_enumerate = __Pyx_GetBuiltinName(__pyx_n_s_enumerate); if (!__pyx_builtin_enumerate) __PYX_ERR(0, 58, __pyx_L1_error)
   __pyx_builtin_range = __Pyx_GetBuiltinName(__pyx_n_s_range); if (!__pyx_builtin_range) __PYX_ERR(0, 95, __pyx_L1_error)
-  __pyx_builtin_sum = __Pyx_GetBuiltinName(__pyx_n_s_sum); if (!__pyx_builtin_sum) __PYX_ERR(0, 111, __pyx_L1_error)
-  __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 983, __pyx_L1_error)
+  __pyx_builtin_sum = __Pyx_GetBuiltinName(__pyx_n_s_sum); if (!__pyx_builtin_sum) __PYX_ERR(0, 113, __pyx_L1_error)
+  __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 984, __pyx_L1_error)
   return 0;
   __pyx_L1_error:;
   return -1;
@@ -8101,74 +8552,74 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":983
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":984
  *         __pyx_import_array()
  *     except Exception:
  *         raise ImportError("numpy.core.multiarray failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_umath() except -1:
  */
-  __pyx_tuple_ = PyTuple_Pack(1, __pyx_kp_u_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple_)) __PYX_ERR(1, 983, __pyx_L1_error)
+  __pyx_tuple_ = PyTuple_Pack(1, __pyx_kp_u_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple_)) __PYX_ERR(1, 984, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple_);
   __Pyx_GIVEREF(__pyx_tuple_);
 
-  /* "../../../../../../home/snichol2/micromamba/envs/c2/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":989
+  /* "../../../mambaforge/envs/crispresso/lib/python3.10/site-packages/numpy/__init__.cython-30.pxd":990
  *         _import_umath()
  *     except Exception:
  *         raise ImportError("numpy.core.umath failed to import")             # <<<<<<<<<<<<<<
  * 
  * cdef inline int import_ufunc() except -1:
  */
-  __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_u_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(1, 989, __pyx_L1_error)
+  __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_u_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(1, 990, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__2);
   __Pyx_GIVEREF(__pyx_tuple__2);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":17
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":16
  * 
  * 
  * re_find_indels = re.compile("(-*-)")             # <<<<<<<<<<<<<<
  * 
  * @cython.boundscheck(False)
  */
-  __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_u__5); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(0, 17, __pyx_L1_error)
+  __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_u__5); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(0, 16, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__6);
   __Pyx_GIVEREF(__pyx_tuple__6);
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":19
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":18
  * re_find_indels = re.compile("(-*-)")
  * 
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
  */
-  __pyx_tuple__7 = PyTuple_Pack(30, __pyx_n_s_read_seq_al, __pyx_n_s_ref_seq_al, __pyx_n_s_include_indx, __pyx_n_s_ref_positions, __pyx_n_s_all_substitution_positions, __pyx_n_s_substitution_positions, __pyx_n_s_all_substitution_values, __pyx_n_s_substitution_values, __pyx_n_s_all_deletion_positions, __pyx_n_s_deletion_positions, __pyx_n_s_deletion_coordinates, __pyx_n_s_deletion_sizes, __pyx_n_s_start_deletion, __pyx_n_s_all_insertion_positions, __pyx_n_s_all_insertion_left_positions, __pyx_n_s_insertion_positions, __pyx_n_s_insertion_coordinates, __pyx_n_s_insertion_sizes, __pyx_n_s_start_insertion, __pyx_n_s_seq_len, __pyx_n_s_include_indx_set, __pyx_n_s_nucSet, __pyx_n_s_idx, __pyx_n_s_idx_c, __pyx_n_s_current_insertion_size, __pyx_n_s_c, __pyx_n_s_end_deletion, __pyx_n_s_substitution_n, __pyx_n_s_deletion_n, __pyx_n_s_insertion_n); if (unlikely(!__pyx_tuple__7)) __PYX_ERR(0, 19, __pyx_L1_error)
+  __pyx_tuple__7 = PyTuple_Pack(31, __pyx_n_s_read_seq_al, __pyx_n_s_ref_seq_al, __pyx_n_s_include_indx, __pyx_n_s_ref_positions, __pyx_n_s_all_substitution_positions, __pyx_n_s_substitution_positions, __pyx_n_s_all_substitution_values, __pyx_n_s_substitution_values, __pyx_n_s_all_deletion_positions, __pyx_n_s_all_deletion_coordinates, __pyx_n_s_deletion_positions, __pyx_n_s_deletion_coordinates, __pyx_n_s_deletion_sizes, __pyx_n_s_start_deletion, __pyx_n_s_all_insertion_positions, __pyx_n_s_all_insertion_left_positions, __pyx_n_s_insertion_positions, __pyx_n_s_insertion_coordinates, __pyx_n_s_insertion_sizes, __pyx_n_s_start_insertion, __pyx_n_s_seq_len, __pyx_n_s_include_indx_set, __pyx_n_s_nucSet, __pyx_n_s_idx, __pyx_n_s_idx_c, __pyx_n_s_current_insertion_size, __pyx_n_s_c, __pyx_n_s_end_deletion, __pyx_n_s_substitution_n, __pyx_n_s_deletion_n, __pyx_n_s_insertion_n); if (unlikely(!__pyx_tuple__7)) __PYX_ERR(0, 18, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__7);
   __Pyx_GIVEREF(__pyx_tuple__7);
-  __pyx_codeobj__8 = (PyObject*)__Pyx_PyCode_New(3, 0, 0, 30, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__7, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_find_indels_substitutions, 19, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__8)) __PYX_ERR(0, 19, __pyx_L1_error)
+  __pyx_codeobj__8 = (PyObject*)__Pyx_PyCode_New(3, 0, 0, 31, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__7, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_find_indels_substitutions, 18, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__8)) __PYX_ERR(0, 18, __pyx_L1_error)
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":138
- * 
- * 
+ *         'ref_positions': ref_positions,
+ *     }
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
  */
-  __pyx_tuple__9 = PyTuple_Pack(33, __pyx_n_s_read_seq_al, __pyx_n_s_ref_seq_al, __pyx_n_s_include_indx, __pyx_n_s_sub_seq, __pyx_n_s_st, __pyx_n_s_en, __pyx_n_s_idx_c, __pyx_n_s_idx, __pyx_n_s_ref_positions, __pyx_n_s_all_substitution_positions, __pyx_n_s_substitution_positions, __pyx_n_s_all_substitution_values, __pyx_n_s_substitution_values, __pyx_n_s_nucSet, __pyx_n_s_c, __pyx_n_s_substitution_n, __pyx_n_s_all_deletion_positions, __pyx_n_s_deletion_positions, __pyx_n_s_deletion_coordinates, __pyx_n_s_deletion_sizes, __pyx_n_s_all_insertion_positions, __pyx_n_s_all_insertion_left_positions, __pyx_n_s_insertion_positions, __pyx_n_s_insertion_coordinates, __pyx_n_s_insertion_sizes, __pyx_n_s_include_indx_set, __pyx_n_s_p, __pyx_n_s_ref_st, __pyx_n_s_ref_en, __pyx_n_s_inc_del_pos, __pyx_n_s_deletion_n, __pyx_n_s_insertion_n, __pyx_n_s_retDict); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(0, 138, __pyx_L1_error)
+  __pyx_tuple__9 = PyTuple_Pack(34, __pyx_n_s_read_seq_al, __pyx_n_s_ref_seq_al, __pyx_n_s_include_indx, __pyx_n_s_sub_seq, __pyx_n_s_st, __pyx_n_s_en, __pyx_n_s_idx_c, __pyx_n_s_idx, __pyx_n_s_ref_positions, __pyx_n_s_all_substitution_positions, __pyx_n_s_substitution_positions, __pyx_n_s_all_substitution_values, __pyx_n_s_substitution_values, __pyx_n_s_nucSet, __pyx_n_s_c, __pyx_n_s_substitution_n, __pyx_n_s_all_deletion_positions, __pyx_n_s_deletion_positions, __pyx_n_s_deletion_coordinates, __pyx_n_s_all_deletion_coordinates, __pyx_n_s_deletion_sizes, __pyx_n_s_all_insertion_positions, __pyx_n_s_all_insertion_left_positions, __pyx_n_s_insertion_positions, __pyx_n_s_insertion_coordinates, __pyx_n_s_insertion_sizes, __pyx_n_s_include_indx_set, __pyx_n_s_p, __pyx_n_s_ref_st, __pyx_n_s_ref_en, __pyx_n_s_inc_del_pos, __pyx_n_s_deletion_n, __pyx_n_s_insertion_n, __pyx_n_s_retDict); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(0, 138, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__9);
   __Pyx_GIVEREF(__pyx_tuple__9);
-  __pyx_codeobj__10 = (PyObject*)__Pyx_PyCode_New(3, 0, 0, 33, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__9, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_find_indels_substitutions_legacy, 138, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__10)) __PYX_ERR(0, 138, __pyx_L1_error)
+  __pyx_codeobj__10 = (PyObject*)__Pyx_PyCode_New(3, 0, 0, 34, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__9, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_find_indels_substitutions_legacy, 138, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__10)) __PYX_ERR(0, 138, __pyx_L1_error)
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":265
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":268
  * 
  * 
  * def calculate_homology(a, b):             # <<<<<<<<<<<<<<
  *     cdef char *al = a
  *     cdef char *bl = b
  */
-  __pyx_tuple__11 = PyTuple_Pack(7, __pyx_n_s_a, __pyx_n_s_b, __pyx_n_s_al, __pyx_n_s_bl, __pyx_n_s_l, __pyx_n_s_score, __pyx_n_s_i); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 265, __pyx_L1_error)
+  __pyx_tuple__11 = PyTuple_Pack(7, __pyx_n_s_a, __pyx_n_s_b, __pyx_n_s_al, __pyx_n_s_bl, __pyx_n_s_l, __pyx_n_s_score, __pyx_n_s_i); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 268, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__11);
   __Pyx_GIVEREF(__pyx_tuple__11);
-  __pyx_codeobj__12 = (PyObject*)__Pyx_PyCode_New(2, 0, 0, 7, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__11, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_calculate_homology, 265, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__12)) __PYX_ERR(0, 265, __pyx_L1_error)
+  __pyx_codeobj__12 = (PyObject*)__Pyx_PyCode_New(2, 0, 0, 7, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__11, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_CRISPResso2_CRISPRessoCOREResour, __pyx_n_s_calculate_homology, 268, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__12)) __PYX_ERR(0, 268, __pyx_L1_error)
   __Pyx_RefNannyFinishContext();
   return 0;
   __pyx_L1_error:;
@@ -8198,7 +8649,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) {
  *   <void>numpy._import_array
  */
 #ifdef NPY_FEATURE_VERSION
-#if !NO_IMPORT_ARRAY
+#ifndef NO_IMPORT_ARRAY
 if (unlikely(_import_array() == -1)) {
     PyErr_SetString(PyExc_ImportError, "numpy.core.multiarray failed to import "
     "(auto-generated because you didn't call 'numpy.import_array()' after cimporting numpy; "
@@ -8265,33 +8716,33 @@ static int __Pyx_modinit_type_import_code(void) {
   /*--- Type import code ---*/
   __pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 9, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_ptype_7cpython_4type_type = __Pyx_ImportType_3_0_0(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", 
+  __pyx_ptype_7cpython_4type_type = __Pyx_ImportType_3_0_10(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", 
   #if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000
-  sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyTypeObject),
+  sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyTypeObject),
   #elif CYTHON_COMPILING_IN_LIMITED_API
-  sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyTypeObject),
+  sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyTypeObject),
   #else
-  sizeof(PyHeapTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyHeapTypeObject),
+  sizeof(PyHeapTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyHeapTypeObject),
   #endif
-  __Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error)
+  __Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_t_1 = PyImport_ImportModule("numpy"); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 202, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_ptype_5numpy_dtype = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "dtype", sizeof(PyArray_Descr), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyArray_Descr),__Pyx_ImportType_CheckSize_Ignore_3_0_0); if (!__pyx_ptype_5numpy_dtype) __PYX_ERR(1, 202, __pyx_L1_error)
-  __pyx_ptype_5numpy_flatiter = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "flatiter", sizeof(PyArrayIterObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyArrayIterObject),__Pyx_ImportType_CheckSize_Ignore_3_0_0); if (!__pyx_ptype_5numpy_flatiter) __PYX_ERR(1, 225, __pyx_L1_error)
-  __pyx_ptype_5numpy_broadcast = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "broadcast", sizeof(PyArrayMultiIterObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyArrayMultiIterObject),__Pyx_ImportType_CheckSize_Ignore_3_0_0); if (!__pyx_ptype_5numpy_broadcast) __PYX_ERR(1, 229, __pyx_L1_error)
-  __pyx_ptype_5numpy_ndarray = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "ndarray", sizeof(PyArrayObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyArrayObject),__Pyx_ImportType_CheckSize_Ignore_3_0_0); if (!__pyx_ptype_5numpy_ndarray) __PYX_ERR(1, 238, __pyx_L1_error)
-  __pyx_ptype_5numpy_generic = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "generic", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_generic) __PYX_ERR(1, 809, __pyx_L1_error)
-  __pyx_ptype_5numpy_number = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "number", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_number) __PYX_ERR(1, 811, __pyx_L1_error)
-  __pyx_ptype_5numpy_integer = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "integer", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_integer) __PYX_ERR(1, 813, __pyx_L1_error)
-  __pyx_ptype_5numpy_signedinteger = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "signedinteger", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_signedinteger) __PYX_ERR(1, 815, __pyx_L1_error)
-  __pyx_ptype_5numpy_unsignedinteger = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "unsignedinteger", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_unsignedinteger) __PYX_ERR(1, 817, __pyx_L1_error)
-  __pyx_ptype_5numpy_inexact = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "inexact", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_inexact) __PYX_ERR(1, 819, __pyx_L1_error)
-  __pyx_ptype_5numpy_floating = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "floating", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_floating) __PYX_ERR(1, 821, __pyx_L1_error)
-  __pyx_ptype_5numpy_complexfloating = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "complexfloating", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_complexfloating) __PYX_ERR(1, 823, __pyx_L1_error)
-  __pyx_ptype_5numpy_flexible = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "flexible", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_flexible) __PYX_ERR(1, 825, __pyx_L1_error)
-  __pyx_ptype_5numpy_character = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "character", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_0); if (!__pyx_ptype_5numpy_character) __PYX_ERR(1, 827, __pyx_L1_error)
-  __pyx_ptype_5numpy_ufunc = __Pyx_ImportType_3_0_0(__pyx_t_1, "numpy", "ufunc", sizeof(PyUFuncObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_0(PyUFuncObject),__Pyx_ImportType_CheckSize_Ignore_3_0_0); if (!__pyx_ptype_5numpy_ufunc) __PYX_ERR(1, 865, __pyx_L1_error)
+  __pyx_ptype_5numpy_dtype = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "dtype", sizeof(PyArray_Descr), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyArray_Descr),__Pyx_ImportType_CheckSize_Ignore_3_0_10); if (!__pyx_ptype_5numpy_dtype) __PYX_ERR(1, 202, __pyx_L1_error)
+  __pyx_ptype_5numpy_flatiter = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "flatiter", sizeof(PyArrayIterObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyArrayIterObject),__Pyx_ImportType_CheckSize_Ignore_3_0_10); if (!__pyx_ptype_5numpy_flatiter) __PYX_ERR(1, 225, __pyx_L1_error)
+  __pyx_ptype_5numpy_broadcast = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "broadcast", sizeof(PyArrayMultiIterObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyArrayMultiIterObject),__Pyx_ImportType_CheckSize_Ignore_3_0_10); if (!__pyx_ptype_5numpy_broadcast) __PYX_ERR(1, 229, __pyx_L1_error)
+  __pyx_ptype_5numpy_ndarray = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "ndarray", sizeof(PyArrayObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyArrayObject),__Pyx_ImportType_CheckSize_Ignore_3_0_10); if (!__pyx_ptype_5numpy_ndarray) __PYX_ERR(1, 238, __pyx_L1_error)
+  __pyx_ptype_5numpy_generic = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "generic", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_generic) __PYX_ERR(1, 809, __pyx_L1_error)
+  __pyx_ptype_5numpy_number = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "number", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_number) __PYX_ERR(1, 811, __pyx_L1_error)
+  __pyx_ptype_5numpy_integer = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "integer", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_integer) __PYX_ERR(1, 813, __pyx_L1_error)
+  __pyx_ptype_5numpy_signedinteger = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "signedinteger", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_signedinteger) __PYX_ERR(1, 815, __pyx_L1_error)
+  __pyx_ptype_5numpy_unsignedinteger = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "unsignedinteger", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_unsignedinteger) __PYX_ERR(1, 817, __pyx_L1_error)
+  __pyx_ptype_5numpy_inexact = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "inexact", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_inexact) __PYX_ERR(1, 819, __pyx_L1_error)
+  __pyx_ptype_5numpy_floating = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "floating", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_floating) __PYX_ERR(1, 821, __pyx_L1_error)
+  __pyx_ptype_5numpy_complexfloating = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "complexfloating", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_complexfloating) __PYX_ERR(1, 823, __pyx_L1_error)
+  __pyx_ptype_5numpy_flexible = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "flexible", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_flexible) __PYX_ERR(1, 825, __pyx_L1_error)
+  __pyx_ptype_5numpy_character = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "character", sizeof(PyObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyObject),__Pyx_ImportType_CheckSize_Warn_3_0_10); if (!__pyx_ptype_5numpy_character) __PYX_ERR(1, 827, __pyx_L1_error)
+  __pyx_ptype_5numpy_ufunc = __Pyx_ImportType_3_0_10(__pyx_t_1, "numpy", "ufunc", sizeof(PyUFuncObject), __PYX_GET_STRUCT_ALIGNMENT_3_0_10(PyUFuncObject),__Pyx_ImportType_CheckSize_Ignore_3_0_10); if (!__pyx_ptype_5numpy_ufunc) __PYX_ERR(1, 866, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __Pyx_RefNannyFinishContext();
   return 0;
@@ -8506,7 +8957,7 @@ static CYTHON_SMALL_CODE int __pyx_pymod_exec_CRISPRessoCOREResources(PyObject *
   __pyx_t_1 = PyModule_Create(&__pyx_moduledef); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
   {
     int add_module_result = PyState_AddModule(__pyx_t_1, &__pyx_moduledef);
-    __pyx_t_1 = 0; /* transfer ownership from __pyx_t_1 to CRISPRessoCOREResources pseudovariable */
+    __pyx_t_1 = 0; /* transfer ownership from __pyx_t_1 to "CRISPRessoCOREResources" pseudovariable */
     if (unlikely((add_module_result < 0))) __PYX_ERR(0, 1, __pyx_L1_error)
     pystate_addmodule_run = 1;
   }
@@ -8518,10 +8969,8 @@ static CYTHON_SMALL_CODE int __pyx_pymod_exec_CRISPRessoCOREResources(PyObject *
   CYTHON_UNUSED_VAR(__pyx_t_1);
   __pyx_d = PyModule_GetDict(__pyx_m); if (unlikely(!__pyx_d)) __PYX_ERR(0, 1, __pyx_L1_error)
   Py_INCREF(__pyx_d);
-  __pyx_b = PyImport_AddModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_b)) __PYX_ERR(0, 1, __pyx_L1_error)
-  Py_INCREF(__pyx_b);
-  __pyx_cython_runtime = PyImport_AddModule((char *) "cython_runtime"); if (unlikely(!__pyx_cython_runtime)) __PYX_ERR(0, 1, __pyx_L1_error)
-  Py_INCREF(__pyx_cython_runtime);
+  __pyx_b = __Pyx_PyImport_AddModuleRef(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_b)) __PYX_ERR(0, 1, __pyx_L1_error)
+  __pyx_cython_runtime = __Pyx_PyImport_AddModuleRef((const char *) "cython_runtime"); if (unlikely(!__pyx_cython_runtime)) __PYX_ERR(0, 1, __pyx_L1_error)
   if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
   #if CYTHON_REFNANNY
 __Pyx_RefNanny = __Pyx_RefNannyImportAPI("refnanny");
@@ -8533,7 +8982,7 @@ if (!__Pyx_RefNanny) {
 }
 #endif
   __Pyx_RefNannySetupContext("__Pyx_PyMODINIT_FUNC PyInit_CRISPRessoCOREResources(void)", 0);
-  if (__Pyx_check_binary_version() < 0) __PYX_ERR(0, 1, __pyx_L1_error)
+  if (__Pyx_check_binary_version(__PYX_LIMITED_VERSION_HEX, __Pyx_get_runtime_version(), CYTHON_COMPILING_IN_LIMITED_API) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
   #ifdef __Pxy_PyFrame_Initialize_Offsets
   __Pxy_PyFrame_Initialize_Offsets();
   #endif
@@ -8621,39 +9070,39 @@ if (!__Pyx_RefNanny) {
   if (PyDict_SetItem(__pyx_d, __pyx_n_s_re, __pyx_t_2) < 0) __PYX_ERR(0, 4, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":17
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":16
  * 
  * 
  * re_find_indels = re.compile("(-*-)")             # <<<<<<<<<<<<<<
  * 
  * @cython.boundscheck(False)
  */
-  __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_re); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 17, __pyx_L1_error)
+  __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_re); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_compile); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 17, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_compile); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 16, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 17, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 16, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_re_find_indels, __pyx_t_2) < 0) __PYX_ERR(0, 17, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_re_find_indels, __pyx_t_2) < 0) __PYX_ERR(0, 16, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":19
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":18
  * re_find_indels = re.compile("(-*-)")
  * 
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
  */
-  __pyx_t_2 = __Pyx_CyFunction_New(&__pyx_mdef_11CRISPResso2_23CRISPRessoCOREResources_1find_indels_substitutions, 0, __pyx_n_s_find_indels_substitutions, NULL, __pyx_n_s_CRISPResso2_CRISPRessoCOREResour_2, __pyx_d, ((PyObject *)__pyx_codeobj__8)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 19, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_CyFunction_New(&__pyx_mdef_11CRISPResso2_23CRISPRessoCOREResources_1find_indels_substitutions, 0, __pyx_n_s_find_indels_substitutions, NULL, __pyx_n_s_CRISPResso2_CRISPRessoCOREResour_2, __pyx_d, ((PyObject *)__pyx_codeobj__8)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 18, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_find_indels_substitutions, __pyx_t_2) < 0) __PYX_ERR(0, 19, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_find_indels_substitutions, __pyx_t_2) < 0) __PYX_ERR(0, 18, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":138
- * 
- * 
+ *         'ref_positions': ref_positions,
+ *     }
  * @cython.boundscheck(False)             # <<<<<<<<<<<<<<
  * @cython.nonecheck(False)
  * @cython.wraparound(False)
@@ -8663,16 +9112,16 @@ if (!__Pyx_RefNanny) {
   if (PyDict_SetItem(__pyx_d, __pyx_n_s_find_indels_substitutions_legacy, __pyx_t_2) < 0) __PYX_ERR(0, 138, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
-  /* "CRISPResso2/CRISPRessoCOREResources.pyx":265
+  /* "CRISPResso2/CRISPRessoCOREResources.pyx":268
  * 
  * 
  * def calculate_homology(a, b):             # <<<<<<<<<<<<<<
  *     cdef char *al = a
  *     cdef char *bl = b
  */
-  __pyx_t_2 = __Pyx_CyFunction_New(&__pyx_mdef_11CRISPResso2_23CRISPRessoCOREResources_5calculate_homology, 0, __pyx_n_s_calculate_homology, NULL, __pyx_n_s_CRISPResso2_CRISPRessoCOREResour_2, __pyx_d, ((PyObject *)__pyx_codeobj__12)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 265, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_CyFunction_New(&__pyx_mdef_11CRISPResso2_23CRISPRessoCOREResources_5calculate_homology, 0, __pyx_n_s_calculate_homology, NULL, __pyx_n_s_CRISPResso2_CRISPRessoCOREResour_2, __pyx_d, ((PyObject *)__pyx_codeobj__12)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 268, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_calculate_homology, __pyx_t_2) < 0) __PYX_ERR(0, 265, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_calculate_homology, __pyx_t_2) < 0) __PYX_ERR(0, 268, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
   /* "CRISPResso2/CRISPRessoCOREResources.pyx":1
@@ -8812,6 +9261,8 @@ static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObjec
     tmp_value = tstate->current_exception;
     tstate->current_exception = value;
     Py_XDECREF(tmp_value);
+    Py_XDECREF(type);
+    Py_XDECREF(tb);
 #else
     PyObject *tmp_type, *tmp_value, *tmp_tb;
     tmp_type = tstate->curexc_type;
@@ -8869,14 +9320,20 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject
 #endif
 
 /* PyObjectGetAttrStrNoError */
+#if __PYX_LIMITED_VERSION_HEX < 0x030d00A1
 static void __Pyx_PyObject_GetAttrStr_ClearAttributeError(void) {
     __Pyx_PyThreadState_declare
     __Pyx_PyThreadState_assign
     if (likely(__Pyx_PyErr_ExceptionMatches(PyExc_AttributeError)))
         __Pyx_PyErr_Clear();
 }
+#endif
 static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStrNoError(PyObject* obj, PyObject* attr_name) {
     PyObject *result;
+#if __PYX_LIMITED_VERSION_HEX >= 0x030d00A1
+    (void) PyObject_GetOptionalAttr(obj, attr_name, &result);
+    return result;
+#else
 #if CYTHON_COMPILING_IN_CPYTHON && CYTHON_USE_TYPE_SLOTS && PY_VERSION_HEX >= 0x030700B1
     PyTypeObject* tp = Py_TYPE(obj);
     if (likely(tp->tp_getattro == PyObject_GenericGetAttr)) {
@@ -8888,6 +9345,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStrNoError(PyObject* obj, P
         __Pyx_PyObject_GetAttrStr_ClearAttributeError();
     }
     return result;
+#endif
 }
 
 /* GetBuiltinName */
@@ -9088,8 +9546,13 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg
     ternaryfunc call = Py_TYPE(func)->tp_call;
     if (unlikely(!call))
         return PyObject_Call(func, arg, kw);
+    #if PY_MAJOR_VERSION < 3
     if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object")))
         return NULL;
+    #else
+    if (unlikely(Py_EnterRecursiveCall(" while calling a Python object")))
+        return NULL;
+    #endif
     result = (*call)(func, arg, kw);
     Py_LeaveRecursiveCall();
     if (unlikely(!result) && unlikely(!PyErr_Occurred())) {
@@ -9461,13 +9924,31 @@ static CYTHON_INLINE PyObject * __Pyx_GetKwValue_FASTCALL(PyObject *kwnames, PyO
     {
         int eq = __Pyx_PyUnicode_Equals(s, PyTuple_GET_ITEM(kwnames, i), Py_EQ);
         if (unlikely(eq != 0)) {
-            if (unlikely(eq < 0)) return NULL;  // error
+            if (unlikely(eq < 0)) return NULL;
             return kwvalues[i];
         }
     }
-    return NULL;  // not found (no exception set)
+    return NULL;
+}
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030d0000
+CYTHON_UNUSED static PyObject *__Pyx_KwargsAsDict_FASTCALL(PyObject *kwnames, PyObject *const *kwvalues) {
+    Py_ssize_t i, nkwargs = PyTuple_GET_SIZE(kwnames);
+    PyObject *dict;
+    dict = PyDict_New();
+    if (unlikely(!dict))
+        return NULL;
+    for (i=0; i<nkwargs; i++) {
+        PyObject *key = PyTuple_GET_ITEM(kwnames, i);
+        if (unlikely(PyDict_SetItem(dict, key, kwvalues[i]) < 0))
+            goto bad;
+    }
+    return dict;
+bad:
+    Py_DECREF(dict);
+    return NULL;
 }
 #endif
+#endif
 
 /* RaiseArgTupleInvalid */
 static void __Pyx_RaiseArgtupleInvalid(
@@ -9525,22 +10006,52 @@ static int __Pyx_ParseOptionalKeywords(
     PyObject*** first_kw_arg = argnames + num_pos_args;
     int kwds_is_tuple = CYTHON_METH_FASTCALL && likely(PyTuple_Check(kwds));
     while (1) {
+        Py_XDECREF(key); key = NULL;
+        Py_XDECREF(value); value = NULL;
         if (kwds_is_tuple) {
-            if (pos >= PyTuple_GET_SIZE(kwds)) break;
+            Py_ssize_t size;
+#if CYTHON_ASSUME_SAFE_MACROS
+            size = PyTuple_GET_SIZE(kwds);
+#else
+            size = PyTuple_Size(kwds);
+            if (size < 0) goto bad;
+#endif
+            if (pos >= size) break;
+#if CYTHON_AVOID_BORROWED_REFS
+            key = __Pyx_PySequence_ITEM(kwds, pos);
+            if (!key) goto bad;
+#elif CYTHON_ASSUME_SAFE_MACROS
             key = PyTuple_GET_ITEM(kwds, pos);
+#else
+            key = PyTuple_GetItem(kwds, pos);
+            if (!key) goto bad;
+#endif
             value = kwvalues[pos];
             pos++;
         }
         else
         {
             if (!PyDict_Next(kwds, &pos, &key, &value)) break;
+#if CYTHON_AVOID_BORROWED_REFS
+            Py_INCREF(key);
+#endif
         }
         name = first_kw_arg;
         while (*name && (**name != key)) name++;
         if (*name) {
             values[name-argnames] = value;
+#if CYTHON_AVOID_BORROWED_REFS
+            Py_INCREF(value);
+            Py_DECREF(key);
+#endif
+            key = NULL;
+            value = NULL;
             continue;
         }
+#if !CYTHON_AVOID_BORROWED_REFS
+        Py_INCREF(key);
+#endif
+        Py_INCREF(value);
         name = first_kw_arg;
         #if PY_MAJOR_VERSION < 3
         if (likely(PyString_Check(key))) {
@@ -9548,6 +10059,9 @@ static int __Pyx_ParseOptionalKeywords(
                 if ((CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**name) == PyString_GET_SIZE(key))
                         && _PyString_Eq(**name, key)) {
                     values[name-argnames] = value;
+#if CYTHON_AVOID_BORROWED_REFS
+                    value = NULL;
+#endif
                     break;
                 }
                 name++;
@@ -9577,6 +10091,9 @@ static int __Pyx_ParseOptionalKeywords(
                 if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad;
                 if (cmp == 0) {
                     values[name-argnames] = value;
+#if CYTHON_AVOID_BORROWED_REFS
+                    value = NULL;
+#endif
                     break;
                 }
                 name++;
@@ -9603,6 +10120,8 @@ static int __Pyx_ParseOptionalKeywords(
             goto invalid_keyword;
         }
     }
+    Py_XDECREF(key);
+    Py_XDECREF(value);
     return 0;
 arg_passed_twice:
     __Pyx_RaiseDoubleKeywordsError(function_name, key);
@@ -9622,6 +10141,8 @@ static int __Pyx_ParseOptionalKeywords(
         function_name, key);
     #endif
 bad:
+    Py_XDECREF(key);
+    Py_XDECREF(value);
     return -1;
 }
 
@@ -9713,7 +10234,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
         }
     }
 #else
-    if (is_list || PySequence_Check(o)) {
+    if (is_list || !PyMapping_Check(o)) {
         return PySequence_GetItem(o, i);
     }
 #endif
@@ -9776,9 +10297,10 @@ static CYTHON_INLINE int __Pyx_PySet_ContainsTF(PyObject* key, PyObject* set, in
 
 /* UnpackUnboundCMethod */
 static PyObject *__Pyx_SelflessCall(PyObject *method, PyObject *args, PyObject *kwargs) {
+    PyObject *result;
     PyObject *selfless_args = PyTuple_GetSlice(args, 1, PyTuple_Size(args));
     if (unlikely(!selfless_args)) return NULL;
-    PyObject *result = PyObject_Call(method, selfless_args, kwargs);
+    result = PyObject_Call(method, selfless_args, kwargs);
     Py_DECREF(selfless_args);
     return result;
 }
@@ -9798,7 +10320,7 @@ static int __Pyx_TryUnpackUnboundCMethod(__Pyx_CachedCFunction* target) {
     #if PY_MAJOR_VERSION >= 3
     if (likely(__Pyx_TypeCheck(method, &PyMethodDescr_Type)))
     #else
-    if (likely(!PyCFunction_Check(method)))
+    if (likely(!__Pyx_CyOrPyCFunction_Check(method)))
     #endif
     {
         PyMethodDescrObject *descr = (PyMethodDescrObject*) method;
@@ -9806,9 +10328,7 @@ static int __Pyx_TryUnpackUnboundCMethod(__Pyx_CachedCFunction* target) {
         target->flag = descr->d_method->ml_flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_STACKLESS);
     } else
 #endif
-#if defined(CYTHON_COMPILING_IN_PYPY)
-#elif PY_VERSION_HEX >= 0x03090000
-    if (PyCFunction_CheckExact(method))
+#if CYTHON_COMPILING_IN_PYPY
 #else
     if (PyCFunction_Check(method))
 #endif
@@ -9934,9 +10454,15 @@ static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args,
     PyObject *result;
     assert(kwargs == NULL || PyDict_Check(kwargs));
     nk = kwargs ? PyDict_Size(kwargs) : 0;
+    #if PY_MAJOR_VERSION < 3
     if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object"))) {
         return NULL;
     }
+    #else
+    if (unlikely(Py_EnterRecursiveCall(" while calling a Python object"))) {
+        return NULL;
+    }
+    #endif
     if (
 #if PY_MAJOR_VERSION >= 3
             co->co_kwonlyargcount == 0 &&
@@ -10011,10 +10537,15 @@ static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args,
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) {
     PyObject *self, *result;
     PyCFunction cfunc;
-    cfunc = PyCFunction_GET_FUNCTION(func);
-    self = PyCFunction_GET_SELF(func);
+    cfunc = __Pyx_CyOrPyCFunction_GET_FUNCTION(func);
+    self = __Pyx_CyOrPyCFunction_GET_SELF(func);
+    #if PY_MAJOR_VERSION < 3
     if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object")))
         return NULL;
+    #else
+    if (unlikely(Py_EnterRecursiveCall(" while calling a Python object")))
+        return NULL;
+    #endif
     result = cfunc(self, arg);
     Py_LeaveRecursiveCall();
     if (unlikely(!result) && unlikely(!PyErr_Occurred())) {
@@ -10027,42 +10558,33 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject
 #endif
 
 /* PyObjectFastCall */
+#if PY_VERSION_HEX < 0x03090000 || CYTHON_COMPILING_IN_LIMITED_API
 static PyObject* __Pyx_PyObject_FastCall_fallback(PyObject *func, PyObject **args, size_t nargs, PyObject *kwargs) {
     PyObject *argstuple;
-    PyObject *result;
+    PyObject *result = 0;
     size_t i;
     argstuple = PyTuple_New((Py_ssize_t)nargs);
     if (unlikely(!argstuple)) return NULL;
     for (i = 0; i < nargs; i++) {
         Py_INCREF(args[i]);
-        PyTuple_SET_ITEM(argstuple, (Py_ssize_t)i, args[i]);
+        if (__Pyx_PyTuple_SET_ITEM(argstuple, (Py_ssize_t)i, args[i]) < 0) goto bad;
     }
     result = __Pyx_PyObject_Call(func, argstuple, kwargs);
+  bad:
     Py_DECREF(argstuple);
     return result;
 }
+#endif
 static CYTHON_INLINE PyObject* __Pyx_PyObject_FastCallDict(PyObject *func, PyObject **args, size_t _nargs, PyObject *kwargs) {
     Py_ssize_t nargs = __Pyx_PyVectorcall_NARGS(_nargs);
 #if CYTHON_COMPILING_IN_CPYTHON
     if (nargs == 0 && kwargs == NULL) {
-#if defined(__Pyx_CyFunction_USED) && defined(NDEBUG)
-        if (__Pyx_IsCyOrPyCFunction(func))
-#else
-        if (PyCFunction_Check(func))
-#endif
-        {
-            if (likely(PyCFunction_GET_FLAGS(func) & METH_NOARGS)) {
-                return __Pyx_PyObject_CallMethO(func, NULL);
-            }
-        }
+        if (__Pyx_CyOrPyCFunction_Check(func) && likely( __Pyx_CyOrPyCFunction_GET_FLAGS(func) & METH_NOARGS))
+            return __Pyx_PyObject_CallMethO(func, NULL);
     }
     else if (nargs == 1 && kwargs == NULL) {
-        if (PyCFunction_Check(func))
-        {
-            if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) {
-                return __Pyx_PyObject_CallMethO(func, args[0]);
-            }
-        }
+        if (__Pyx_CyOrPyCFunction_Check(func) && likely( __Pyx_CyOrPyCFunction_GET_FLAGS(func) & METH_O))
+            return __Pyx_PyObject_CallMethO(func, args[0]);
     }
 #endif
     #if PY_VERSION_HEX < 0x030800B1
@@ -10086,21 +10608,31 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_FastCallDict(PyObject *func, PyObj
     }
     #endif
     #endif
-    #if CYTHON_VECTORCALL
-    vectorcallfunc f = _PyVectorcall_Function(func);
-    if (f) {
-        return f(func, args, (size_t)nargs, kwargs);
-    }
-    #elif defined(__Pyx_CyFunction_USED) && CYTHON_BACKPORT_VECTORCALL
-    if (__Pyx_CyFunction_CheckExact(func)) {
-        __pyx_vectorcallfunc f = __Pyx_CyFunction_func_vectorcall(func);
-        if (f) return f(func, args, (size_t)nargs, kwargs);
+    if (kwargs == NULL) {
+        #if CYTHON_VECTORCALL
+        #if PY_VERSION_HEX < 0x03090000
+        vectorcallfunc f = _PyVectorcall_Function(func);
+        #else
+        vectorcallfunc f = PyVectorcall_Function(func);
+        #endif
+        if (f) {
+            return f(func, args, (size_t)nargs, NULL);
+        }
+        #elif defined(__Pyx_CyFunction_USED) && CYTHON_BACKPORT_VECTORCALL
+        if (__Pyx_CyFunction_CheckExact(func)) {
+            __pyx_vectorcallfunc f = __Pyx_CyFunction_func_vectorcall(func);
+            if (f) return f(func, args, (size_t)nargs, NULL);
+        }
+        #endif
     }
-    #endif
     if (nargs == 0) {
         return __Pyx_PyObject_Call(func, __pyx_empty_tuple, kwargs);
     }
+    #if PY_VERSION_HEX >= 0x03090000 && !CYTHON_COMPILING_IN_LIMITED_API
+    return PyObject_VectorcallDict(func, args, (size_t)nargs, kwargs);
+    #else
     return __Pyx_PyObject_FastCall_fallback(func, args, (size_t)nargs, kwargs);
+    #endif
 }
 
 /* PyObjectCallOneArg */
@@ -10144,7 +10676,7 @@ static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name)
 {
     PyObject *result;
 #if !CYTHON_AVOID_BORROWED_REFS
-#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1
+#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 && PY_VERSION_HEX < 0x030d0000
     result = _PyDict_GetItem_KnownHash(__pyx_d, name, ((PyASCIIObject *) name)->hash);
     __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version)
     if (likely(result)) {
@@ -10193,9 +10725,10 @@ static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
 
 /* IterFinish */
 static CYTHON_INLINE int __Pyx_IterFinish(void) {
+    PyObject* exc_type;
     __Pyx_PyThreadState_declare
     __Pyx_PyThreadState_assign
-    PyObject* exc_type = __Pyx_PyErr_CurrentExceptionType();
+    exc_type = __Pyx_PyErr_CurrentExceptionType();
     if (unlikely(exc_type)) {
         if (unlikely(!__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)))
             return -1;
@@ -10216,10 +10749,10 @@ static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) {
 }
 
 /* TypeImport */
-#ifndef __PYX_HAVE_RT_ImportType_3_0_0
-#define __PYX_HAVE_RT_ImportType_3_0_0
-static PyTypeObject *__Pyx_ImportType_3_0_0(PyObject *module, const char *module_name, const char *class_name,
-    size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_0_0 check_size)
+#ifndef __PYX_HAVE_RT_ImportType_3_0_10
+#define __PYX_HAVE_RT_ImportType_3_0_10
+static PyTypeObject *__Pyx_ImportType_3_0_10(PyObject *module, const char *module_name, const char *class_name,
+    size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_0_10 check_size)
 {
     PyObject *result = 0;
     char warning[200];
@@ -10273,7 +10806,7 @@ static PyTypeObject *__Pyx_ImportType_3_0_0(PyObject *module, const char *module
             module_name, class_name, size, basicsize+itemsize);
         goto bad;
     }
-    if (check_size == __Pyx_ImportType_CheckSize_Error_3_0_0 &&
+    if (check_size == __Pyx_ImportType_CheckSize_Error_3_0_10 &&
             ((size_t)basicsize > size || (size_t)(basicsize + itemsize) < size)) {
         PyErr_Format(PyExc_ValueError,
             "%.200s.%.200s size changed, may indicate binary incompatibility. "
@@ -10281,7 +10814,7 @@ static PyTypeObject *__Pyx_ImportType_3_0_0(PyObject *module, const char *module
             module_name, class_name, size, basicsize, basicsize+itemsize);
         goto bad;
     }
-    else if (check_size == __Pyx_ImportType_CheckSize_Warn_3_0_0 && (size_t)basicsize > size) {
+    else if (check_size == __Pyx_ImportType_CheckSize_Warn_3_0_10 && (size_t)basicsize > size) {
         PyOS_snprintf(warning, sizeof(warning),
             "%s.%s size changed, may indicate binary incompatibility. "
             "Expected %zd from C header, got %zd from PyObject",
@@ -10318,14 +10851,9 @@ static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) {
     {
         #if PY_MAJOR_VERSION >= 3
         if (level == -1) {
-            if ((1) && (strchr(__Pyx_MODULE_NAME, '.'))) {
-                #if CYTHON_COMPILING_IN_LIMITED_API
-                module = PyImport_ImportModuleLevelObject(
-                    name, empty_dict, empty_dict, from_list, 1);
-                #else
+            if (strchr(__Pyx_MODULE_NAME, '.') != NULL) {
                 module = PyImport_ImportModuleLevelObject(
                     name, __pyx_d, empty_dict, from_list, 1);
-                #endif
                 if (unlikely(!module)) {
                     if (unlikely(!PyErr_ExceptionMatches(PyExc_ImportError)))
                         goto bad;
@@ -10344,14 +10872,9 @@ static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) {
                 name, __pyx_d, empty_dict, from_list, py_level, (PyObject *)NULL);
             Py_DECREF(py_level);
             #else
-            #if CYTHON_COMPILING_IN_LIMITED_API
-            module = PyImport_ImportModuleLevelObject(
-                name, empty_dict, empty_dict, from_list, level);
-            #else
             module = PyImport_ImportModuleLevelObject(
                 name, __pyx_d, empty_dict, from_list, level);
             #endif
-            #endif
         }
     }
 bad:
@@ -10566,10 +11089,7 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
 
 /* FetchSharedCythonModule */
 static PyObject *__Pyx_FetchSharedCythonABIModule(void) {
-    PyObject *abi_module = PyImport_AddModule((char*) __PYX_ABI_MODULE_NAME);
-    if (unlikely(!abi_module)) return NULL;
-    Py_INCREF(abi_module);
-    return abi_module;
+    return __Pyx_PyImport_AddModuleRef((char*) __PYX_ABI_MODULE_NAME);
 }
 
 /* FetchCommonType */
@@ -10730,8 +11250,22 @@ static CYTHON_INLINE PyObject *__Pyx_PyVectorcall_FastCallDict(PyObject *func, _
 #endif
 
 /* CythonFunctionShared */
+#if CYTHON_COMPILING_IN_LIMITED_API
+static CYTHON_INLINE int __Pyx__IsSameCyOrCFunction(PyObject *func, void *cfunc) {
+    if (__Pyx_CyFunction_Check(func)) {
+        return PyCFunction_GetFunction(((__pyx_CyFunctionObject*)func)->func) == (PyCFunction) cfunc;
+    } else if (PyCFunction_Check(func)) {
+        return PyCFunction_GetFunction(func) == (PyCFunction) cfunc;
+    }
+    return 0;
+}
+#else
+static CYTHON_INLINE int __Pyx__IsSameCyOrCFunction(PyObject *func, void *cfunc) {
+    return __Pyx_CyOrPyCFunction_Check(func) && __Pyx_CyOrPyCFunction_GET_FUNCTION(func) == (PyCFunction) cfunc;
+}
+#endif
 static CYTHON_INLINE void __Pyx__CyFunction_SetClassObj(__pyx_CyFunctionObject* f, PyObject* classobj) {
-#if PY_VERSION_HEX < 0x030900B1
+#if PY_VERSION_HEX < 0x030900B1 || CYTHON_COMPILING_IN_LIMITED_API
     __Pyx_Py_XDECREF_SET(
         __Pyx_CyFunction_GetClassObj(f),
             ((classobj) ? __Pyx_NewRef(classobj) : NULL));
@@ -10746,6 +11280,10 @@ __Pyx_CyFunction_get_doc(__pyx_CyFunctionObject *op, void *closure)
 {
     CYTHON_UNUSED_VAR(closure);
     if (unlikely(op->func_doc == NULL)) {
+#if CYTHON_COMPILING_IN_LIMITED_API
+        op->func_doc = PyObject_GetAttrString(op->func, "__doc__");
+        if (unlikely(!op->func_doc)) return NULL;
+#else
         if (((PyCFunctionObject*)op)->m_ml->ml_doc) {
 #if PY_MAJOR_VERSION >= 3
             op->func_doc = PyUnicode_FromString(((PyCFunctionObject*)op)->m_ml->ml_doc);
@@ -10758,6 +11296,7 @@ __Pyx_CyFunction_get_doc(__pyx_CyFunctionObject *op, void *closure)
             Py_INCREF(Py_None);
             return Py_None;
         }
+#endif
     }
     Py_INCREF(op->func_doc);
     return op->func_doc;
@@ -10778,7 +11317,9 @@ __Pyx_CyFunction_get_name(__pyx_CyFunctionObject *op, void *context)
 {
     CYTHON_UNUSED_VAR(context);
     if (unlikely(op->func_name == NULL)) {
-#if PY_MAJOR_VERSION >= 3
+#if CYTHON_COMPILING_IN_LIMITED_API
+        op->func_name = PyObject_GetAttrString(op->func, "__name__");
+#elif PY_MAJOR_VERSION >= 3
         op->func_name = PyUnicode_InternFromString(((PyCFunctionObject*)op)->m_ml->ml_name);
 #else
         op->func_name = PyString_InternFromString(((PyCFunctionObject*)op)->m_ml->ml_name);
@@ -10897,10 +11438,10 @@ __Pyx_CyFunction_init_defaults(__pyx_CyFunctionObject *op) {
     op->defaults_kwdict = PyTuple_GET_ITEM(res, 1);
     Py_INCREF(op->defaults_kwdict);
     #else
-    op->defaults_tuple = PySequence_ITEM(res, 0);
+    op->defaults_tuple = __Pyx_PySequence_ITEM(res, 0);
     if (unlikely(!op->defaults_tuple)) result = -1;
     else {
-        op->defaults_kwdict = PySequence_ITEM(res, 1);
+        op->defaults_kwdict = __Pyx_PySequence_ITEM(res, 1);
         if (unlikely(!op->defaults_kwdict)) result = -1;
     }
     #endif
@@ -11009,7 +11550,15 @@ __Pyx_CyFunction_get_is_coroutine(__pyx_CyFunctionObject *op, void *context) {
         fromlist = PyList_New(1);
         if (unlikely(!fromlist)) return NULL;
         Py_INCREF(marker);
+#if CYTHON_ASSUME_SAFE_MACROS
         PyList_SET_ITEM(fromlist, 0, marker);
+#else
+        if (unlikely(PyList_SetItem(fromlist, 0, marker) < 0)) {
+            Py_DECREF(marker);
+            Py_DECREF(fromlist);
+            return NULL;
+        }
+#endif
         module = PyImport_ImportModuleLevelObject(__pyx_n_s_asyncio_coroutines, NULL, NULL, fromlist, 0);
         Py_DECREF(fromlist);
         if (unlikely(!module)) goto ignore;
@@ -11025,6 +11574,18 @@ __Pyx_CyFunction_get_is_coroutine(__pyx_CyFunctionObject *op, void *context) {
     op->func_is_coroutine = __Pyx_PyBool_FromLong(is_coroutine);
     return __Pyx_NewRef(op->func_is_coroutine);
 }
+#if CYTHON_COMPILING_IN_LIMITED_API
+static PyObject *
+__Pyx_CyFunction_get_module(__pyx_CyFunctionObject *op, void *context) {
+    CYTHON_UNUSED_VAR(context);
+    return PyObject_GetAttrString(op->func, "__module__");
+}
+static int
+__Pyx_CyFunction_set_module(__pyx_CyFunctionObject *op, PyObject* value, void *context) {
+    CYTHON_UNUSED_VAR(context);
+    return PyObject_SetAttrString(op->func, "__module__", value);
+}
+#endif
 static PyGetSetDef __pyx_CyFunction_getsets[] = {
     {(char *) "func_doc", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
     {(char *) "__doc__",  (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
@@ -11044,20 +11605,27 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = {
     {(char *) "__kwdefaults__", (getter)__Pyx_CyFunction_get_kwdefaults, (setter)__Pyx_CyFunction_set_kwdefaults, 0, 0},
     {(char *) "__annotations__", (getter)__Pyx_CyFunction_get_annotations, (setter)__Pyx_CyFunction_set_annotations, 0, 0},
     {(char *) "_is_coroutine", (getter)__Pyx_CyFunction_get_is_coroutine, 0, 0, 0},
+#if CYTHON_COMPILING_IN_LIMITED_API
+    {"__module__", (getter)__Pyx_CyFunction_get_module, (setter)__Pyx_CyFunction_set_module, 0, 0},
+#endif
     {0, 0, 0, 0, 0}
 };
 static PyMemberDef __pyx_CyFunction_members[] = {
+#if !CYTHON_COMPILING_IN_LIMITED_API
     {(char *) "__module__", T_OBJECT, offsetof(PyCFunctionObject, m_module), 0, 0},
+#endif
 #if CYTHON_USE_TYPE_SPECS
     {(char *) "__dictoffset__", T_PYSSIZET, offsetof(__pyx_CyFunctionObject, func_dict), READONLY, 0},
 #if CYTHON_METH_FASTCALL
 #if CYTHON_BACKPORT_VECTORCALL
     {(char *) "__vectorcalloffset__", T_PYSSIZET, offsetof(__pyx_CyFunctionObject, func_vectorcall), READONLY, 0},
 #else
+#if !CYTHON_COMPILING_IN_LIMITED_API
     {(char *) "__vectorcalloffset__", T_PYSSIZET, offsetof(PyCFunctionObject, vectorcall), READONLY, 0},
 #endif
 #endif
-#if PY_VERSION_HEX < 0x030500A0
+#endif
+#if PY_VERSION_HEX < 0x030500A0 || CYTHON_COMPILING_IN_LIMITED_API
     {(char *) "__weaklistoffset__", T_PYSSIZET, offsetof(__pyx_CyFunctionObject, func_weakreflist), READONLY, 0},
 #else
     {(char *) "__weaklistoffset__", T_PYSSIZET, offsetof(PyCFunctionObject, m_weakreflist), READONLY, 0},
@@ -11080,30 +11648,40 @@ static PyMethodDef __pyx_CyFunction_methods[] = {
     {"__reduce__", (PyCFunction)__Pyx_CyFunction_reduce, METH_VARARGS, 0},
     {0, 0, 0, 0}
 };
-#if PY_VERSION_HEX < 0x030500A0
+#if PY_VERSION_HEX < 0x030500A0 || CYTHON_COMPILING_IN_LIMITED_API
 #define __Pyx_CyFunction_weakreflist(cyfunc) ((cyfunc)->func_weakreflist)
 #else
 #define __Pyx_CyFunction_weakreflist(cyfunc) (((PyCFunctionObject*)cyfunc)->m_weakreflist)
 #endif
 static PyObject *__Pyx_CyFunction_Init(__pyx_CyFunctionObject *op, PyMethodDef *ml, int flags, PyObject* qualname,
                                        PyObject *closure, PyObject *module, PyObject* globals, PyObject* code) {
+#if !CYTHON_COMPILING_IN_LIMITED_API
     PyCFunctionObject *cf = (PyCFunctionObject*) op;
+#endif
     if (unlikely(op == NULL))
         return NULL;
+#if CYTHON_COMPILING_IN_LIMITED_API
+    op->func = PyCFunction_NewEx(ml, (PyObject*)op, module);
+    if (unlikely(!op->func)) return NULL;
+#endif
     op->flags = flags;
     __Pyx_CyFunction_weakreflist(op) = NULL;
+#if !CYTHON_COMPILING_IN_LIMITED_API
     cf->m_ml = ml;
     cf->m_self = (PyObject *) op;
+#endif
     Py_XINCREF(closure);
     op->func_closure = closure;
+#if !CYTHON_COMPILING_IN_LIMITED_API
     Py_XINCREF(module);
     cf->m_module = module;
+#endif
     op->func_dict = NULL;
     op->func_name = NULL;
     Py_INCREF(qualname);
     op->func_qualname = qualname;
     op->func_doc = NULL;
-#if PY_VERSION_HEX < 0x030900B1
+#if PY_VERSION_HEX < 0x030900B1 || CYTHON_COMPILING_IN_LIMITED_API
     op->func_classobj = NULL;
 #else
     ((PyCMethodObject*)op)->mm_class = NULL;
@@ -11149,13 +11727,18 @@ static int
 __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
 {
     Py_CLEAR(m->func_closure);
+#if CYTHON_COMPILING_IN_LIMITED_API
+    Py_CLEAR(m->func);
+#else
     Py_CLEAR(((PyCFunctionObject*)m)->m_module);
+#endif
     Py_CLEAR(m->func_dict);
     Py_CLEAR(m->func_name);
     Py_CLEAR(m->func_qualname);
     Py_CLEAR(m->func_doc);
     Py_CLEAR(m->func_globals);
     Py_CLEAR(m->func_code);
+#if !CYTHON_COMPILING_IN_LIMITED_API
 #if PY_VERSION_HEX < 0x030900B1
     Py_CLEAR(__Pyx_CyFunction_GetClassObj(m));
 #else
@@ -11164,6 +11747,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
         ((PyCMethodObject *) (m))->mm_class = NULL;
         Py_XDECREF(cls);
     }
+#endif
 #endif
     Py_CLEAR(m->defaults_tuple);
     Py_CLEAR(m->defaults_kwdict);
@@ -11194,14 +11778,20 @@ static void __Pyx_CyFunction_dealloc(__pyx_CyFunctionObject *m)
 static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, void *arg)
 {
     Py_VISIT(m->func_closure);
+#if CYTHON_COMPILING_IN_LIMITED_API
+    Py_VISIT(m->func);
+#else
     Py_VISIT(((PyCFunctionObject*)m)->m_module);
+#endif
     Py_VISIT(m->func_dict);
     Py_VISIT(m->func_name);
     Py_VISIT(m->func_qualname);
     Py_VISIT(m->func_doc);
     Py_VISIT(m->func_globals);
     Py_VISIT(m->func_code);
+#if !CYTHON_COMPILING_IN_LIMITED_API
     Py_VISIT(__Pyx_CyFunction_GetClassObj(m));
+#endif
     Py_VISIT(m->defaults_tuple);
     Py_VISIT(m->defaults_kwdict);
     Py_VISIT(m->func_is_coroutine);
@@ -11225,10 +11815,22 @@ __Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
 #endif
 }
 static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, PyObject *arg, PyObject *kw) {
+#if CYTHON_COMPILING_IN_LIMITED_API
+    PyObject *f = ((__pyx_CyFunctionObject*)func)->func;
+    PyObject *py_name = NULL;
+    PyCFunction meth;
+    int flags;
+    meth = PyCFunction_GetFunction(f);
+    if (unlikely(!meth)) return NULL;
+    flags = PyCFunction_GetFlags(f);
+    if (unlikely(flags < 0)) return NULL;
+#else
     PyCFunctionObject* f = (PyCFunctionObject*)func;
     PyCFunction meth = f->m_ml->ml_meth;
+    int flags = f->m_ml->ml_flags;
+#endif
     Py_ssize_t size;
-    switch (f->m_ml->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
+    switch (flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
     case METH_VARARGS:
         if (likely(kw == NULL || PyDict_Size(kw) == 0))
             return (*meth)(self, arg);
@@ -11237,24 +11839,43 @@ static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, Py
         return (*(PyCFunctionWithKeywords)(void*)meth)(self, arg, kw);
     case METH_NOARGS:
         if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
+#if CYTHON_ASSUME_SAFE_MACROS
             size = PyTuple_GET_SIZE(arg);
+#else
+            size = PyTuple_Size(arg);
+            if (unlikely(size < 0)) return NULL;
+#endif
             if (likely(size == 0))
                 return (*meth)(self, NULL);
+#if CYTHON_COMPILING_IN_LIMITED_API
+            py_name = __Pyx_CyFunction_get_name((__pyx_CyFunctionObject*)func, NULL);
+            if (!py_name) return NULL;
+            PyErr_Format(PyExc_TypeError,
+                "%.200S() takes no arguments (%" CYTHON_FORMAT_SSIZE_T "d given)",
+                py_name, size);
+            Py_DECREF(py_name);
+#else
             PyErr_Format(PyExc_TypeError,
                 "%.200s() takes no arguments (%" CYTHON_FORMAT_SSIZE_T "d given)",
                 f->m_ml->ml_name, size);
+#endif
             return NULL;
         }
         break;
     case METH_O:
         if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
+#if CYTHON_ASSUME_SAFE_MACROS
             size = PyTuple_GET_SIZE(arg);
+#else
+            size = PyTuple_Size(arg);
+            if (unlikely(size < 0)) return NULL;
+#endif
             if (likely(size == 1)) {
                 PyObject *result, *arg0;
                 #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
                 arg0 = PyTuple_GET_ITEM(arg, 0);
                 #else
-                arg0 = PySequence_ITEM(arg, 0); if (unlikely(!arg0)) return NULL;
+                arg0 = __Pyx_PySequence_ITEM(arg, 0); if (unlikely(!arg0)) return NULL;
                 #endif
                 result = (*meth)(self, arg0);
                 #if !(CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS)
@@ -11262,9 +11883,18 @@ static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, Py
                 #endif
                 return result;
             }
+#if CYTHON_COMPILING_IN_LIMITED_API
+            py_name = __Pyx_CyFunction_get_name((__pyx_CyFunctionObject*)func, NULL);
+            if (!py_name) return NULL;
+            PyErr_Format(PyExc_TypeError,
+                "%.200S() takes exactly one argument (%" CYTHON_FORMAT_SSIZE_T "d given)",
+                py_name, size);
+            Py_DECREF(py_name);
+#else
             PyErr_Format(PyExc_TypeError,
                 "%.200s() takes exactly one argument (%" CYTHON_FORMAT_SSIZE_T "d given)",
                 f->m_ml->ml_name, size);
+#endif
             return NULL;
         }
         break;
@@ -11272,12 +11902,28 @@ static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, Py
         PyErr_SetString(PyExc_SystemError, "Bad call flags for CyFunction");
         return NULL;
     }
+#if CYTHON_COMPILING_IN_LIMITED_API
+    py_name = __Pyx_CyFunction_get_name((__pyx_CyFunctionObject*)func, NULL);
+    if (!py_name) return NULL;
+    PyErr_Format(PyExc_TypeError, "%.200S() takes no keyword arguments",
+                 py_name);
+    Py_DECREF(py_name);
+#else
     PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
                  f->m_ml->ml_name);
+#endif
     return NULL;
 }
 static CYTHON_INLINE PyObject *__Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
-    return __Pyx_CyFunction_CallMethod(func, ((PyCFunctionObject*)func)->m_self, arg, kw);
+    PyObject *self, *result;
+#if CYTHON_COMPILING_IN_LIMITED_API
+    self = PyCFunction_GetSelf(((__pyx_CyFunctionObject*)func)->func);
+    if (unlikely(!self) && PyErr_Occurred()) return NULL;
+#else
+    self = ((PyCFunctionObject*)func)->m_self;
+#endif
+    result = __Pyx_CyFunction_CallMethod(func, self, arg, kw);
+    return result;
 }
 static PyObject *__Pyx_CyFunction_CallAsMethod(PyObject *func, PyObject *args, PyObject *kw) {
     PyObject *result;
@@ -11297,7 +11943,12 @@ static PyObject *__Pyx_CyFunction_CallAsMethod(PyObject *func, PyObject *args, P
         Py_ssize_t argc;
         PyObject *new_args;
         PyObject *self;
+#if CYTHON_ASSUME_SAFE_MACROS
         argc = PyTuple_GET_SIZE(args);
+#else
+        argc = PyTuple_Size(args);
+        if (unlikely(!argc) < 0) return NULL;
+#endif
         new_args = PyTuple_GetSlice(args, 1, argc);
         if (unlikely(!new_args))
             return NULL;
@@ -11422,7 +12073,7 @@ static PyObject * __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS(PyObject *func,
     default:
         return NULL;
     }
-    return ((_PyCFunctionFastWithKeywords)(void(*)(void))def->ml_meth)(self, args, nargs, kwnames);
+    return ((__Pyx_PyCFunctionFastWithKeywords)(void(*)(void))def->ml_meth)(self, args, nargs, kwnames);
 }
 static PyObject * __Pyx_CyFunction_Vectorcall_FASTCALL_KEYWORDS_METHOD(PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames)
 {
@@ -11510,7 +12161,7 @@ static PyTypeObject __pyx_CyFunctionType_type = {
 #ifdef Py_TPFLAGS_METHOD_DESCRIPTOR
     Py_TPFLAGS_METHOD_DESCRIPTOR |
 #endif
-#ifdef _Py_TPFLAGS_HAVE_VECTORCALL
+#if defined(_Py_TPFLAGS_HAVE_VECTORCALL) && CYTHON_METH_FASTCALL
     _Py_TPFLAGS_HAVE_VECTORCALL |
 #endif
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
@@ -11742,20 +12393,93 @@ static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object) {
 #include "compile.h"
 #include "frameobject.h"
 #include "traceback.h"
-#if PY_VERSION_HEX >= 0x030b00a6
+#if PY_VERSION_HEX >= 0x030b00a6 && !CYTHON_COMPILING_IN_LIMITED_API
   #ifndef Py_BUILD_CORE
     #define Py_BUILD_CORE 1
   #endif
   #include "internal/pycore_frame.h"
 #endif
 #if CYTHON_COMPILING_IN_LIMITED_API
+static PyObject *__Pyx_PyCode_Replace_For_AddTraceback(PyObject *code, PyObject *scratch_dict,
+                                                       PyObject *firstlineno, PyObject *name) {
+    PyObject *replace = NULL;
+    if (unlikely(PyDict_SetItemString(scratch_dict, "co_firstlineno", firstlineno))) return NULL;
+    if (unlikely(PyDict_SetItemString(scratch_dict, "co_name", name))) return NULL;
+    replace = PyObject_GetAttrString(code, "replace");
+    if (likely(replace)) {
+        PyObject *result;
+        result = PyObject_Call(replace, __pyx_empty_tuple, scratch_dict);
+        Py_DECREF(replace);
+        return result;
+    }
+    PyErr_Clear();
+    #if __PYX_LIMITED_VERSION_HEX < 0x030780000
+    {
+        PyObject *compiled = NULL, *result = NULL;
+        if (unlikely(PyDict_SetItemString(scratch_dict, "code", code))) return NULL;
+        if (unlikely(PyDict_SetItemString(scratch_dict, "type", (PyObject*)(&PyType_Type)))) return NULL;
+        compiled = Py_CompileString(
+            "out = type(code)(\n"
+            "  code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize,\n"
+            "  code.co_flags, code.co_code, code.co_consts, code.co_names,\n"
+            "  code.co_varnames, code.co_filename, co_name, co_firstlineno,\n"
+            "  code.co_lnotab)\n", "<dummy>", Py_file_input);
+        if (!compiled) return NULL;
+        result = PyEval_EvalCode(compiled, scratch_dict, scratch_dict);
+        Py_DECREF(compiled);
+        if (!result) PyErr_Print();
+        Py_DECREF(result);
+        result = PyDict_GetItemString(scratch_dict, "out");
+        if (result) Py_INCREF(result);
+        return result;
+    }
+    #else
+    return NULL;
+    #endif
+}
 static void __Pyx_AddTraceback(const char *funcname, int c_line,
                                int py_line, const char *filename) {
+    PyObject *code_object = NULL, *py_py_line = NULL, *py_funcname = NULL, *dict = NULL;
+    PyObject *replace = NULL, *getframe = NULL, *frame = NULL;
+    PyObject *exc_type, *exc_value, *exc_traceback;
+    int success = 0;
     if (c_line) {
         (void) __pyx_cfilenm;
         (void) __Pyx_CLineForTraceback(__Pyx_PyThreadState_Current, c_line);
     }
-    _PyTraceback_Add(funcname, filename, py_line);
+    PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
+    code_object = Py_CompileString("_getframe()", filename, Py_eval_input);
+    if (unlikely(!code_object)) goto bad;
+    py_py_line = PyLong_FromLong(py_line);
+    if (unlikely(!py_py_line)) goto bad;
+    py_funcname = PyUnicode_FromString(funcname);
+    if (unlikely(!py_funcname)) goto bad;
+    dict = PyDict_New();
+    if (unlikely(!dict)) goto bad;
+    {
+        PyObject *old_code_object = code_object;
+        code_object = __Pyx_PyCode_Replace_For_AddTraceback(code_object, dict, py_py_line, py_funcname);
+        Py_DECREF(old_code_object);
+    }
+    if (unlikely(!code_object)) goto bad;
+    getframe = PySys_GetObject("_getframe");
+    if (unlikely(!getframe)) goto bad;
+    if (unlikely(PyDict_SetItemString(dict, "_getframe", getframe))) goto bad;
+    frame = PyEval_EvalCode(code_object, dict, dict);
+    if (unlikely(!frame) || frame == Py_None) goto bad;
+    success = 1;
+  bad:
+    PyErr_Restore(exc_type, exc_value, exc_traceback);
+    Py_XDECREF(code_object);
+    Py_XDECREF(py_py_line);
+    Py_XDECREF(py_funcname);
+    Py_XDECREF(dict);
+    Py_XDECREF(replace);
+    if (success) {
+        PyTraceBack_Here(
+            (struct _frame*)frame);
+    }
+    Py_XDECREF(frame);
 }
 #else
 static PyCodeObject* __Pyx_CreateCodeObjectForTraceback(
@@ -11808,7 +12532,7 @@ static PyCodeObject* __Pyx_CreateCodeObjectForTraceback(
     #else
     py_code = PyCode_NewEmpty(filename, funcname, py_line);
     #endif
-    Py_XDECREF(py_funcname);  // XDECREF since it's only set on Py3 if cline
+    Py_XDECREF(py_funcname);
     return py_code;
 bad:
     Py_XDECREF(py_funcname);
@@ -12357,7 +13081,7 @@ static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
 #endif
             if (likely(v)) {
                 int ret = -1;
-#if !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
+#if PY_VERSION_HEX < 0x030d0000 && !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
                 int one = 1; int is_little = (int)*(unsigned char *)&one;
                 unsigned char *bytes = (unsigned char *)&val;
                 ret = _PyLong_AsByteArray((PyLongObject *)v,
@@ -12493,8 +13217,34 @@ static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) {
     {
         int one = 1; int little = (int)*(unsigned char *)&one;
         unsigned char *bytes = (unsigned char *)&value;
+#if !CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX < 0x030d0000
         return _PyLong_FromByteArray(bytes, sizeof(int),
                                      little, !is_unsigned);
+#else
+        PyObject *from_bytes, *result = NULL;
+        PyObject *py_bytes = NULL, *arg_tuple = NULL, *kwds = NULL, *order_str = NULL;
+        from_bytes = PyObject_GetAttrString((PyObject*)&PyLong_Type, "from_bytes");
+        if (!from_bytes) return NULL;
+        py_bytes = PyBytes_FromStringAndSize((char*)bytes, sizeof(int));
+        if (!py_bytes) goto limited_bad;
+        order_str = PyUnicode_FromString(little ? "little" : "big");
+        if (!order_str) goto limited_bad;
+        arg_tuple = PyTuple_Pack(2, py_bytes, order_str);
+        if (!arg_tuple) goto limited_bad;
+        if (!is_unsigned) {
+            kwds = PyDict_New();
+            if (!kwds) goto limited_bad;
+            if (PyDict_SetItemString(kwds, "signed", __Pyx_NewRef(Py_True))) goto limited_bad;
+        }
+        result = PyObject_Call(from_bytes, arg_tuple, kwds);
+        limited_bad:
+        Py_XDECREF(kwds);
+        Py_XDECREF(arg_tuple);
+        Py_XDECREF(order_str);
+        Py_XDECREF(py_bytes);
+        Py_XDECREF(from_bytes);
+        return result;
+#endif
     }
 }
 
@@ -12668,7 +13418,7 @@ static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *x) {
 #endif
             if (likely(v)) {
                 int ret = -1;
-#if !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
+#if PY_VERSION_HEX < 0x030d0000 && !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
                 int one = 1; int is_little = (int)*(unsigned char *)&one;
                 unsigned char *bytes = (unsigned char *)&val;
                 ret = _PyLong_AsByteArray((PyLongObject *)v,
@@ -12804,8 +13554,34 @@ static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
     {
         int one = 1; int little = (int)*(unsigned char *)&one;
         unsigned char *bytes = (unsigned char *)&value;
+#if !CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX < 0x030d0000
         return _PyLong_FromByteArray(bytes, sizeof(long),
                                      little, !is_unsigned);
+#else
+        PyObject *from_bytes, *result = NULL;
+        PyObject *py_bytes = NULL, *arg_tuple = NULL, *kwds = NULL, *order_str = NULL;
+        from_bytes = PyObject_GetAttrString((PyObject*)&PyLong_Type, "from_bytes");
+        if (!from_bytes) return NULL;
+        py_bytes = PyBytes_FromStringAndSize((char*)bytes, sizeof(long));
+        if (!py_bytes) goto limited_bad;
+        order_str = PyUnicode_FromString(little ? "little" : "big");
+        if (!order_str) goto limited_bad;
+        arg_tuple = PyTuple_Pack(2, py_bytes, order_str);
+        if (!arg_tuple) goto limited_bad;
+        if (!is_unsigned) {
+            kwds = PyDict_New();
+            if (!kwds) goto limited_bad;
+            if (PyDict_SetItemString(kwds, "signed", __Pyx_NewRef(Py_True))) goto limited_bad;
+        }
+        result = PyObject_Call(from_bytes, arg_tuple, kwds);
+        limited_bad:
+        Py_XDECREF(kwds);
+        Py_XDECREF(arg_tuple);
+        Py_XDECREF(order_str);
+        Py_XDECREF(py_bytes);
+        Py_XDECREF(from_bytes);
+        return result;
+#endif
     }
 }
 
@@ -12818,7 +13594,8 @@ __Pyx_PyType_GetName(PyTypeObject* tp)
                                                __pyx_n_s_name);
     if (unlikely(name == NULL) || unlikely(!PyUnicode_Check(name))) {
         PyErr_Clear();
-        Py_XSETREF(name, __Pyx_NewRef(__pyx_n_s__13));
+        Py_XDECREF(name);
+        name = __Pyx_NewRef(__pyx_n_s__13);
     }
     return name;
 }
@@ -12994,7 +13771,7 @@ static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {
 #endif
             if (likely(v)) {
                 int ret = -1;
-#if !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
+#if PY_VERSION_HEX < 0x030d0000 && !(CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_LIMITED_API) || defined(_PyLong_AsByteArray)
                 int one = 1; int is_little = (int)*(unsigned char *)&one;
                 unsigned char *bytes = (unsigned char *)&val;
                 ret = _PyLong_AsByteArray((PyLongObject *)v,
@@ -13214,41 +13991,50 @@ static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches2(PyObject *err, PyObj
 #endif
 
 /* CheckBinaryVersion */
-static int __Pyx_check_binary_version(void) {
-    char ctversion[5];
-    int same=1, i, found_dot;
-    const char* rt_from_call = Py_GetVersion();
-    PyOS_snprintf(ctversion, 5, "%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
-    found_dot = 0;
-    for (i = 0; i < 4; i++) {
-        if (!ctversion[i]) {
-            same = (rt_from_call[i] < '0' || rt_from_call[i] > '9');
-            break;
+static unsigned long __Pyx_get_runtime_version(void) {
+#if __PYX_LIMITED_VERSION_HEX >= 0x030B00A4
+    return Py_Version & ~0xFFUL;
+#else
+    const char* rt_version = Py_GetVersion();
+    unsigned long version = 0;
+    unsigned long factor = 0x01000000UL;
+    unsigned int digit = 0;
+    int i = 0;
+    while (factor) {
+        while ('0' <= rt_version[i] && rt_version[i] <= '9') {
+            digit = digit * 10 + (unsigned int) (rt_version[i] - '0');
+            ++i;
         }
-        if (rt_from_call[i] != ctversion[i]) {
-            same = 0;
+        version += factor * digit;
+        if (rt_version[i] != '.')
             break;
-        }
+        digit = 0;
+        factor >>= 8;
+        ++i;
     }
-    if (!same) {
-        char rtversion[5] = {'\0'};
+    return version;
+#endif
+}
+static int __Pyx_check_binary_version(unsigned long ct_version, unsigned long rt_version, int allow_newer) {
+    const unsigned long MAJOR_MINOR = 0xFFFF0000UL;
+    if ((rt_version & MAJOR_MINOR) == (ct_version & MAJOR_MINOR))
+        return 0;
+    if (likely(allow_newer && (rt_version & MAJOR_MINOR) > (ct_version & MAJOR_MINOR)))
+        return 1;
+    {
         char message[200];
-        for (i=0; i<4; ++i) {
-            if (rt_from_call[i] == '.') {
-                if (found_dot) break;
-                found_dot = 1;
-            } else if (rt_from_call[i] < '0' || rt_from_call[i] > '9') {
-                break;
-            }
-            rtversion[i] = rt_from_call[i];
-        }
         PyOS_snprintf(message, sizeof(message),
-                      "compile time version %s of module '%.100s' "
-                      "does not match runtime version %s",
-                      ctversion, __Pyx_MODULE_NAME, rtversion);
+                      "compile time Python version %d.%d "
+                      "of module '%.100s' "
+                      "%s "
+                      "runtime version %d.%d",
+                       (int) (ct_version >> 24), (int) ((ct_version >> 16) & 0xFF),
+                       __Pyx_MODULE_NAME,
+                       (allow_newer) ? "was newer than" : "does not match",
+                       (int) (rt_version >> 24), (int) ((rt_version >> 16) & 0xFF)
+       );
         return PyErr_WarnEx(NULL, message, 1);
     }
-    return 0;
 }
 
 /* InitStrings */
@@ -13294,8 +14080,24 @@ static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
     return 0;
 }
 
+#include <string.h>
+static CYTHON_INLINE Py_ssize_t __Pyx_ssize_strlen(const char *s) {
+    size_t len = strlen(s);
+    if (unlikely(len > (size_t) PY_SSIZE_T_MAX)) {
+        PyErr_SetString(PyExc_OverflowError, "byte string is too long");
+        return -1;
+    }
+    return (Py_ssize_t) len;
+}
 static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char* c_str) {
-    return __Pyx_PyUnicode_FromStringAndSize(c_str, (Py_ssize_t)strlen(c_str));
+    Py_ssize_t len = __Pyx_ssize_strlen(c_str);
+    if (unlikely(len < 0)) return NULL;
+    return __Pyx_PyUnicode_FromStringAndSize(c_str, len);
+}
+static CYTHON_INLINE PyObject* __Pyx_PyByteArray_FromString(const char* c_str) {
+    Py_ssize_t len = __Pyx_ssize_strlen(c_str);
+    if (unlikely(len < 0)) return NULL;
+    return PyByteArray_FromStringAndSize(c_str, len);
 }
 static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject* o) {
     Py_ssize_t ignore;
diff --git a/CRISPResso2/CRISPRessoCOREResources.pyx b/CRISPResso2/CRISPRessoCOREResources.pyx
index 3eb98383..d64480fb 100644
--- a/CRISPResso2/CRISPRessoCOREResources.pyx
+++ b/CRISPResso2/CRISPRessoCOREResources.pyx
@@ -7,7 +7,6 @@ cdef extern from "stdlib.h":
     ctypedef unsigned int size_t
     size_t strlen(char* s)
 
-
 cdef extern from "Python.h":
     ctypedef void PyObject
     int _PyBytes_Resize(PyObject **, size_t)
@@ -37,6 +36,7 @@ def find_indels_substitutions(read_seq_al, ref_seq_al, _include_indx):
     substitution_values=[]
 
     all_deletion_positions = []
+    all_deletion_coordinates = []
     deletion_positions = []
     deletion_coordinates = []
     deletion_sizes = []
@@ -93,6 +93,7 @@ def find_indels_substitutions(read_seq_al, ref_seq_al, _include_indx):
         elif read_seq_al[idx_c] != '-' and start_deletion != -1:  # this is the end of a deletion
             end_deletion = ref_positions[idx_c]
             all_deletion_positions.extend(range(start_deletion, end_deletion))
+            all_deletion_coordinates.append((start_deletion, end_deletion))
             if include_indx_set.intersection(range(start_deletion, end_deletion)):
                 deletion_positions.extend(range(start_deletion, end_deletion))
                 deletion_coordinates.append((start_deletion, end_deletion))
@@ -102,6 +103,7 @@ def find_indels_substitutions(read_seq_al, ref_seq_al, _include_indx):
     if start_deletion != -1:
         end_deletion = ref_positions[seq_len - 1]
         all_deletion_positions.extend(range(start_deletion, end_deletion))
+        all_deletion_coordinates.append((start_deletion, end_deletion))
         if include_indx_set.intersection(range(start_deletion, end_deletion)):
             deletion_positions.extend(range(start_deletion, end_deletion))
             deletion_coordinates.append((start_deletion, end_deletion))
@@ -120,6 +122,7 @@ def find_indels_substitutions(read_seq_al, ref_seq_al, _include_indx):
         'insertion_n': insertion_n,
 
         'all_deletion_positions': all_deletion_positions,
+        'all_deletion_coordinates': all_deletion_coordinates,
         'deletion_positions': deletion_positions,
         'deletion_coordinates': deletion_coordinates,
         'deletion_sizes': deletion_sizes,
@@ -130,14 +133,13 @@ def find_indels_substitutions(read_seq_al, ref_seq_al, _include_indx):
         'all_substitution_values': np.array(all_substitution_values),
         'substitution_values': np.array(substitution_values),
         'substitution_n': substitution_n,
-
         'ref_positions': ref_positions,
     }
-
-
 @cython.boundscheck(False)
 @cython.nonecheck(False)
 @cython.wraparound(False)
+
+
 def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
 
     cdef char* sub_seq=''
@@ -162,7 +164,6 @@ def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
     substitution_positions=[]
     all_substitution_values=[]
     substitution_values=[]
-
     nucSet = set(['A', 'T', 'C', 'G', 'N'])
     idx=0
     for idx_c, c in enumerate(ref_seq_al):
@@ -189,6 +190,7 @@ def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
     all_deletion_positions=[]
     deletion_positions=[]
     deletion_coordinates=[]
+    all_deletion_coordinates=[]
     deletion_sizes=[]
 
     all_insertion_positions=[]
@@ -207,6 +209,7 @@ def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
         if en < len(ref_positions):
           ref_en = ref_positions[en]
         all_deletion_positions.extend(range(ref_st,ref_en))
+        all_deletion_coordinates.append((ref_st,ref_en))
         inc_del_pos = include_indx_set.intersection(range(ref_st,ref_en))
         if(len(inc_del_pos)>0):
           deletion_positions.extend(range(ref_st,ref_en))
@@ -236,28 +239,28 @@ def find_indels_substitutions_legacy(read_seq_al, ref_seq_al, _include_indx):
 
     insertion_n = np.sum(insertion_sizes)
 
-
     retDict = {
-	    'all_insertion_positions':all_insertion_positions,
-	    'all_insertion_left_positions':all_insertion_left_positions,
-	    'insertion_positions':insertion_positions,
-	    'insertion_coordinates':insertion_coordinates,
-	    'insertion_sizes':insertion_sizes,
-	    'insertion_n':insertion_n,
-
-	    'all_deletion_positions':all_deletion_positions,
-	    'deletion_positions':deletion_positions,
-	    'deletion_coordinates':deletion_coordinates,
-	    'deletion_sizes':deletion_sizes,
-	    'deletion_n':deletion_n,
-
-	    'all_substitution_positions':all_substitution_positions,
-	    'substitution_positions':substitution_positions,
-	    'all_substitution_values':np.array(all_substitution_values),
-	    'substitution_values':np.array(substitution_values),
-	    'substitution_n':substitution_n,
-
-	    'ref_positions':ref_positions,
+        'all_insertion_positions':all_insertion_positions,
+        'all_insertion_left_positions':all_insertion_left_positions,
+        'insertion_positions':insertion_positions,
+        'insertion_coordinates':insertion_coordinates,
+        'insertion_sizes':insertion_sizes,
+        'insertion_n':insertion_n,
+        'all_deletion_positions':all_deletion_positions,
+
+        'deletion_positions':deletion_positions,
+        'deletion_coordinates':deletion_coordinates,
+        'all_deletion_coordinates':all_deletion_coordinates,
+        'deletion_sizes':deletion_sizes,
+        'deletion_n':deletion_n,
+
+        'all_substitution_positions':all_substitution_positions,
+        'substitution_positions':substitution_positions,
+        'all_substitution_values':np.array(all_substitution_values),
+        'substitution_values':np.array(substitution_values),
+        'substitution_n':substitution_n,
+
+        'ref_positions':ref_positions,
     }
     return retDict
 
diff --git a/CRISPResso2/CRISPRessoCompareCORE.py b/CRISPResso2/CRISPRessoCompareCORE.py
index 64c77c53..7d797024 100644
--- a/CRISPResso2/CRISPRessoCompareCORE.py
+++ b/CRISPResso2/CRISPRessoCompareCORE.py
@@ -8,10 +8,8 @@
 from copy import deepcopy
 import sys
 import traceback
-import argparse
 from CRISPResso2 import CRISPRessoShared
-from CRISPResso2 import CRISPRessoPlot
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 
 import logging
 
@@ -33,13 +31,6 @@ def check_library(library_name):
                 sys.exit(1)
 
 
-def get_amplicon_output(amplicon_name, output_folder):
-    profile_file=os.path.join(output_folder, amplicon_name+'.effect_vector_combined.txt')
-    if os.path.exists(quantification_file) and profile_file:
-        return quantification_file, profile_file
-    else:
-        raise CRISPRessoShared.OutputFolderIncompleteException('The folder %s is not a valid CRISPResso2 output folder. Cannot find profile file %s for amplicon %s.' % (output_folder, profile_file, amplicon_name))
-
 def parse_profile(profile_file):
     return np.loadtxt(profile_file, skiprows=1)
 
@@ -72,6 +63,35 @@ def normalize_name(name, output_folder_1, output_folder_2):
         return name
 
 
+def get_matching_allele_files(run_info_1, run_info_2):
+    def get_amplicon_info(run_info):
+        return {
+            amplicon['sequence']: {
+                'name': amplicon_name,
+                'guides': amplicon['sgRNA_orig_sequences'],
+                'cut_points': amplicon['sgRNA_cut_points'],
+                'allele_files': amplicon['allele_frequency_files'],
+            }
+            for amplicon_name, amplicon in run_info['results']['refs'].items()
+        }
+    amplicons_1 = get_amplicon_info(run_info_1)
+    amplicons_2 = get_amplicon_info(run_info_2)
+    matching_allele_files = []
+    for sequence_1 in amplicons_1:
+        if sequence_1 in amplicons_2:
+            if amplicons_1[sequence_1]['cut_points'] != amplicons_2[sequence_1]['cut_points']:
+                warn(f'Report 1 has different cut points than report 2 for amplicon {amplicons_1[sequence_1]["name"]}, skipping comparison')
+                continue
+            guides_1 = set(amplicons_1[sequence_1]['guides'])
+            guides_2 = set(amplicons_2[sequence_1]['guides'])
+            if not guides_1 & guides_2:
+                warn(f'Report 1 has no shared guides with report 2 for amplicon {amplicons_1[sequence_1]["name"]}, skipping comparison')
+                continue
+            matching_allele_files.extend((f_1, f_2) for f_1, f_2 in zip(amplicons_1[sequence_1]['allele_files'], amplicons_2[sequence_1]['allele_files']))
+
+    return matching_allele_files
+
+
 def main():
     try:
         description = ['~~~CRISPRessoCompare~~~', '-Comparison of two CRISPResso analyses-']
@@ -85,26 +105,15 @@ def main():
         compare_header = CRISPRessoShared.get_crispresso_header(description, compare_header)
         print(compare_header)
 
-        parser = argparse.ArgumentParser(description='CRISPRessoCompare Parameters', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-        parser.add_argument('crispresso_output_folder_1', type=str,  help='First output folder with CRISPResso analysis')
-        parser.add_argument('crispresso_output_folder_2', type=str,  help='Second output folder with CRISPResso analysis')
-
-        #OPTIONALS
-        parser.add_argument('-n', '--name',  help='Output name', default='')
-        parser.add_argument('-n1', '--sample_1_name',  help='Sample 1 name')
-        parser.add_argument('-n2', '--sample_2_name',  help='Sample 2 name')
-        parser.add_argument('-o', '--output_folder',  help='', default='')
-        parser.add_argument('--reported_qvalue_cutoff', help='Q-value cutoff for signifance in tests for differential editing. Each base position is tested (for insertions, deletions, substitutions, and all modifications) using Fisher\'s exact test, followed by Bonferonni correction. The number of bases with a significance below this threshold in the quantification window are counted and reported in the output summary.', type=float, default=0.05)
-        parser.add_argument('--min_frequency_alleles_around_cut_to_plot', type=float, help='Minimum %% reads required to report an allele in the alleles table plot.', default=0.2)
-        parser.add_argument('--max_rows_alleles_around_cut_to_plot',  type=int, help='Maximum number of rows to report in the alleles table plot. ', default=50)
-        parser.add_argument('--suppress_report',  help='Suppress output report', action='store_true')
-        parser.add_argument('--place_report_in_output_folder',  help='If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output.', action='store_true')
-        parser.add_argument('--zip_output', help="If set, the output will be placed in a zip folder.", action='store_true')
-        parser.add_argument('--debug', help='Show debug messages', action='store_true')
-        parser.add_argument('-v', '--verbosity', type=int, help='Verbosity level of output to the console (1-4)', default=3)
+        parser = CRISPRessoShared.getCRISPRessoArgParser("Compare", parser_title = 'CRISPRessoCompare Parameters')
 
         args = parser.parse_args()
 
+        if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
 
         debug_flag = args.debug
@@ -154,7 +163,7 @@ def main():
 
         log_filename = _jp('CRISPRessoCompare_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoCompare_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoCompare_status.json')))
 
         with open(log_filename, 'w+') as outfile:
             outfile.write('[Command used]:\nCRISPRessoCompare %s\n\n[Execution log]:\n' % ' '.join(sys.argv))
@@ -186,7 +195,8 @@ def get_plot_title_with_ref_name(plotTitle, refName):
         sig_counts = {}  # number of bp significantly modified (bonferonni corrected fisher pvalue)
         sig_counts_quant_window = {}
         percent_complete_start, percent_complete_end = 10, 90
-        percent_complete_step = (percent_complete_end - percent_complete_start) / len(amplicon_names_in_both)
+        if amplicon_names_in_both:
+            percent_complete_step = (percent_complete_end - percent_complete_start) / len(amplicon_names_in_both)
         for amplicon_name in amplicon_names_in_both:
             percent_complete = percent_complete_start + percent_complete_step * amplicon_names_in_both.index(amplicon_name)
             info('Loading data for amplicon %s' % amplicon_name, {'percent_complete': percent_complete})
@@ -250,7 +260,7 @@ def get_plot_title_with_ref_name(plotTitle, refName):
             crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'Editing efficiency comparison'
             crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Figure 1: Comparison for amplicon ' + amplicon_name + '; Left: Percentage of modified and unmodified reads in each sample; Right: relative percentage of modified and unmodified reads'
             output_1 = os.path.join(args.crispresso_output_folder_1, run_info_1['running_info']['report_filename'])
-            output_2 = os.path.join(args.crispresso_output_folder_1, run_info_2['running_info']['report_filename'])
+            output_2 = os.path.join(args.crispresso_output_folder_2, run_info_2['running_info']['report_filename'])
             crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = []
             if os.path.isfile(output_1):
                 crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name].append((sample_1_name +' output', os.path.relpath(output_1, OUTPUT_DIRECTORY)))
@@ -362,61 +372,56 @@ def get_plot_title_with_ref_name(plotTitle, refName):
 
 
             #create merged heatmaps for each cut site
-            allele_files_1 = amplicon_info_1[amplicon_name]['allele_files']
-            allele_files_2 = amplicon_info_2[amplicon_name]['allele_files']
-            for allele_file_1 in allele_files_1:
-                allele_file_1_name = os.path.split(allele_file_1)[1] #get file part of path
-                for allele_file_2 in allele_files_2:
-                    allele_file_2_name = os.path.split(allele_file_2)[1] #get file part of path
-                    #if files are the same (same amplicon, cut site, guide), run comparison
-                    if allele_file_1_name == allele_file_2_name:
-                        df1 = pd.read_csv(allele_file_1, sep="\t")
-                        df2 = pd.read_csv(allele_file_2, sep="\t")
-
-                        #find unmodified reference for comparison (if it exists)
-                        ref_seq_around_cut = ""
-                        if len(df1.loc[df1['Reference_Sequence'].str.contains('-')==False]) > 0:
-                            ref_seq_around_cut = df1.loc[df1['Reference_Sequence'].str.contains('-')==False]['Reference_Sequence'].iloc[0]
-                        #otherwise figure out which sgRNA was used for this comparison
-                        elif len(df2.loc[df2['Reference_Sequence'].str.contains('-')==False]) > 0:
-                            ref_seq_around_cut = df2.loc[df2['Reference_Sequence'].str.contains('-')==False]['Reference_Sequence'].iloc[0]
-                        else:
-                            seq_len = df2[df2['Unedited']==True]['Reference_Sequence'].iloc[0]
-                            for sgRNA_interval, cut_point in zip(sgRNA_intervals, cut_points):
-                                sgRNA_seq = consensus_sequence[sgRNA_interval[0]:sgRNA_interval[1]]
-                                if sgRNA_seq in allele_file_1_name:
-                                    this_sgRNA_seq = sgRNA_seq
-                                    this_cut_point = cut_point
-                                    ref_seq_around_cut=consensus_sequence[max(0, this_cut_point-args.offset_around_cut_to_plot+1):min(seq_len, cut_point+args.offset_around_cut_to_plot+1)]
-                                    break
-
-                        merged = pd.merge(df1, df2, on = ['Aligned_Sequence', 'Reference_Sequence', 'Unedited', 'n_deleted', 'n_inserted', 'n_mutated'], suffixes=('_' + sample_1_name, '_'+sample_2_name), how='outer')
-                        quant_cols = ['#Reads_'+sample_1_name, '%Reads_'+sample_1_name, '#Reads_'+sample_2_name, '%Reads_'+sample_2_name]
-                        merged[quant_cols] = merged[quant_cols].fillna(0)
-                        lfc_error =0.1
-                        merged['each_LFC'] = np.log2(((merged['%Reads_'+sample_1_name]+lfc_error)/(merged['%Reads_'+sample_2_name]+lfc_error)).astype(float)).replace([np.inf, np.NaN], 0)
-                        merged = merged.reset_index().set_index('Aligned_Sequence')
-                        output_root = allele_file_1_name.replace(".txt", "")
-                        allele_comparison_file = _jp(output_root+'.txt')
-                        merged.to_csv(allele_comparison_file, sep="\t", index=None)
-
-                        plot_name = '3.'+output_root+'_top'
-                        CRISPRessoPlot.plot_alleles_table_compare(ref_seq_around_cut, merged.sort_values(['each_LFC'], ascending=True), sample_1_name, sample_2_name, _jp(plot_name),
-                                    MIN_FREQUENCY=args.min_frequency_alleles_around_cut_to_plot, MAX_N_ROWS=args.max_rows_alleles_around_cut_to_plot, SAVE_ALSO_PNG=save_png)
-                        crispresso2_info['results']['general_plots']['summary_plot_names'].append(plot_name)
-                        crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'Alleles enriched in ' + sample_1_name
-                        crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Distribution comparison of alleles. Nucleotides are indicated by unique colors (A = green; C = red; G = yellow; T = purple). Substitutions are shown in bold font. Red rectangles highlight inserted sequences. Horizontal dashed lines indicate deleted sequences. The vertical dashed line indicates the predicted cleavage site. '+ \
-                        'The proportion and number of reads is shown for each sample on the right, with the values for ' + sample_1_name + ' followed by the values for ' + sample_2_name +'. Alleles are sorted for enrichment in ' + sample_1_name+'.'
-                        crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Allele comparison table', os.path.basename(allele_comparison_file))]
-
-                        plot_name = '3.'+output_root+'_bottom'
-                        CRISPRessoPlot.plot_alleles_table_compare(ref_seq_around_cut, merged.sort_values(['each_LFC'], ascending=False), sample_1_name, sample_2_name, _jp(plot_name),
-                                    MIN_FREQUENCY=args.min_frequency_alleles_around_cut_to_plot, MAX_N_ROWS=args.max_rows_alleles_around_cut_to_plot, SAVE_ALSO_PNG=save_png)
-                        crispresso2_info['results']['general_plots']['summary_plot_names'].append(plot_name)
-                        crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'Alleles enriched in ' + sample_2_name
-                        crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Distribution comparison of alleles. Nucleotides are indicated by unique colors (A = green; C = red; G = yellow; T = purple). Substitutions are shown in bold font. Red rectangles highlight inserted sequences. Horizontal dashed lines indicate deleted sequences. The vertical dashed line indicates the predicted cleavage site. '+ \
-                        'The proportion and number of reads is shown for each sample on the right, with the values for ' + sample_1_name + ' followed by the values for ' + sample_2_name +'. Alleles are sorted for enrichment in ' + sample_2_name+'.'
-                        crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Allele comparison table', os.path.basename(allele_comparison_file))]
+            matching_allele_files = get_matching_allele_files(run_info_1, run_info_2)
+            for allele_file_1, allele_file_2 in matching_allele_files:
+                df1 = pd.read_csv(os.path.join(args.crispresso_output_folder_1, allele_file_1), sep="\t")
+                df2 = pd.read_csv(os.path.join(args.crispresso_output_folder_2, allele_file_2), sep="\t")
+
+                #find unmodified reference for comparison (if it exists)
+                ref_seq_around_cut = ""
+                if len(df1.loc[df1['Reference_Sequence'].str.contains('-')==False]) > 0:
+                    ref_seq_around_cut = df1.loc[df1['Reference_Sequence'].str.contains('-')==False]['Reference_Sequence'].iloc[0]
+                #otherwise figure out which sgRNA was used for this comparison
+                elif len(df2.loc[df2['Reference_Sequence'].str.contains('-')==False]) > 0:
+                    ref_seq_around_cut = df2.loc[df2['Reference_Sequence'].str.contains('-')==False]['Reference_Sequence'].iloc[0]
+                else:
+                    seq_len = df2[df2['Unedited']==True]['Reference_Sequence'].iloc[0]
+                    for sgRNA_interval, cut_point in zip(sgRNA_intervals, cut_points):
+                        sgRNA_seq = consensus_sequence[sgRNA_interval[0]:sgRNA_interval[1]]
+                        if sgRNA_seq in allele_file_1:
+                            this_sgRNA_seq = sgRNA_seq
+                            this_cut_point = cut_point
+                            ref_seq_around_cut=consensus_sequence[max(0, this_cut_point-args.offset_around_cut_to_plot+1):min(seq_len, cut_point+args.offset_around_cut_to_plot+1)]
+                            break
+
+                merged = pd.merge(df1, df2, on = ['Aligned_Sequence', 'Reference_Sequence', 'Unedited', 'n_deleted', 'n_inserted', 'n_mutated'], suffixes=('_' + sample_1_name, '_'+sample_2_name), how='outer')
+                quant_cols = ['#Reads_'+sample_1_name, '%Reads_'+sample_1_name, '#Reads_'+sample_2_name, '%Reads_'+sample_2_name]
+                merged[quant_cols] = merged[quant_cols].fillna(0)
+                lfc_error =0.1
+                merged['each_LFC'] = np.log2(((merged['%Reads_'+sample_1_name]+lfc_error)/(merged['%Reads_'+sample_2_name]+lfc_error)).astype(float)).replace([np.inf, np.NaN], 0)
+                merged = merged.sort_values(['%Reads_'+sample_1_name, 'Reference_Sequence', 'n_deleted', 'n_inserted', 'n_mutated'], ascending=False)
+                merged = merged.reset_index(drop=True).set_index('Aligned_Sequence')
+                args.crispresso_output_folder_root = os.path.split(allele_file_1)[1].replace(".txt", "")
+                allele_comparison_file = _jp(args.crispresso_output_folder_root+'.txt')
+                merged.to_csv(allele_comparison_file, sep="\t", index=None)
+
+                plot_name = '3.'+args.crispresso_output_folder_root+'_top'
+                CRISPRessoPlot.plot_alleles_table_compare(ref_seq_around_cut, merged.sort_values(['each_LFC'], ascending=True), sample_1_name, sample_2_name, _jp(plot_name),
+                            MIN_FREQUENCY=args.min_frequency_alleles_around_cut_to_plot, MAX_N_ROWS=args.max_rows_alleles_around_cut_to_plot, SAVE_ALSO_PNG=save_png)
+                crispresso2_info['results']['general_plots']['summary_plot_names'].append(plot_name)
+                crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'Alleles enriched in ' + sample_1_name
+                crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Distribution comparison of alleles. Nucleotides are indicated by unique colors (A = green; C = red; G = yellow; T = purple). Substitutions are shown in bold font. Red rectangles highlight inserted sequences. Horizontal dashed lines indicate deleted sequences. The vertical dashed line indicates the predicted cleavage site. '+ \
+                'The proportion and number of reads is shown for each sample on the right, with the values for ' + sample_1_name + ' followed by the values for ' + sample_2_name +'. Alleles are sorted for enrichment in ' + sample_1_name+'.'
+                crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Allele comparison table', os.path.basename(allele_comparison_file))]
+
+                plot_name = '3.'+args.crispresso_output_folder_root+'_bottom'
+                CRISPRessoPlot.plot_alleles_table_compare(ref_seq_around_cut, merged.sort_values(['each_LFC'], ascending=False), sample_1_name, sample_2_name, _jp(plot_name),
+                            MIN_FREQUENCY=args.min_frequency_alleles_around_cut_to_plot, MAX_N_ROWS=args.max_rows_alleles_around_cut_to_plot, SAVE_ALSO_PNG=save_png)
+                crispresso2_info['results']['general_plots']['summary_plot_names'].append(plot_name)
+                crispresso2_info['results']['general_plots']['summary_plot_titles'][plot_name] = 'Alleles enriched in ' + sample_2_name
+                crispresso2_info['results']['general_plots']['summary_plot_labels'][plot_name] = 'Distribution comparison of alleles. Nucleotides are indicated by unique colors (A = green; C = red; G = yellow; T = purple). Substitutions are shown in bold font. Red rectangles highlight inserted sequences. Horizontal dashed lines indicate deleted sequences. The vertical dashed line indicates the predicted cleavage site. '+ \
+                'The proportion and number of reads is shown for each sample on the right, with the values for ' + sample_1_name + ' followed by the values for ' + sample_2_name +'. Alleles are sorted for enrichment in ' + sample_2_name+'.'
+                crispresso2_info['results']['general_plots']['summary_plot_datas'][plot_name] = [('Allele comparison table', os.path.basename(allele_comparison_file))]
 
         debug('Calculating significant base counts...', {'percent_complete': 95})
         sig_counts_filename = _jp('CRISPRessoCompare_significant_base_counts.txt')
@@ -440,7 +445,7 @@ def get_plot_title_with_ref_name(plotTitle, refName):
                 report_name = _jp("CRISPResso2Compare_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_compare_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_compare_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
diff --git a/CRISPResso2/CRISPRessoMetaCORE.py b/CRISPResso2/CRISPRessoMetaCORE.py
index 87235ab6..4afd6d8d 100644
--- a/CRISPResso2/CRISPRessoMetaCORE.py
+++ b/CRISPResso2/CRISPRessoMetaCORE.py
@@ -13,9 +13,8 @@
 import traceback
 import json
 from CRISPResso2 import CRISPRessoShared
-from CRISPResso2 import CRISPRessoPlot
 from CRISPResso2 import CRISPRessoMultiProcessing
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 
 
 import logging
@@ -87,7 +86,7 @@ def main():
         '''
         print(CRISPRessoShared.get_crispresso_header(description, meta_string))
 
-        parser = CRISPRessoShared.getCRISPRessoArgParser(parser_title = 'CRISPRessoMeta Parameters')
+        parser = CRISPRessoShared.getCRISPRessoArgParser("Meta", parser_title = 'CRISPRessoMeta Parameters')
 
         #batch specific params
         parser.add_argument('--metadata', type=str, help='Metadata file according to NIST specification', required=True)
@@ -96,11 +95,16 @@ def main():
 
         args = parser.parse_args()
 
+        if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
 
         debug_flag = args.debug
 
-        crispresso_options = CRISPRessoShared.get_crispresso_options()
+        crispresso_options = CRISPRessoShared.get_core_crispresso_options()
         options_to_ignore = {'name', 'output_folder'}
         crispresso_options_for_meta = list(crispresso_options-options_to_ignore)
 
@@ -123,7 +127,7 @@ def main():
         print('table:')
         print(meta_params)
         #rename column "a" to "amplicon_seq", etc
-        meta_params.rename(index=str, columns=CRISPRessoShared.get_crispresso_options_lookup(), inplace=True)
+        meta_params.rename(index=str, columns=CRISPRessoShared.get_crispresso_options_lookup("Core"), inplace=True)
         meta_count = meta_params.shape[0]
         meta_params.index = range(meta_count)
 
@@ -196,7 +200,7 @@ def main():
                     if 'discard_guide_positions_overhanging_amplicon_edge' in row:
                         discard_guide_positions_overhanging_amplicon_edge = row.discard_guide_positions_overhanging_amplicon_edge
 
-                    (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_names, this_include_idxs,
+                    (this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_sgRNA_include_idxs, this_include_idxs,
                         this_exclude_idxs) = CRISPRessoShared.get_amplicon_info_for_guides(curr_amplicon_seq, guides, guide_mismatches, guide_names, guide_qw_centers,
                         guide_qw_sizes, row.quantification_window_coordinates, row.exclude_bp_from_left, row.exclude_bp_from_right, row.plot_window_size, guide_plot_cut_points, discard_guide_positions_overhanging_amplicon_edge)
                     for guide_seq in this_sgRNA_sequences:
@@ -229,7 +233,7 @@ def main():
 
         log_filename=_jp('CRISPRessoMeta_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoMeta_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoMeta_status.json')))
 
         with open(log_filename, 'w+') as outfile:
             outfile.write('[Command used]:\n%s\n\n[Execution log]:\n' % ' '.join(sys.argv))
@@ -354,7 +358,7 @@ def main():
                 report_name = _jp("CRISPResso2Meta_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_meta_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_meta_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
diff --git a/CRISPResso2/CRISPRessoMultiProcessing.py b/CRISPResso2/CRISPRessoMultiProcessing.py
index d9afb3d1..e0693b9f 100644
--- a/CRISPResso2/CRISPRessoMultiProcessing.py
+++ b/CRISPResso2/CRISPRessoMultiProcessing.py
@@ -14,6 +14,10 @@
 from inspect import getmodule, stack
 import numpy as np
 import pandas as pd
+import traceback
+
+from CRISPResso2.CRISPRessoShared import PlotException
+
 
 def get_max_processes():
     return mp.cpu_count()
@@ -28,17 +32,18 @@ def run_crispresso(crispresso_cmds, descriptor, idx):
     idx: index of the command to run
     """
     crispresso_cmd=crispresso_cmds[idx]
+    logger = logging.getLogger(getmodule(stack()[1][0]).__name__)
 
-    logging.info('Running CRISPResso on %s #%d/%d: %s' % (descriptor, idx, len(crispresso_cmds), crispresso_cmd))
+    logger.info('Running CRISPResso on %s #%d/%d: %s' % (descriptor, idx, len(crispresso_cmds), crispresso_cmd))
 
     return_value = sb.call(crispresso_cmd, shell=True)
 
     if return_value == 137:
-        logging.warn('CRISPResso was killed by your system (return value %d) on %s #%d: "%s"\nPlease reduce the number of processes (-p) and run again.'%(return_value, descriptor, idx, crispresso_cmd))
+        logger.warn('CRISPResso was killed by your system (return value %d) on %s #%d: "%s"\nPlease reduce the number of processes (-p) and run again.'%(return_value, descriptor, idx, crispresso_cmd))
     elif return_value != 0:
-        logging.warn('CRISPResso command failed (return value %d) on %s #%d: "%s"'%(return_value, descriptor, idx, crispresso_cmd))
+        logger.warn('CRISPResso command failed (return value %d) on %s #%d: "%s"'%(return_value, descriptor, idx, crispresso_cmd))
     else:
-        logging.info('Finished CRISPResso %s #%d' %(descriptor, idx))
+        logger.info('Finished CRISPResso %s #%d' %(descriptor, idx))
     return return_value
 
 
@@ -90,11 +95,12 @@ def run_crispresso_cmds(crispresso_cmds, n_processes="1", descriptor = 'region',
         int_n_processes = int(n_processes)
 
     logger.info("Running CRISPResso with %d processes" % int_n_processes)
-    pool = mp.Pool(processes=int_n_processes)
+    if int_n_processes > 1:
+        pool = mp.Pool(processes=int_n_processes)
+        pFunc = partial(run_crispresso, crispresso_cmds, descriptor)
+        p_wrapper = partial(wrapper, pFunc)
     idxs = range(len(crispresso_cmds))
     ret_vals = [None] * len(crispresso_cmds)
-    pFunc = partial(run_crispresso, crispresso_cmds, descriptor)
-    p_wrapper = partial(wrapper, pFunc)
     if start_end_percent is not None:
         percent_complete_increment = start_end_percent[1] - start_end_percent[0]
         percent_complete_step = percent_complete_increment / len(crispresso_cmds)
@@ -108,14 +114,24 @@ def run_crispresso_cmds(crispresso_cmds, n_processes="1", descriptor = 'region',
     signal.signal(signal.SIGINT, original_sigint_handler)
     try:
         completed = 0
-        for idx, res in pool.imap_unordered(p_wrapper, enumerate(idxs)):
-            ret_vals[idx] = res
-            completed += 1
-            percent_complete += percent_complete_step
-            logger.info(
-                "Completed {0}/{1} runs".format(completed, len(crispresso_cmds)),
-                {'percent_complete': percent_complete},
-            )
+        if int_n_processes == 1:
+            for idx, cmd in enumerate(crispresso_cmds):
+                ret_vals[idx] = run_crispresso(crispresso_cmds, descriptor, idx)
+                completed += 1
+                percent_complete += percent_complete_step
+                logger.info(
+                    "Completed {0}/{1} runs".format(completed, len(crispresso_cmds)),
+                    {'percent_complete': percent_complete},
+                )
+        else:
+            for idx, res in pool.imap_unordered(p_wrapper, enumerate(idxs)):
+                ret_vals[idx] = res
+                completed += 1
+                percent_complete += percent_complete_step
+                logger.info(
+                    "Completed {0}/{1} runs".format(completed, len(crispresso_cmds)),
+                    {'percent_complete': percent_complete},
+                )
         for idx, ret in enumerate(ret_vals):
             if ret == 137:
                 raise Exception('CRISPResso %s #%d was killed by your system. Please decrease the number of processes (-p) and run again.'%(descriptor, idx))
@@ -134,8 +150,10 @@ def run_crispresso_cmds(crispresso_cmds, n_processes="1", descriptor = 'region',
         if descriptor.endswith("ch") or descriptor.endswith("sh"):
             plural = descriptor+"es"
         logger.info("Finished all " + plural)
-        pool.close()
-    pool.join()
+        if int_n_processes > 1:
+            pool.close()
+    if int_n_processes > 1:
+        pool.join()
 
 def run_pandas_apply_parallel(input_df, input_function_chunk, n_processes=1):
     """
@@ -162,7 +180,10 @@ def input_function_chunk(df):
     #shuffle the dataset to avoid finishing all the ones on top while leaving the ones on the bottom unfinished
     n_splits = min(n_processes, len(input_df))
     df_split = np.array_split(input_df.sample(frac=1), n_splits)
-    pool = mp.Pool(processes = n_splits)
+    if n_processes > 1:
+        pool = mp.Pool(processes = n_splits)
+    else:
+        return input_function_chunk(input_df)
 
     #handle signals -- bug in python 2.7 (https://stackoverflow.com/questions/11312525/catch-ctrlc-sigint-and-exit-multiprocesses-gracefully-in-python)
     original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
@@ -192,40 +213,55 @@ def run_function_on_array_chunk_parallel(input_array, input_function, n_processe
     input_function: function to run on chunks of the array
         input_function should take in a smaller array of objects
     """
-    pool = mp.Pool(processes = n_processes)
-
-    #handle signals -- bug in python 2.7 (https://stackoverflow.com/questions/11312525/catch-ctrlc-sigint-and-exit-multiprocesses-gracefully-in-python)
-    original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
-    signal.signal(signal.SIGINT, original_sigint_handler)
-    try:
-        n = int(max(10, len(input_array)/n_processes)) #don't parallelize unless at least 10 tasks
-        input_chunks = [input_array[i * n:(i + 1) * n] for i in range((len(input_array) + n - 1) // n )]
-        r = pool.map_async(input_function, input_chunks)
-        results = r.get(60*60*60) # Without the timeout this blocking call ignores all signals.
-    except KeyboardInterrupt:
-        pool.terminate()
-        logging.warn('Caught SIGINT. Program Terminated')
-        raise Exception('CRISPResso2 Terminated')
-        exit (0)
-    except Exception as e:
-        print('CRISPResso2 failed')
-        raise e
+    if n_processes == 1:
+        try:
+            results = input_function(input_array)
+        except Exception as e:
+            print('CRISPResso2 failed')
+            raise e
+        return results
     else:
-        pool.close()
-    pool.join()
-    return [y for x in results for y in x]
+        pool = mp.Pool(processes = n_processes)
+
+        #handle signals -- bug in python 2.7 (https://stackoverflow.com/questions/11312525/catch-ctrlc-sigint-and-exit-multiprocesses-gracefully-in-python)
+        original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+        signal.signal(signal.SIGINT, original_sigint_handler)
+        try:
+            n = int(max(10, len(input_array)/n_processes)) #don't parallelize unless at least 10 tasks
+            input_chunks = [input_array[i * n:(i + 1) * n] for i in range((len(input_array) + n - 1) // n )]
+            r = pool.map_async(input_function, input_chunks)
+            results = r.get(60*60*60) # Without the timeout this blocking call ignores all signals.
+        except KeyboardInterrupt:
+            pool.terminate()
+            logging.warn('Caught SIGINT. Program Terminated')
+            raise Exception('CRISPResso2 Terminated')
+            exit (0)
+        except Exception as e:
+            print('CRISPResso2 failed')
+            raise e
+        else:
+            pool.close()
+        pool.join()
+        return [y for x in results for y in x]
 
 
 
 def run_subprocess(cmd):
     return sb.call(cmd, shell=True)
 
-def run_parallel_commands(commands_arr,n_processes=1,descriptor='CRISPResso2',continue_on_fail=False):
+def run_parallel_commands(commands_arr, n_processes=1, descriptor='CRISPResso2', continue_on_fail=False):
     """
     input: commands_arr: list of shell commands to run
     descriptor: string to print out to user describing run
     """
-    pool = mp.Pool(processes = n_processes)
+    if n_processes > 1:
+        pool = mp.Pool(processes = n_processes)
+    else:
+        for idx, command in enumerate(commands_arr):
+            return_value = run_subprocess(command)
+            if return_value != 0 and not continue_on_fail:
+                raise Exception(f'{descriptor} #{idx} was failed')
+        return
 
     #handle signals -- bug in python 2.7 (https://stackoverflow.com/questions/11312525/catch-ctrlc-sigint-and-exit-multiprocesses-gracefully-in-python)
     original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
@@ -251,7 +287,7 @@ def run_parallel_commands(commands_arr,n_processes=1,descriptor='CRISPResso2',co
     pool.join()
 
 
-def run_plot(plot_func, plot_args, num_processes, process_futures, process_pool):
+def run_plot(plot_func, plot_args, num_processes, process_futures, process_pool, halt_on_plot_fail):
     """Run a plot in parallel if num_processes > 1, otherwise in serial.
 
     Parameters
@@ -266,12 +302,22 @@ def run_plot(plot_func, plot_args, num_processes, process_futures, process_pool)
         The list of futures that submitting the parallel job will return.
     process_pool: ProcessPoolExecutor or ThreadPoolExecutor
         The pool to submit the job to.
+    halt_on_plot_fail: bool
+        If True, an exception will be raised if the plot fails
 
     Returns
     -------
     None
     """
-    if num_processes > 1:
-        process_futures[process_pool.submit(plot_func, **plot_args)] = (plot_func, plot_args)
-    else:
-        plot_func(**plot_args)
+    logger = logging.getLogger(getmodule(stack()[1][0]).__name__)
+    try:
+        if num_processes > 1:
+            process_futures[process_pool.submit(plot_func, **plot_args)] = (plot_func, plot_args)
+        else:
+            plot_func(**plot_args)
+    except Exception as e:
+        if halt_on_plot_fail:
+            logger.critical(f"Plot error, halting execution \n")
+            raise PlotException(f'There was an error generating plot {plot_func.__name__}.')
+        logger.warn(f"Plot error {e}, skipping plot \n")
+        logger.debug(traceback.format_exc())
diff --git a/CRISPResso2/CRISPRessoPlot.py b/CRISPResso2/CRISPRessoPlot.py
index 6ac35e86..a4d16495 100644
--- a/CRISPResso2/CRISPRessoPlot.py
+++ b/CRISPResso2/CRISPRessoPlot.py
@@ -8,12 +8,12 @@
 import numpy as np
 import pandas as pd
 import matplotlib
+import json
 matplotlib.use('AGG')
 import matplotlib.pyplot as plt
 import matplotlib.patches as patches
 import matplotlib.cm as cm
 import matplotlib.gridspec as gridspec
-import plotly.express as px
 from collections import defaultdict
 from copy import deepcopy
 import re
@@ -72,13 +72,30 @@ def get_nuc_color(nuc, alpha):
 
         return (charSum, (1-charSum), (2*charSum*(1-charSum)))
 
-def get_color_lookup(nucs, alpha):
-    colorLookup = {}
-    for nuc in nucs:
-        colorLookup[nuc] = get_nuc_color(nuc, alpha)
-    return colorLookup
+def get_color_lookup(nucs, alpha, custom_colors=None):
+    if custom_colors is None:
+        colorLookup = {}
+        for nuc in nucs:
+            colorLookup[nuc] = get_nuc_color(nuc, alpha)
+        return colorLookup
+    else:
+        get_color = lambda x, y, z: (x / 255.0, y / 255.0, z / 255.0, alpha)
+        colors = {}
+        for nuc in nucs:
+            if nuc == 'INS':
+                rgb = (193, 129, 114)
+            else:
+                rgb = hex_to_rgb(custom_colors[nuc])
+            colors[nuc] = get_color(rgb[0], rgb[1], rgb[2])
+        return colors
+
 
-def plot_nucleotide_quilt(nuc_pct_df,mod_pct_df,fig_filename_root,save_also_png=False,sgRNA_intervals=None,min_text_pct=0.5,max_text_pct=0.95,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None,shade_unchanged=True,group_column='Batch'):
+def hex_to_rgb(value):
+    value = value.lstrip('#')
+    lv = len(value)
+    return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
+
+def plot_nucleotide_quilt(nuc_pct_df,mod_pct_df,fig_filename_root, custom_colors, save_also_png=False,sgRNA_intervals=None,min_text_pct=0.5,max_text_pct=0.95,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None,shade_unchanged=True,group_column='Batch', **kwargs):
     """
     Plots a nucleotide quilt with each square showing the percentage of each base at that position in the reference
     nuc_pct_df: dataframe with percents of each base (ACTGN-) at each position
@@ -111,8 +128,9 @@ def plot_nucleotide_quilt(nuc_pct_df,mod_pct_df,fig_filename_root,save_also_png=
         samplesList.append(nuc_pct_df.iloc[sample_row_start, 0])
 
     # make a color map of fixed colors
-    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1)
-    unchanged_color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=0.3)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
+    unchanged_color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=0.3,
+                                              custom_colors=custom_colors)
 
     #fig = plt.figure(figsize=(amp_len/2.0,nSamples*2))
     #fig = plt.figure(figsize=(amp_len,nSamples))
@@ -185,7 +203,7 @@ def plot_nucleotide_quilt(nuc_pct_df,mod_pct_df,fig_filename_root,save_also_png=
             sample_row_start = nNucs * i
             y_start = nSamples - i
 
-            ins_pct = float(mod_pct_df_indexed.loc[sampleName,'Insertions_Left'][pos_ind-2])
+            ins_pct = float(mod_pct_df_indexed.loc[sampleName,'Insertions_Left'].iloc[pos_ind-2])
 
             if ins_pct > min_plot_pct:
                 obs_pct = ins_pct * plotPct
@@ -222,7 +240,7 @@ def plot_nucleotide_quilt(nuc_pct_df,mod_pct_df,fig_filename_root,save_also_png=
 
     plot_y_start = ref_y_start - 0.1
 
-    if sgRNA_intervals and len(sgRNA_intervals) > 0:
+    if sgRNA_intervals:
         sgRNA_rows = get_rows_for_sgRNA_annotation(sgRNA_intervals, amp_len)
         num_sgRNA_rows = max(sgRNA_rows) + 1
         sgRNA_y_height = num_sgRNA_rows * 0.3
@@ -314,6 +332,7 @@ def plot_indel_size_distribution(
     title,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
     densityPct_0 = 0.0
@@ -345,8 +364,13 @@ def plot_indel_size_distribution(
         fancybox=True,
         shadow=True,
     )
-    lgd.legendHandles[0].set_height(3)
-    lgd.legendHandles[1].set_height(3)
+    # Try catch block to account for updated naming conventions in matplotlib v3.9.0
+    try:
+        lgd.legendHandles[0].set_height(3)
+        lgd.legendHandles[1].set_height(3)
+    except AttributeError as e:
+        lgd.legend_handles[0].set_height(3)
+        lgd.legend_handles[1].set_height(3)
 
     ax.tick_params(left=True, bottom=True)
     fig.savefig(plot_root + '.pdf', pad_inches=1, bbox_inches='tight')
@@ -363,7 +387,9 @@ def plot_frequency_deletions_insertions(
     xmax_del,
     xmax_ins,
     xmax_mut,
+    custom_colors,
     save_also_png=False,
+    **kwargs,
 ):
     y_values_mut = ref['y_values_mut']
     x_bins_mut = ref['x_bins_mut']
@@ -392,8 +418,13 @@ def plot_frequency_deletions_insertions(
         fancybox=True,
         shadow=True,
     )
-    lgd.legendHandles[0].set_height(6)
-    lgd.legendHandles[1].set_height(6)
+    # Try catch block to account for updated naming conventions in matplotlib v3.9.0
+    try:
+        lgd.legendHandles[0].set_height(6)
+        lgd.legendHandles[1].set_height(6)
+    except AttributeError as e:
+        lgd.legend_handles[0].set_height(6)
+        lgd.legend_handles[1].set_height(6)
 
     ax.set_xlim([-1, xmax_ins])
     y_label_values= np.round(
@@ -426,8 +457,13 @@ def plot_frequency_deletions_insertions(
         fancybox=True,
         shadow=True,
     )
-    lgd.legendHandles[0].set_height(6)
-    lgd.legendHandles[1].set_height(6)
+    # Try catch block to account for updated naming conventions in matplotlib v3.9.0
+    try:
+        lgd.legendHandles[0].set_height(6)
+        lgd.legendHandles[1].set_height(6)
+    except AttributeError as e:
+        lgd.legend_handles[0].set_height(6)
+        lgd.legend_handles[1].set_height(6)
 
     ax.set_xlim([-1 * xmax_del, 1])
     y_label_values = np.round(
@@ -460,8 +496,13 @@ def plot_frequency_deletions_insertions(
         fancybox=True,
         shadow=True,
     )
-    lgd.legendHandles[0].set_height(6)
-    lgd.legendHandles[1].set_height(6)
+    # Try catch block to account for updated naming conventions in matplotlib v3.9.0
+    try:
+        lgd.legendHandles[0].set_height(6)
+        lgd.legendHandles[1].set_height(6)
+    except AttributeError as e:
+        lgd.legend_handles[0].set_height(6)
+        lgd.legend_handles[1].set_height(6)
 
     ax.set_xlim([-1, xmax_mut])
     y_label_values= np.round(
@@ -497,7 +538,9 @@ def plot_amplicon_modifications(
     y_max,
     plot_titles,
     plot_root,
+    custom_colors,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
 
@@ -535,9 +578,9 @@ def plot_amplicon_modifications(
 
     ax.plot(
         all_indelsub_count_vectors,
-        'r',
         lw=3,
         label=plot_titles['combined'],
+        color=custom_colors['Deletion']
     )
 
     if cut_points:
@@ -648,7 +691,9 @@ def plot_modification_frequency(
     y_max,
     plot_title,
     plot_root,
+    custom_colors,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
 
@@ -685,13 +730,13 @@ def plot_modification_frequency(
         ax.add_patch(p)
 
     ax.plot(
-        all_insertion_count_vectors, 'r', lw=3, label='Insertions',
+        all_insertion_count_vectors, lw=3, label='Insertions', color=custom_colors['Insertion']
     )
     ax.plot(
-        all_deletion_count_vectors, 'm', lw=3, label='Deletions',
+        all_deletion_count_vectors, lw=3, label='Deletions', color=custom_colors['Deletion']
     )
     ax.plot(
-        all_substitution_count_vectors, 'g', lw=3, label='Substitutions',
+        all_substitution_count_vectors, lw=3, label='Substitutions', color=custom_colors['Substitution']
     )
 
     y_max = max(
@@ -816,7 +861,9 @@ def plot_quantification_window_locations(
     ref_name,
     plot_title,
     plot_root,
+    custom_colors,
     save_also_png,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
 
@@ -859,11 +906,9 @@ def plot_quantification_window_locations(
         )
         ax.add_patch(p)
 
-    ax.plot(insertion_count_vectors, 'r', linewidth=3, label='Insertions')
-    ax.plot(deletion_count_vectors, 'm', linewidth=3, label='Deletions')
-    ax.plot(
-        substitution_count_vectors, 'g', linewidth=3, label='Substitutions',
-    )
+    ax.plot(insertion_count_vectors, linewidth=3, label='Insertions', color=custom_colors['Insertion'])
+    ax.plot(deletion_count_vectors, linewidth=3, label='Deletions', color=custom_colors['Deletion'])
+    ax.plot(substitution_count_vectors, linewidth=3, label='Substitutions', color=custom_colors['Substitution'])
 
     if cut_points:
         added_legend = False
@@ -986,6 +1031,7 @@ def plot_position_dependent_indels(
     plot_titles,
     plot_root,
     save_also_png,
+    **kwargs,
 ):
     fig, ax = plt.subplots(1, 2, figsize=(24, 10))
     ax1 = ax[0]
@@ -1089,7 +1135,9 @@ def plot_global_modifications_reference(
     ref_name,
     plot_title,
     plot_root,
+    custom_colors,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
     ref1_all_insertion_positions = ref1_all_insertion_count_vectors
@@ -1103,13 +1151,13 @@ def plot_global_modifications_reference(
         1,
     ) * 1.1
 
-    ax.plot(ref1_all_insertion_positions, 'r', linewidth=3, label='Insertions')
-    ax.plot(ref1_all_deletion_positions, 'm', linewidth=3, label='Deletions')
+    ax.plot(ref1_all_insertion_positions, linewidth=3, label='Insertions', color=custom_colors['Insertion'])
+    ax.plot(ref1_all_deletion_positions, linewidth=3, label='Deletions', color=custom_colors['Deletion'])
     ax.plot(
         ref1_all_substitution_positions,
-        'g',
         linewidth=3,
         label='Substitutions',
+        color=custom_colors['Substitution']
     )
 
     ref1_cut_points = ref1['sgRNA_cut_points']
@@ -1248,6 +1296,7 @@ def plot_frameshift_analysis(
     ref_name,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     """Plot 5: Plot a pie chart to plot_root showing classification of reads with regard to coding region for a specific reference sequence, also including a diagram of where the coding region is within the amplicon.
 
@@ -1396,6 +1445,7 @@ def plot_frameshift_frequency(
     plot_titles,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(1, 2, figsize=(22, 10))
     ax1 = ax[0]
@@ -1405,10 +1455,10 @@ def plot_frameshift_frequency(
     ax1.bar(x - 0.1, y)
     ax1.set_xlim(-30.5, 30.5)
     ax1.set_frame_on(False)
-    ax1.set_xticks([idx for idx in range(-30, 31) if idx % 3])
+    ax1.set_xticks([idx for idx in range(-30, 31) if idx % 3 == 0])
     ax1.tick_params(
         which='both',      # both major and minor ticks are affected
-        bottom=False,      # ticks along the bottom edge are off
+        bottom=True,      # ticks along the bottom edge are off
         top=False,         # ticks along the top edge are off
         labelbottom=True,  # labels along the bottom edge are off
     )
@@ -1416,7 +1466,7 @@ def plot_frameshift_frequency(
     xmin, xmax = ax1.get_xaxis().get_view_interval()
     ymin, ymax = ax1.get_yaxis().get_view_interval()
     ax1.set_xticklabels(
-        map(str, [idx for idx in range(-30, 31) if idx % 3]),
+        map(str, [idx for idx in range(-30, 31) if idx % 3 == 0]),
         rotation='vertical',
     )
     ax1.set_title(plot_titles['fs'])
@@ -1448,7 +1498,7 @@ def plot_frameshift_frequency(
     ax2.set_xticks([idx for idx in range(-30, 31) if (idx % 3 == 0)])
     ax2.tick_params(
         which='both',      # both major and minor ticks are affected
-        bottom=False,      # ticks along the bottom edge are off
+        bottom=True,      # ticks along the bottom edge are off
         top=False,         # ticks along the top edge are off
         labelbottom=True,  # labels along the bottom edge are off
     )
@@ -1491,6 +1541,7 @@ def plot_global_frameshift_analysis(
     global_non_modified_non_frameshift,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(12, 12))
 
@@ -1527,6 +1578,7 @@ def plot_global_frameshift_in_frame_mutations(
     global_hists_inframe,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, axs = plt.subplots(2, 1, figsize=(22, 10))
     ax1 = axs[0]
@@ -1536,10 +1588,11 @@ def plot_global_frameshift_in_frame_mutations(
     ax1.bar(x - 0.1, y)
     ax1.set_xlim(-30.5, 30.5)
     ax1.set_frame_on(False)
-    ax1.set_xticks([idx for idx in range(-30, 31) if idx % 3])
+    ax1.set_xticks([idx for idx in range(-30, 31) if idx % 3 == 0])
     ax1.tick_params(
         which='both',      # both major and minor ticks are affected
-        bottom=False,      # ticks along the bottom edge are off
+        left=True,
+        bottom=True,      # ticks along the bottom edge are off
         top=False,         # ticks along the top edge are off
         labelbottom=True,  # labels along the bottom edge are off
     )
@@ -1547,7 +1600,7 @@ def plot_global_frameshift_in_frame_mutations(
     xmin, xmax = ax1.get_xaxis().get_view_interval()
     ymin, ymax = ax1.get_yaxis().get_view_interval()
     ax1.set_xticklabels(
-        map(str, [idx for idx in range(-30, 31) if idx % 3]),
+        map(str, [idx for idx in range(-30, 31) if idx % 3 == 0]),
         rotation='vertical',
     )
     ax1.set_title('Global Frameshift profile')
@@ -1622,6 +1675,7 @@ def plot_impact_on_splice_sites(
     global_count_total,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(12, 12))
     patches, texts, autotexts = ax.pie(
@@ -1659,26 +1713,28 @@ def plot_non_coding_mutations(
     sgRNA_intervals,
     plot_title,
     plot_root,
+    custom_colors,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(10, 10))
     ax.plot(
         insertion_count_vectors_noncoding,
-        'r',
         linewidth=3,
         label='Insertions',
+        color=custom_colors['Insertion']
     )
     ax.plot(
         deletion_count_vectors_noncoding,
-        'm',
         linewidth=3,
         label='Deletions',
+        color=custom_colors['Deletion']
     )
     ax.plot(
         substitution_count_vectors_noncoding,
-        'g',
         linewidth=3,
         label='Substitutions',
+        color=custom_colors['Substitution']
     )
 
     y_max = max(
@@ -1800,6 +1856,7 @@ def plot_potential_splice_sites(
     count_total,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
     fig, ax = plt.subplots(figsize=(12, 12))
     patches, texts, autotexts = ax.pie(
@@ -1830,8 +1887,9 @@ def plot_scaffold_indel_lengths(
     df_scaffold_insertion_sizes,
     plot_root,
     save_also_png=False,
+    **kwargs,
 ):
-    colors = 'b', 'g'
+    colors = ['b', 'g']
     fig, ax = plt.subplots(figsize=(12, 6))
     ax.hist(
         [
@@ -1974,7 +2032,7 @@ def add_sgRNA_to_ax(ax,sgRNA_intervals,sgRNA_y_start,sgRNA_y_height,amp_len,x_of
         else:
             ax.text(x_offset+min_sgRNA_x, this_sgRNA_y_start + this_sgRNA_y_height/2, 'sgRNA ', horizontalalignment='right', verticalalignment='center', fontsize=font_size)
 
-def plot_conversion_map(nuc_pct_df,fig_filename_root,conversion_nuc_from,conversion_nuc_to,save_also_png,plotPct = 0.9,min_text_pct=0.3,max_text_pct=0.9,conversion_scale_max=None,sgRNA_intervals=None,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None):
+def plot_conversion_map(nuc_pct_df,fig_filename_root,conversion_nuc_from,conversion_nuc_to,save_also_png,custom_colors,plotPct = 0.9,min_text_pct=0.3,max_text_pct=0.9,conversion_scale_max=None,sgRNA_intervals=None,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None,**kwargs):
     """
     Plots a heatmap of conversion across several sequences
     :param nuc_pct_df combined df of multiple batches
@@ -2034,7 +2092,9 @@ def plot_conversion_map(nuc_pct_df,fig_filename_root,conversion_nuc_from,convers
     plt.clf()
 
     # make a color map of fixed colors (for coloring reference in this example)
-    color_lookup = get_color_lookup(['A', 'T', 'C', 'G'], alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
+    unchanged_color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=0.3,
+                                              custom_colors=custom_colors)
 
 #    fig = plt.figure(figsize=(amp_len/2.0,nSamples*2))
     fig, ax = plt.subplots(figsize=((amp_len+10)/2.0, (nSamples+1)*2))
@@ -2144,7 +2204,7 @@ def plot_conversion_map(nuc_pct_df,fig_filename_root,conversion_nuc_from,convers
     plt.close(fig)
 
 
-def plot_subs_across_ref(ref_len, ref_seq, ref_name, ref_count, all_substitution_base_vectors, plot_title, fig_filename_root, save_also_png, quantification_window_idxs=None):
+def plot_subs_across_ref(ref_len, ref_seq, ref_name, ref_count, all_substitution_base_vectors, plot_title, fig_filename_root, save_also_png, custom_colors, quantification_window_idxs=None,**kwargs):
     """
     Plots substitutions across the reference sequece - each position on the x axis reprsents a nucleotide in the reference
     bars at each x posion show the number of times the reference nucleotide was substituted for another reference
@@ -2153,8 +2213,7 @@ def plot_subs_across_ref(ref_len, ref_seq, ref_name, ref_count, all_substitution
     fig, ax = plt.subplots(figsize=(16, 8))
     ind = np.arange(ref_len)
 
-    alph = ['A', 'C', 'G', 'T', 'N']
-    color_lookup = get_color_lookup(alph, alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
 
     pA = ax.bar(ind, all_substitution_base_vectors[ref_name+"_A"], color=color_lookup['A'])
     pC = ax.bar(ind, all_substitution_base_vectors[ref_name+"_C"], color=color_lookup['C'], bottom=all_substitution_base_vectors[ref_name+"_A"])
@@ -2210,7 +2269,7 @@ def plot_subs_across_ref(ref_len, ref_seq, ref_name, ref_count, all_substitution
         fig.savefig(fig_filename_root + '.png', bbox_extra_artists=(lgd,), bbox_inches='tight')
     plt.close(fig)
 
-def plot_sub_freqs(alt_nuc_counts, plot_title, fig_filename_root, save_also_png):
+def plot_sub_freqs(alt_nuc_counts, plot_title, fig_filename_root, save_also_png, custom_colors,**kwargs):
     """
     Plots histogram of substitution frequencies for each nucleotide (from nuc X to nuc Y)
     input:
@@ -2220,8 +2279,7 @@ def plot_sub_freqs(alt_nuc_counts, plot_title, fig_filename_root, save_also_png)
     #plot all substitution rates
     fig, ax = plt.subplots(figsize=(8.3, 8))
 
-    alph = ['A', 'C', 'G', 'T', 'N']
-    color_lookup = get_color_lookup(alph, alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
 
     ax.bar([1, 2, 3], [alt_nuc_counts['A']['C'], alt_nuc_counts['A']['G'], alt_nuc_counts['A']['T']], color=color_lookup['A'])
     ax.bar([5, 6, 7], [alt_nuc_counts['C']['A'], alt_nuc_counts['C']['G'], alt_nuc_counts['C']['T']], color=color_lookup['C'])
@@ -2241,7 +2299,7 @@ def plot_sub_freqs(alt_nuc_counts, plot_title, fig_filename_root, save_also_png)
         fig.savefig(fig_filename_root + '.png', bbox_inches='tight')
     plt.close(fig)
 
-def plot_nuc_freqs(df_nuc_freq, tot_aln_reads, plot_title, fig_filename_root, save_also_png):
+def plot_nuc_freqs(df_nuc_freq, tot_aln_reads, plot_title, fig_filename_root, save_also_png,**kwargs):
     """
     Plots a heatmap of the percentage of reads that had each nucletide at each base in the reference
     Positions in the reference that have more than one allele can be spotted using this plot
@@ -2257,7 +2315,7 @@ def plot_nuc_freqs(df_nuc_freq, tot_aln_reads, plot_title, fig_filename_root, sa
         plt.savefig(fig_filename_root + '.png', bbox_inches='tight')
     plt.close()
 
-def plot_log_nuc_freqs(df_nuc_freq,tot_aln_reads,plot_title,fig_filename_root,save_also_png,quantification_window_idxs=None):
+def plot_log_nuc_freqs(df_nuc_freq,tot_aln_reads,plot_title,fig_filename_root,save_also_png,quantification_window_idxs=None,**kwargs):
     """
     Plots a heatmap of the percentage of reads that had each nucletide at each base in the reference
     Positions in the reference that have more than one allele can be spotted using this plot
@@ -2295,14 +2353,14 @@ def plot_log_nuc_freqs(df_nuc_freq,tot_aln_reads,plot_title,fig_filename_root,sa
     plt.close(fig)
 
 
-def plot_conversion_at_sel_nucs(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png):
+def plot_conversion_at_sel_nucs(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png, custom_colors,**kwargs):
     '''
     Plots the conversion at selected nucleotides
     Looks for the 'conversion_nuc_from' in the ref_sequence and sets those as 'selected nucleotides'
     At selected nucleotides, the proportion of each base is shown as a barplot
     '''
     nucs = list(df_subs.index)
-    color_lookup = get_color_lookup(nucs, alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
     amp_len = len(ref_sequence)
 
     fig = plt.figure(figsize=(amp_len, 6))
@@ -2357,14 +2415,14 @@ def plot_conversion_at_sel_nucs(df_subs, ref_name, ref_sequence, plot_title, con
         fig.savefig(fig_filename_root+'.png', bbox_inches='tight', pad_inches=0.1)
     plt.close(fig)
 
-def plot_conversion_at_sel_nucs_not_include_ref(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png):
+def plot_conversion_at_sel_nucs_not_include_ref(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png, custom_colors, **kwargs):
     '''
     Plots the conversion at selected nucleotides but ignores non-substitutions (for example at nucs that are 'C' in the reference, bars show the proportion of A T G (not C))
     Looks for the 'conversion_nuc_from' in the ref_sequence and sets those as 'selected nucleotides'
     At selected nucleotides, the proportion of each substitution is shown as a barplot
     '''
     nucs = list(df_subs.index)
-    color_lookup = get_color_lookup(nucs, alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
     amp_len = len(ref_sequence)
 
     fig = plt.figure(figsize=(amp_len, 6))
@@ -2429,14 +2487,14 @@ def plot_conversion_at_sel_nucs_not_include_ref(df_subs, ref_name, ref_sequence,
         fig.savefig(fig_filename_root+'.png', bbox_inches='tight', pad_inches=0.1)
     plt.close(fig)
 
-def plot_conversion_at_sel_nucs_not_include_ref_scaled(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png):
+def plot_conversion_at_sel_nucs_not_include_ref_scaled(df_subs, ref_name, ref_sequence, plot_title, conversion_nuc_from, fig_filename_root, save_also_png, custom_colors, **kwargs):
     '''
     Plots the conversion at selected nucleotides not including reference base, scaled by number of events
     Looks for the 'conversion_nuc_from' in the ref_sequence and sets those as 'selected nucleotides'
     At selected nucleotides, the count of each base is shown as a barplot
     '''
     nucs = list(df_subs.index)
-    color_lookup = get_color_lookup(nucs, alpha=1)
+    color_lookup = get_color_lookup(['A', 'T', 'C', 'G', 'N', 'INS', '-'], alpha=1, custom_colors=custom_colors)
     nucs.remove(conversion_nuc_from)
     amp_len = len(ref_sequence)
 
@@ -2520,7 +2578,7 @@ def _annotate_heatmap(self, ax, mesh):
 
 
         for x, y, m, color, val, per_element_dict  in zip(xpos.flat, ypos.flat,
-                                       mesh.get_array(), mesh.get_facecolors(),
+                                       mesh.get_array().flat, mesh.get_facecolors(),
                                        self.annot_data.flat, self.per_element_annot_kws.flat):
             #print per_element_dict
             if m is not np.ma.masked:
@@ -2591,6 +2649,7 @@ def custom_heatmap(data, vmin=None, vmax=None, cmap=None, center=None, robust=Fa
     plotter.plot(ax, cbar_ax, kwargs)
     return ax
 
+
 def prep_alleles_table(df_alleles, reference_seq, MAX_N_ROWS, MIN_FREQUENCY):
     """
     Prepares a df of alleles for Plotting
@@ -2609,7 +2668,6 @@ def prep_alleles_table(df_alleles, reference_seq, MAX_N_ROWS, MIN_FREQUENCY):
     """
     dna_to_numbers={'-':0,'A':1,'T':2,'C':3,'G':4,'N':5}
     seq_to_numbers= lambda seq: [dna_to_numbers[x] for x in seq]
-
     X=[]
     annot=[]
     y_labels=[]
@@ -2699,7 +2757,22 @@ def prep_alleles_table_compare(df_alleles, sample_name_1, sample_name_2, MAX_N_R
 
     return X, annot, y_labels, insertion_dict, per_element_annot_kws
 
-def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insertion_dict,per_element_annot_kws,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None):
+def plot_alleles_heatmap(
+        reference_seq,
+        fig_filename_root,
+        X,
+        annot,
+        y_labels,
+        insertion_dict,
+        per_element_annot_kws,
+        custom_colors,
+        SAVE_ALSO_PNG=False,
+        plot_cut_point=True,
+        cut_point_ind=None,
+        sgRNA_intervals=None,
+        sgRNA_names=None,
+        sgRNA_mismatches=None,
+        **kwargs):
     """
     Plots alleles in a heatmap (nucleotides color-coded for easy visualization)
     input:
@@ -2712,6 +2785,7 @@ def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insert
     -per_element_annot_kws: annotations for each cell (e.g. bold for substitutions, etc.)
     -SAVE_ALSO_PNG: whether to write png file as well
     -plot_cut_point: if false, won't draw 'predicted cleavage' line
+    -cut_point_ind: index of cut point (if None, will be plot in the middle calculated as len(reference_seq)/2)
     -sgRNA_intervals: locations where sgRNA is located
     -sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
     -sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
@@ -2728,16 +2802,17 @@ def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insert
     INDEL_color = get_nuc_color('N', alpha)
 
     if custom_colors is not None:
+        hex_alpha = '66'  # this is equivalent to 40% in hexadecimal
         if 'A' in custom_colors:
-            A_color = custom_colors['A']
+            A_color = custom_colors['A'] + hex_alpha
         if 'T' in custom_colors:
-            T_color = custom_colors['T']
+            T_color = custom_colors['T'] + hex_alpha
         if 'C' in custom_colors:
-            C_color = custom_colors['C']
+            C_color = custom_colors['C'] + hex_alpha
         if 'G' in custom_colors:
-            G_color = custom_colors['G']
+            G_color = custom_colors['G'] + hex_alpha
         if 'N' in custom_colors:
-            INDEL_color = custom_colors['N']
+            INDEL_color = custom_colors['N'] + hex_alpha
 
     dna_to_numbers={'-':0,'A':1,'T':2,'C':3,'G':4,'N':5}
     seq_to_numbers= lambda seq: [dna_to_numbers[x] for x in seq]
@@ -2832,7 +2907,9 @@ def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insert
 
     #cut point vertical line
     if plot_cut_point:
-        ax_hm.vlines([plot_nuc_len/2], *ax_hm.get_ylim(), linestyles='dashed')
+        if cut_point_ind is None:
+            cut_point_ind = [plot_nuc_len / 2]
+        ax_hm.vlines(cut_point_ind,*ax_hm.get_ylim(),linestyles='dashed')
 
 
     ax_hm_ref.yaxis.tick_right()
@@ -2867,7 +2944,7 @@ def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insert
         fig.savefig(fig_filename_root+'.png', bbox_inches='tight', bbox_extra_artists=(lgd,))
     plt.close(fig)
 
-def plot_alleles_heatmap_hist(reference_seq,fig_filename_root,X,annot,y_labels,insertion_dict,per_element_annot_kws,count_values,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None):
+def plot_alleles_heatmap_hist(reference_seq,fig_filename_root,X,annot,y_labels,insertion_dict,per_element_annot_kws,count_values,custom_colors,SAVE_ALSO_PNG=False,plot_cut_point=True,cut_point_ind=None,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,**kwargs):
     """
     Plots alleles in a heatmap (nucleotides color-coded for easy visualization)
     input:
@@ -2880,6 +2957,7 @@ def plot_alleles_heatmap_hist(reference_seq,fig_filename_root,X,annot,y_labels,i
     -per_element_annot_kws: annotations for each cell (e.g. bold for substitutions, etc.)
     -SAVE_ALSO_PNG: whether to write png file as well
     -plot_cut_point: if false, won't draw 'predicted cleavage' line
+    -cut_point_ind: index of cut point (if None, will be plot in the middle calculated as len(reference_seq)/2)
     -sgRNA_intervals: locations where sgRNA is located
     -sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
     -sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
@@ -2962,7 +3040,9 @@ def plot_alleles_heatmap_hist(reference_seq,fig_filename_root,X,annot,y_labels,i
 
     #cut point vertical line
     if plot_cut_point:
-        ax_hm.vlines([plot_nuc_len/2], *ax_hm.get_ylim(), linestyles='dashed')
+        if cut_point_ind is None:
+            cut_point_ind = [plot_nuc_len / 2]
+        ax_hm.vlines(cut_point_ind, *ax_hm.get_ylim(), linestyles='dashed')
 
     #create boxes for ins
     for idx, lss in insertion_dict.items():
@@ -3002,7 +3082,94 @@ def plot_alleles_heatmap_hist(reference_seq,fig_filename_root,X,annot,y_labels,i
         plt.savefig(fig_filename_root+'.png', bbox_inches='tight', bbox_extra_artists=(lgd,), pad_inches=0.1)
     plt.close()
 
-def plot_alleles_table(reference_seq,df_alleles,fig_filename_root,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None,annotate_wildtype_allele='****'):
+
+def plot_alleles_table_prepped(
+    reference_seq,
+    prepped_df_alleles,
+    annotations,
+    y_labels,
+    insertion_dict,
+    per_element_annot_kws,
+    is_reference,
+    fig_filename_root,
+    custom_colors,
+    SAVE_ALSO_PNG=False,
+    plot_cut_point=True,
+    cut_point_ind=None,
+    sgRNA_intervals=None,
+    sgRNA_names=None,
+    sgRNA_mismatches=None,
+    annotate_wildtype_allele='****',
+    **kwargs,
+):
+    """Plot an allele table for a pre-filtered dataframe with allele frequencies.
+
+    Parameters
+    ----------
+    reference_seq : str
+        The reference amplicon sequence to plot.
+    prepped_df_alleles : pd.DataFrame
+        Merged dataframe (should include columns "#Reads','%Reads"), from `CRISPRessoPlot.prep_alleles_table`.
+    annotations : list
+        List of annotations for each allele, from `CRISPRessoPlot.prep_alleles_table`.
+    y_labels : list
+        List of labels for each row/allele, from `CRISPRessoPlot.prep_alleles_table`.
+    insertion_dict : dict
+        Locations of insertions -- red squares will be drawn around these, from `CRISPRessoPlot.prep_alleles_table`.
+    per_element_annot_kws : list
+        Annotations for each cell (e.g. bold for substitutions, etc.), from `CRISPRessoPlot.prep_alleles_table`.
+    is_reference : list
+        List of booleans for whether the read is equal to the reference, from `CRISPRessoPlot.prep_alleles_table`.
+    fig_filename_root : str
+        Figure filename to plot (not including '.pdf' or '.png').
+    custom_colors : dict
+        Dict of colors to plot (e.g. colors['A'] = (1,0,0,0.4) # red,blue,green,alpha ).
+    SAVE_ALSO_PNG : bool
+        Whether to write png file as well.
+    plot_cut_point : bool
+        If False, won't draw 'predicted cleavage' line.
+    cut_point_ind : int
+        Index of cut point (if None, will be plot in the middle calculated as len(reference_seq)/2).
+    sgRNA_intervals : list
+        Locations where sgRNAs are located.
+    sgRNA_names : list
+        Names of sgRNAs (otherwise empty).
+    sgRNA_mismatches : list
+        Array (for each sgRNA_interval) of locations in sgRNA where there are mismatches.
+    annotate_wildtype_allele : str
+        String to add to the end of the wildtype allele (e.g. '****' or '').
+    kwargs : dict
+        Additional keyword arguments.
+
+    Returns
+    -------
+    None
+    """
+    if annotate_wildtype_allele != '':
+        for ix, is_ref in enumerate(is_reference):
+            if is_ref:
+                y_labels[ix] += annotate_wildtype_allele
+
+    plot_alleles_heatmap(
+        reference_seq=reference_seq,
+        fig_filename_root=fig_filename_root,
+        X=prepped_df_alleles,
+        annot=annotations,
+        y_labels=y_labels,
+        insertion_dict=insertion_dict,
+        per_element_annot_kws=per_element_annot_kws,
+        custom_colors=custom_colors,
+        SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+        plot_cut_point=plot_cut_point,
+        cut_point_ind=cut_point_ind,
+        sgRNA_intervals=sgRNA_intervals,
+        sgRNA_names=sgRNA_names,
+        sgRNA_mismatches=sgRNA_mismatches,
+    )
+
+
+
+def plot_alleles_table(reference_seq,df_alleles,fig_filename_root,custom_colors,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,cut_point_ind=None,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,annotate_wildtype_allele='****',**kwargs):
     """
     plots an allele table for a dataframe with allele frequencies
     input:
@@ -3013,6 +3180,7 @@ def plot_alleles_table(reference_seq,df_alleles,fig_filename_root,MIN_FREQUENCY=
     MAX_N_ROWS: max rows to plot
     SAVE_ALSO_PNG: whether to write png file as well
     plot_cut_point: if false, won't draw 'predicted cleavage' line
+    cut_point_ind: index of cut point (if None, will be plot in the middle calculated as len(reference_seq)/2)
     sgRNA_intervals: locations where sgRNA is located
     sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
     sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
@@ -3024,9 +3192,23 @@ def plot_alleles_table(reference_seq,df_alleles,fig_filename_root,MIN_FREQUENCY=
         for ix, is_ref in enumerate(is_reference):
             if is_ref:
                 y_labels[ix] += annotate_wildtype_allele
-    plot_alleles_heatmap(reference_seq, fig_filename_root, X, annot, y_labels, insertion_dict, per_element_annot_kws, SAVE_ALSO_PNG, plot_cut_point, sgRNA_intervals, sgRNA_names, sgRNA_mismatches, custom_colors)
 
-def plot_alleles_table_from_file(alleles_file_name,fig_filename_root,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None,annotate_wildtype_allele=''):
+    plot_alleles_heatmap(reference_seq=reference_seq,
+                         fig_filename_root=fig_filename_root,
+                         X=X,
+                         annot=annot,
+                         y_labels=y_labels,
+                         insertion_dict=insertion_dict,
+                         per_element_annot_kws=per_element_annot_kws,
+                         custom_colors=custom_colors,
+                         SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                         plot_cut_point=plot_cut_point,
+                         cut_point_ind=cut_point_ind,
+                         sgRNA_intervals=sgRNA_intervals,
+                         sgRNA_names=sgRNA_names,
+                         sgRNA_mismatches=sgRNA_mismatches)
+
+def plot_alleles_table_from_file(alleles_file_name,fig_filename_root,custom_colors,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,cut_point_ind=None,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,annotate_wildtype_allele='',**kwargs):
     """
     plots an allele table for a dataframe with allele frequencies
     infers the reference sequence by finding reference sequences without gaps (-)
@@ -3039,6 +3221,7 @@ def plot_alleles_table_from_file(alleles_file_name,fig_filename_root,MIN_FREQUEN
     MAX_N_ROWS: max rows to plot
     SAVE_ALSO_PNG: whether to write png file as well
     plot_cut_point: if false, won't draw 'predicted cleavage' line
+    cut_point_ind: index of cut point (if None, will be plot in the middle calculated as len(reference_seq)/2)
     sgRNA_intervals: locations where sgRNA is located
     sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
     sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
@@ -3060,9 +3243,22 @@ def plot_alleles_table_from_file(alleles_file_name,fig_filename_root,MIN_FREQUEN
         for ix, is_ref in enumerate(is_reference):
             if is_ref:
                 y_labels[ix] += annotate_wildtype_allele
-    plot_alleles_heatmap(reference_seq, fig_filename_root, X, annot, y_labels, insertion_dict, per_element_annot_kws, SAVE_ALSO_PNG, plot_cut_point, sgRNA_intervals, sgRNA_names, sgRNA_mismatches, custom_colors)
-
-def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,MIN_FREQUENCY=None,MAX_N_ROWS=None,SAVE_ALSO_PNG=False,custom_colors=None,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None):
+    plot_alleles_heatmap(reference_seq=reference_seq,
+                         fig_filename_root=fig_filename_root,
+                         X=X,
+                         annot=annot,
+                         y_labels=y_labels,
+                         insertion_dict=insertion_dict,
+                         per_element_annot_kws=per_element_annot_kws,
+                         custom_colors=custom_colors,
+                         SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                         plot_cut_point=plot_cut_point,
+                         cut_point_ind=cut_point_ind,
+                         sgRNA_intervals=sgRNA_intervals,
+                         sgRNA_names=sgRNA_names,
+                         sgRNA_mismatches=sgRNA_mismatches)
+
+def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,custom_colors,MIN_FREQUENCY=None,MAX_N_ROWS=None,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,**kwargs):
     """
     plots an allele table for each sgRNA/amplicon in a CRISPresso run (useful for plotting after running using the plot harness)
     This function is only used for one-off plotting purposes and not for the general CRISPResso analysis
@@ -3129,11 +3325,23 @@ def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,M
                 new_sgRNA_intervals += [(int_start - new_sel_cols_start - 1, int_end - new_sel_cols_start - 1)]
 
             X, annot, y_labels, insertion_dict, per_element_annot_kws, is_reference = prep_alleles_table(df_alleles, ref_seq_around_cut, MAX_N_ROWS, MIN_FREQUENCY)
-            plot_alleles_heatmap(ref_seq_around_cut, fig_filename_root+"_"+ref_name+"_"+sgRNA_label, X, annot, y_labels, insertion_dict, per_element_annot_kws, SAVE_ALSO_PNG, plot_cut_point, new_sgRNA_intervals, sgRNA_names, sgRNA_mismatches, custom_colors)
+            plot_alleles_heatmap(reference_seq=ref_seq_around_cut,
+                                 fig_filename_root=fig_filename_root+"_"+ref_name+"_"+sgRNA_label,
+                                 X=X,
+                                 annot=annot,
+                                 y_labels=y_labels,
+                                 insertion_dict=insertion_dict,
+                                 per_element_annot_kws=per_element_annot_kws,
+                                 custom_colors=custom_colors,
+                                 SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                                 plot_cut_point=plot_cut_point,
+                                 sgRNA_intervals=new_sgRNA_intervals,
+                                 sgRNA_names=sgRNA_names,
+                                 sgRNA_mismatches=sgRNA_mismatches)
             plot_count += 1
     print('Plotted ' + str(plot_count) + ' plots')
 
-def plot_alleles_table_compare(reference_seq,df_alleles,sample_name_1,sample_name_2,fig_filename_root,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None):
+def plot_alleles_table_compare(reference_seq,df_alleles,sample_name_1,sample_name_2,fig_filename_root,custom_colors=None,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,**kwargs):
     """
     plots an allele table for a dataframe with allele frequencies from two CRISPResso runs
     input:
@@ -3151,9 +3359,21 @@ def plot_alleles_table_compare(reference_seq,df_alleles,sample_name_1,sample_nam
     custom_colors: dict of colors to plot (e.g. colors['A'] = (1,0,0,0.4) # red,blue,green,alpha )
     """
     X, annot, y_labels, insertion_dict, per_element_annot_kws = prep_alleles_table_compare(df_alleles, sample_name_1, sample_name_2, MAX_N_ROWS, MIN_FREQUENCY)
-    plot_alleles_heatmap(reference_seq, fig_filename_root, X, annot, y_labels, insertion_dict, per_element_annot_kws, SAVE_ALSO_PNG, plot_cut_point, sgRNA_intervals, sgRNA_names, sgRNA_mismatches, custom_colors)
-
-def plot_nucleotide_quilt_from_folder(crispresso_output_folder,fig_filename_root,save_also_png=False,sgRNA_intervals=None,min_text_pct=0.5,max_text_pct=0.95,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None,shade_unchanged=True):
+    plot_alleles_heatmap(reference_seq=reference_seq,
+                         fig_filename_root=fig_filename_root,
+                         X=X,
+                         annot=annot,
+                         y_labels=y_labels,
+                         insertion_dict=insertion_dict,
+                         per_element_annot_kws=per_element_annot_kws,
+                         custom_colors=custom_colors,
+                         SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                         plot_cut_point=plot_cut_point,
+                         sgRNA_intervals=sgRNA_intervals,
+                         sgRNA_names=sgRNA_names,
+                         sgRNA_mismatches=sgRNA_mismatches)
+
+def plot_nucleotide_quilt_from_folder(crispresso_output_folder,fig_filename_root,save_also_png=False,sgRNA_intervals=None,min_text_pct=0.5,max_text_pct=0.95,quantification_window_idxs=None,sgRNA_names=None,sgRNA_mismatches=None,shade_unchanged=True,**kwargs):
     """
     plots an allele table for each sgRNA/amplicon in a CRISPresso run (useful for plotting after running using the plot harness)
     This function is only used for one-off plotting purposes and not for the general CRISPResso analysis
@@ -3229,7 +3449,7 @@ def plot_nucleotide_quilt_from_folder(crispresso_output_folder,fig_filename_root
             plot_count += 1
     print('Plotted ' + str(plot_count) + ' plots')
 
-def plot_unmod_mod_pcts(fig_filename_root,df_summary_quantification,save_png,cutoff=None,max_samples_to_include_unprocessed=20):
+def plot_unmod_mod_pcts(fig_filename_root,df_summary_quantification,save_png,cutoff=None,max_samples_to_include_unprocessed=20,**kwargs):
     """
     plots a stacked horizontal barplot for summarizing number of reads, and the percent that are modified and unmodified
     params:
@@ -3288,7 +3508,7 @@ def plot_unmod_mod_pcts(fig_filename_root,df_summary_quantification,save_png,cut
         fig.savefig(fig_filename_root+'.png', bbox_inches='tight')
     plt.close(fig)
 
-def plot_reads_total(fig_filename_root,df_summary_quantification,save_png,cutoff=None):
+def plot_reads_total(fig_filename_root,df_summary_quantification,save_png,cutoff=None,**kwargs):
     """
     plots a horizontal barplot for summarizing number of reads aligned to each sample
     """
@@ -3321,7 +3541,7 @@ def plot_reads_total(fig_filename_root,df_summary_quantification,save_png,cutoff
 
 
 def plot_read_barplot(N_READS_INPUT, N_READS_AFTER_PREPROCESSING, N_TOTAL,
-                      plot_root, save_png
+                      plot_root, save_png,**kwargs
                       ):
     """Plot barplot of total, processed, and aligned reads.
 
@@ -3376,7 +3596,7 @@ def plot_read_barplot(N_READS_INPUT, N_READS_AFTER_PREPROCESSING, N_TOTAL,
 
 def plot_class_piechart_and_barplot(class_counts_order, class_counts, ref_names,
                                     expected_hdr_amplicon_seq, N_TOTAL,
-                                    piechart_plot_root, barplot_plot_root, save_png):
+                                    piechart_plot_root, barplot_plot_root, custom_colors, save_png,**kwargs):
     """Plot a pie chart and barplot of class assignments for reads.
 
        Class assignments include: 'MODIFIED','UNMODIFIED','HDR',etc.
@@ -3471,7 +3691,7 @@ def plot_class_piechart_and_barplot(class_counts_order, class_counts, ref_names,
     plt.close()
 
 
-def plot_class_dsODN_piechart(sizes, labels, plot_root, save_also_png=False):
+def plot_class_dsODN_piechart(sizes, labels, plot_root, save_also_png=False,**kwargs):
     fig, ax = plt.subplots(figsize=(12, 12))
     patches, texts, autotexts =ax.pie(sizes, labels=labels, autopct='%1.2f%%')
 
@@ -3496,6 +3716,7 @@ def plot_quantification_comparison_barchart(
     plot_titles,
     plot_path,
     save_also_png=False,
+    **kwargs
 ):
     fig, axs = plt.subplots(1, 2, figsize=(30, 15))
     n_groups = 2
@@ -3574,6 +3795,7 @@ def plot_quantification_positions(
     plot_title,
     plot_path,
     save_also_png=False,
+    **kwargs,
 ):
     fig, axs = plt.subplots(2, 1, figsize=(20, 10))
     ax1 = axs[0]
@@ -3714,85 +3936,3 @@ def plot_quantification_positions(
         )
 
     plt.close(fig)
-
-
-def plot_allele_modification_heatmap(
-    sample_values, sample_sgRNA_intervals, plot_path, title,
-):
-    fig = px.imshow(
-        sample_values,
-        labels={
-            'x': 'Amplicon Nucleotide (Position)',
-            'y': 'Sample (Index)',
-            'color': '{0} (%)'.format(title),
-        },
-        aspect='auto',
-    )
-    for sample_id, sgRNA_intervals in zip(
-        range(sample_values.shape[0]), sample_sgRNA_intervals,
-    ):
-        for sgRNA_interval in sgRNA_intervals:
-            fig.add_shape(
-                type='rect',
-                x0=sgRNA_interval[0],
-                y0=sample_id - 0.5,
-                x1=sgRNA_interval[1],
-                y1=sample_id + 0.5,
-                line={'color': 'Black'},
-            )
-
-    fig.update_layout(
-        autosize=True,
-    )
-    fig['layout']['yaxis']['scaleanchor'] = 'x'
-    fig['layout']['yaxis']['gridcolor'] = 'rgba(0, 0, 0, 0)'
-    fig['layout']['xaxis']['gridcolor'] = 'rgba(0, 0, 0, 0)'
-    return fig.write_html(
-        plot_path,
-        config={
-            'responsive': True,
-            'displaylogo': False,
-        },
-        include_plotlyjs='cdn',
-        full_html=False,
-        div_id='allele-modification-heatmap-{0}'.format(title.lower()),
-    )
-
-
-def plot_allele_modification_line(
-    sample_values, sample_sgRNA_intervals, plot_path, title,
-):
-    fig = px.line(sample_values.transpose())
-    sgRNA_intervals = set(
-        tuple(sgRNA_interval)
-        for sample_sgRNA_interval in sample_sgRNA_intervals
-        for sgRNA_interval in sample_sgRNA_interval
-    )
-    for sgRNA_interval in sgRNA_intervals:
-        fig.add_shape(
-            type='rect',
-            x0=sgRNA_interval[0],
-            y0=0,
-            x1=sgRNA_interval[1],
-            y1=0.5,
-            fillcolor='Gray',
-            opacity=0.2,
-            line={'color': 'gray'},
-        )
-
-    fig.update_layout(
-        autosize=True,
-        xaxis_title='Amplicon Nucleotide (Position)',
-        yaxis_title='{0} (%)'.format(title),
-        legend_title='Samples',
-    )
-    return fig.write_html(
-        plot_path,
-        config={
-            'responsive': True,
-            'displaylogo': False,
-        },
-        include_plotlyjs='cdn',
-        full_html=False,
-        div_id='allele-modification-line-{0}'.format(title.lower()),
-    )
diff --git a/CRISPResso2/CRISPRessoPooledCORE.py b/CRISPResso2/CRISPRessoPooledCORE.py
index 8cab59f9..7a5211e2 100644
--- a/CRISPResso2/CRISPRessoPooledCORE.py
+++ b/CRISPResso2/CRISPRessoPooledCORE.py
@@ -18,8 +18,8 @@
 import zipfile
 from CRISPResso2 import CRISPRessoShared
 from CRISPResso2 import CRISPRessoMultiProcessing
-from CRISPResso2 import CRISPRessoReport
-from CRISPResso2 import CRISPRessoPlot
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
+
 import traceback
 
 import logging
@@ -107,7 +107,7 @@ def summarize_region_fastq_chunk(input_arr):
     for input in input_arr:
 #        print('doing region ' + str(input))
         region_fastq, uncompressed_reference = input.split(" ")
-        #region format: REGION_chr8_1077_1198.fastq.gz
+        # region format: REGION_chr8_1077_1198.fastq.gz
         #But if the chr has underscores, it could look like this:
         #    REGION_chr8_KI270812v1_alt_1077_1198.fastq.gz
         region_info = os.path.basename(region_fastq).replace('.fastq.gz', '').replace('.fastq', '').split('_')
@@ -294,51 +294,19 @@ def main():
             ))
             sys.exit()
 
-        parser = CRISPRessoShared.getCRISPRessoArgParser(parser_title = 'CRISPRessoPooled Parameters')
-        parser.add_argument('-f', '--amplicons_file', type=str,  help='Amplicons description file. This file is a tab-delimited text file with up to 14 columns (2 required):\
-        \namplicon_name:  an identifier for the amplicon (must be unique).\
-        \namplicon_seq:  amplicon sequence used in the experiment.\
-        \nguide_seq (OPTIONAL):  sgRNA sequence used for this amplicon without the PAM sequence. Multiple guides can be given separated by commas and not spaces.\
-        \nexpected_hdr_amplicon_seq (OPTIONAL): expected amplicon sequence in case of HDR.\
-        \ncoding_seq (OPTIONAL): Subsequence(s) of the amplicon corresponding to coding sequences. If more than one separate them by commas and not spaces.\
-        \nprime_editing_pegRNA_spacer_seq (OPTIONAL): pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5\'->3\' order, so for Cas9, the PAM would be on the right side of the given sequence.\
-        \nprime_editing_nicking_guide_seq (OPTIONAL): Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5\'->3\' order, so for Cas9, the PAM would be on the right side of the sequence.\
-        \nprime_editing_pegRNA_extension_seq (OPTIONAL): Extension sequence used in prime editing. The sequence should be given in the RNA 5\'->3\' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS).\
-        \nprime_editing_pegRNA_scaffold_seq (OPTIONAL): If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as \'Scaffold-incorporated\'. The sequence should be given in the 5\'->3\' order such that the RT template directly follows this sequence. A common value ends with \'GGCACCGAGUCGGUGC\'.\
-        \nprime_editing_pegRNA_scaffold_min_match_length (OPTIONAL): Minimum number of bases matching scaffold sequence for the read to be counted as \'Scaffold-incorporated\'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences.\
-        \nprime_editing_override_prime_edited_ref_seq (OPTIONAL): If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence.\
-        \nquantification_window_coordinates (OPTIONAL): Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the "--quantification_window_center", "-- cleavage_offset", "--window_around_sgrna" or "-- window_around_sgrna" values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separated by the dash sign like "start-stop", and multiple ranges can be separated by the underscore (\_). A value of 0 disables this filter. (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 5th-10th bp in the first reference and the 5th-10th and 20th-30th bp in the second reference) (default: None)\
-        \nquantification_window_size (OPTIONAL): Defines the size (in bp) of the quantification window extending from the position specified by the "--cleavage_offset" or "--quantification_window_center" parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp.\
-        \nquantification_window_center (OPTIONAL): Center of quantification window to use within respect to the 3\' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17.', default='')
-
-        #tool specific optional
-        parser.add_argument('--gene_annotations', type=str, help='Gene Annotation Table from UCSC Genome Browser Tables (http://genome.ucsc.edu/cgi-bin/hgTables?command=start), \
-        please select as table "knownGene", as output format "all fields from selected table" and as file returned "gzip compressed"', default='')
-        # rationale for setting the default scores:
-        # --end-to-end - no clipping, match bonus -ma is set to 0
-        # -N 0 number of mismatches allowed in seed alignment
-        # --np 0 where read (or ref have ambiguous character (N)) penalty is 0
-        # -mp 3,2 mismatch penalty - set max mismatch to -3 to coincide with the gap extension penalty (2 is the default min mismatch penalty)
-        # --score-min L,-5,-3*(1-H) For a given homology score, we allow up to (1-H) mismatches (-3) or gap extensions (-3) and one gap open (-5). This score translates to -5 + -3(1-H)L where L is the sequence length
-        parser.add_argument('--bowtie2_options_string', type=str, help='Override options for the Bowtie2 alignment command. By default, this is " --end-to-end -N 0 --np 0 -mp 3,2 --score-min L,-5,-3(1-H)" where H is the default homology score.', default='')
-        parser.add_argument('--use_legacy_bowtie2_options_string', help='Use legacy (more stringent) Bowtie2 alignment parameters: " -k 1 --end-to-end -N 0 --np 0 ".', action='store_true')
-        parser.add_argument('--min_reads_to_use_region',  type=float, help='Minimum number of reads that align to a region to perform the CRISPResso analysis', default=1000)
-        parser.add_argument('--skip_failed',  help='Continue with pooled analysis even if one sample fails', action='store_true')
-        parser.add_argument('--skip_reporting_problematic_regions', help='Skip reporting of problematic regions. By default, when both amplicons (-f) and genome (-x) are provided, problematic reads that align to the genome but to positions other than where the amplicons align are reported as problematic', action='store_true')
-        parser.add_argument('--crispresso_command', help='CRISPResso command to call', default='CRISPResso')
-        parser.add_argument('--compile_postrun_references', help='If set, a file will be produced which compiles the reference sequences of frequent amplicons.', action='store_true')
-        parser.add_argument('--compile_postrun_reference_allele_cutoff', type=float, help='Only alleles with at least this percentage frequency in the population will be reported in the postrun analysis. This parameter is given as a percent, so 30 is 30%%.', default=30)
-        parser.add_argument('--alternate_alleles', type=str, help='Path to tab-separated file with alternate allele sequences for pooled experiments. This file has the columns "region_name","reference_seqs", and "reference_names" and gives the reference sequences of alternate alleles that will be passed to CRISPResso for each individual region for allelic analysis. Multiple reference alleles and reference names for a given region name are separated by commas (no spaces).', default='')
-        parser.add_argument('--limit_open_files_for_demux', help='If set, only one file will be opened during demultiplexing of read alignment locations. This will be slightly slower as the reads must be sorted, but may be necessary if the number of amplicons is greater than the number of files that can be opened due to OS constraints.', action='store_true')
-        parser.add_argument('--aligned_pooled_bam', type=str, help='Path to aligned input for CRISPRessoPooled processing. If this parameter is specified, the alignments in the given bam will be used to demultiplex reads. If this parameter is not set (default), input reads provided by --fastq_r1 (and optionally --fastq_r2) will be aligned to the reference genome using bowtie2. If the input bam is given, the corresponding reference fasta must also be given to extract reference genomic sequences via the parameter --bowtie2_index. Note that if the aligned reads are paired-end sequenced, they should already be merged into 1 read (e.g. via Flash) before alignment.', default=None)
-        parser.add_argument('--demultiplex_only_at_amplicons', help='If set, and an amplicon file (--amplicons_file) and reference sequence (--bowtie2_index) are provided, reads overlapping alignment positions of amplicons will be demultiplexed and assigned to that amplicon. If this flag is not set, the entire genome will be demultiplexed and reads with the same start and stop coordinates as an amplicon will be assigned to that amplicon.', action='store_true')
+        parser = CRISPRessoShared.getCRISPRessoArgParser("Pooled", parser_title = 'CRISPRessoPooled Parameters')
 
         args = parser.parse_args()
 
+        if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
 
-        crispresso_options = CRISPRessoShared.get_crispresso_options()
-        options_to_ignore = {'fastq_r1', 'fastq_r2', 'amplicon_seq', 'amplicon_name', 'output_folder', 'name', 'zip_output'}
+        crispresso_options = CRISPRessoShared.get_core_crispresso_options()
+        options_to_ignore = {'fastq_r1', 'fastq_r2', 'amplicon_seq', 'amplicon_name', 'output_folder', 'name', 'zip_output', 'split_interleaved_input'}
         crispresso_options_for_pooled = list(crispresso_options-options_to_ignore)
 
         files_to_remove = []
@@ -359,7 +327,7 @@ def main():
 
         log_filename = _jp('CRISPRessoPooled_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoPooled_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoPooled_status.json')))
 
         if args.zip_output and not args.place_report_in_output_folder:
             logger.warn('Invalid arguement combination: If zip_output is True then place_report_in_output_folder must also be True. Setting place_report_in_output_folder to True.')
@@ -410,14 +378,14 @@ def main():
 
         if args.amplicons_file and not args.bowtie2_index:
             RUNNING_MODE='ONLY_AMPLICONS'
-            info('Only the Amplicon description file was provided. The analysis will be perfomed using only the provided amplicons sequences.')
+            info('Only the amplicon description file was provided. The analysis will be performed using only the provided amplicon sequences.')
 
         elif args.bowtie2_index and not args.amplicons_file:
             RUNNING_MODE='ONLY_GENOME'
-            info('Only the bowtie2 reference genome index file was provided. The analysis will be perfomed using only genomic regions where enough reads align.')
+            info('Only the bowtie2 reference genome index file was provided. The analysis will be performed using only genomic regions with sufficient read alignment depth.')
         elif args.bowtie2_index and args.amplicons_file:
             RUNNING_MODE='AMPLICONS_AND_GENOME'
-            info('Amplicon description file and bowtie2 reference genome index files provided. The analysis will be perfomed using the reads that are aligned only to the amplicons provided and not to other genomic regions.')
+            info('Amplicon description file and bowtie2 reference genome index files provided. The analysis will be performed using the reads that are aligned to the genome only where the provided amplicons align and not to other genomic regions.')
         else:
             error('Please provide the amplicons description file (-f or --amplicons_file option) or the bowtie2 reference genome index file (-x or --bowtie2_index option) or both.')
             sys.exit(1)
@@ -511,6 +479,15 @@ def main():
 
         info('Processing input', {'percent_complete': 5})
 
+        if args.split_interleaved_input:
+            info('Splitting paired end single fastq file into two files...')
+            args.fastq_r1, args.fastq_r2 = CRISPRessoShared.split_interleaved_fastq(
+                args.fastq_r1,
+                output_filename_r1=_jp('{0}_splitted_r1.fastq.gz'.format(os.path.basename(args.fastq_r1).replace('.fastq', '').replace('.gz', ''))),
+                output_filename_r2=_jp('{0}_splitted_r2.fastq.gz'.format(os.path.basename(args.fastq_r1).replace('.fastq', '').replace('.gz', ''))),
+            )
+            files_to_remove += [args.fastq_r1, args.fastq_r2]
+
         # perform read trimming if necessary
         if args.aligned_pooled_bam is not None:
             # don't trim reads in aligned bams
@@ -525,102 +502,125 @@ def main():
                 output_forward_filename = symlink_filename
             else:
                 output_forward_filename = _jp('reads.trimmed.fq.gz')
-                # Trimming with trimmomatic
-                info('Trimming sequences with Trimmomatic...', {'percent_complete': 7})
-                cmd = '%s SE -phred33 %s %s %s >>%s 2>&1'\
-                    % (args.trimmomatic_command, args.fastq_r1,
-                        output_forward_filename,
-                        args.trimmomatic_options_string,
-                        log_filename)
-                # print cmd
-                TRIMMOMATIC_STATUS = sb.call(cmd, shell=True)
-
-                if TRIMMOMATIC_STATUS:
-                    raise TrimmomaticException('TRIMMOMATIC failed to run, please check the log file.')
+                if can_finish_incomplete_run and 'trim_input' in crispresso2_info['running_info']['finished_steps']:
+                    info('Using previously-trimmed input sequences...')
+                    trim_cmd = crispresso2_info['running_info']['finished_steps']['trim_input']
+                else:
+                    info('Trimming sequences with fastp...')
+                    trim_cmd = '{command} -i {r1} -o {out} {options} --json {json_report} --html {html_report} >> {log} 2>&1'.format(
+                        command=args.fastp_command,
+                        r1=args.fastq_r1,
+                        out=output_forward_filename,
+                        options=args.fastp_options_string,
+                        json_report=_jp('fastp_report.json'),
+                        html_report=_jp('fastp_report.html'),
+                        log=log_filename,
+                    )
+                    fastp_status = sb.call(trim_cmd, shell=True)
+
+                    if fastp_status:
+                        raise CRISPRessoShared.FastpException('FASTP failed to run, please check the log file.')
+
+                    crispresso2_info['running_info']['finished_steps']['trim_input'] = trim_cmd
+
+                if not args.keep_intermediate:
+                    files_to_remove += [output_forward_filename]
+
+                crispresso2_info['running_info']['fastp_trim_command'] = trim_cmd
+                CRISPRessoShared.write_crispresso_info(
+                    crispresso2_info_file, crispresso2_info
+                )
+
+                info('Done!', {'percent_complete': 7})
 
             processed_output_filename = output_forward_filename
 
         else:  # paired end reads case
             if not args.trim_sequences:
-                output_forward_paired_filename = args.fastq_r1
-                output_reverse_paired_filename = args.fastq_r2
+                args.fastp_options_string += ' --disable_adapter_trimming --disable_trim_poly_g --disable_quality_filtering --disable_length_filtering'
             else:
-                info('Trimming sequences with Trimmomatic...', {'percent_complete': 7})
-                output_forward_paired_filename = _jp('output_forward_paired.fq.gz')
-                output_forward_unpaired_filename = _jp('output_forward_unpaired.fq.gz')
-                output_reverse_paired_filename = _jp('output_reverse_paired.fq.gz')
-                output_reverse_unpaired_filename = _jp('output_reverse_unpaired.fq.gz')
-
-                # Trimming with trimmomatic
-                cmd = '%s PE -phred33 %s  %s %s  %s  %s  %s %s >>%s 2>&1'\
-                    % (args.trimmomatic_command,
-                        args.fastq_r1, args.fastq_r2, output_forward_paired_filename,
-                        output_forward_unpaired_filename, output_reverse_paired_filename,
-                        output_reverse_unpaired_filename, args.trimmomatic_options_string, log_filename)
-                # print cmd
-                TRIMMOMATIC_STATUS = sb.call(cmd, shell=True)
-                if TRIMMOMATIC_STATUS:
-                    raise TrimmomaticException('TRIMMOMATIC failed to run, please check the log file.')
-
-                info('Done!')
-
-            max_overlap_string = ""
-            min_overlap_string = ""
-            if args.max_paired_end_reads_overlap:
-                max_overlap_string = "--max-overlap " + str(args.max_paired_end_reads_overlap)
-            if args.min_paired_end_reads_overlap:
-                min_overlap_string = "--min-overlap " + str(args.min_paired_end_reads_overlap)
-            # Merging with Flash
-            info('Merging paired sequences with Flash...', {'percent_complete': 10})
-            cmd = args.flash_command+' --allow-outies %s %s %s %s -z -d %s >>%s 2>&1' %\
-                (output_forward_paired_filename,
-                 output_reverse_paired_filename,
-                 max_overlap_string,
-                 min_overlap_string,
-                 OUTPUT_DIRECTORY, log_filename)
+                args.fastp_options_string += ' --detect_adapter_for_pe'
 
-            if args.debug:
-                info('Flash command: %s'%cmd)
+            processed_output_filename = _jp('out.extendedFrags.fastq.gz')
+            not_combined_1_filename = _jp('out.notCombined_1.fastq.gz')
+            not_combined_2_filename = _jp('out.notCombined_2.fastq.gz')
+
+
+            if can_finish_incomplete_run and 'merge_paired_fastq' in crispresso2_info['running_info']['finished_steps']:
+                info('Using previously-merged paired sequences...')
+                fastp_cmd = crispresso2_info['running_info']['finished_steps']['merge_paired_fastq']
+            else:
+                info('Merging paired sequences with fastp...')
+                fastp_cmd = '{command} -i {r1} -I {r2} --merge --merged_out {out_merged} --unpaired1 {unpaired1} --unpaired2 {unpaired2} --overlap_len_require {min_overlap} --thread {num_threads} --json {json_report} --html {html_report} {options} >> {log} 2>&1'.format(
+                    command=args.fastp_command,
+                    r1=args.fastq_r1,
+                    r2=args.fastq_r2,
+                    out_merged=processed_output_filename,
+                    unpaired1=not_combined_1_filename,
+                    unpaired2=not_combined_2_filename,
+                    min_overlap=args.min_paired_end_reads_overlap,
+                    num_threads=n_processes_for_pooled,
+                    json_report=_jp('fastp_report.json'),
+                    html_report=_jp('fastp_report.html'),
+                    options=args.fastp_options_string,
+                    log=log_filename,
+                )
 
-            FLASH_STATUS = sb.call(cmd, shell=True)
-            if FLASH_STATUS:
-                raise FlashException('Flash failed to run, please check the log file.')
+                if args.debug:
+                    info('Fastp command: {0}'.format(fastp_cmd))
 
-            flash_hist_filename = _jp('out.hist')
-            flash_histogram_filename = _jp('out.histogram')
-            flash_not_combined_1_filename = _jp('out.notCombined_1.fastq.gz')
-            flash_not_combined_2_filename = _jp('out.notCombined_2.fastq.gz')
+                fastp_status = sb.call(fastp_cmd, shell=True)
+
+                if fastp_status:
+                    raise CRISPRessoShared.FastpException('Fastp failed to run, please check the log file.')
+
+                crispresso2_info['running_info']['finished_steps']['merge_paired_fastq'] = fastp_cmd
+
+            crispresso2_info['running_info']['fastp_command'] = fastp_cmd
+            CRISPRessoShared.write_crispresso_info(
+                crispresso2_info_file, crispresso2_info
+            )
+
+            if not args.keep_intermediate:
+                files_to_remove += [processed_output_filename, not_combined_1_filename, not_combined_2_filename]
 
-            processed_output_filename = _jp('out.extendedFrags.fastq.gz')
 
             if args.force_merge_pairs:
-                old_flashed_filename = processed_output_filename
                 new_merged_filename = _jp('out.forcemerged_uncombined.fastq.gz')
-                num_reads_force_merged = CRISPRessoShared.force_merge_pairs(flash_not_combined_1_filename, flash_not_combined_2_filename, new_merged_filename)
+                num_reads_force_merged = CRISPRessoShared.force_merge_pairs(not_combined_1_filename, not_combined_2_filename, new_merged_filename)
                 new_output_filename = _jp('out.forcemerged.fastq.gz')
-                merge_command = "cat %s %s > %s"%(processed_output_filename, new_merged_filename, new_output_filename)
-                MERGE_STATUS = sb.call(merge_command, shell=True)
-                if MERGE_STATUS:
-                    raise FlashException('Force-merging read pairs failed to run, please check the log file.')
+                merge_command = "cat {0} {1} > {2}".format(processed_output_filename, new_merged_filename, new_output_filename)
+                merge_status = sb.call(merge_command, shell=True)
+                if merge_status:
+                    raise CRISPRessoShared.FastpException('Force-merging read pairs failed to run, please check the log file.')
+                else:
+                    info(f'Forced {num_reads_force_merged} read pairs together.')
                 processed_output_filename = new_output_filename
 
-            info('Done!')
+                if not args.keep_intermediate:
+                    files_to_remove += [new_merged_filename, new_output_filename]
+
+            info('Done!', {'percent_complete': 7})
 
         if can_finish_incomplete_run and 'count_input_reads' in crispresso2_info['running_info']['finished_steps']:
             (N_READS_INPUT, N_READS_AFTER_PREPROCESSING) = crispresso2_info['running_info']['finished_steps']['count_input_reads']
         # count reads
         else:
+            info('Counting input reads...')
             if args.aligned_pooled_bam is not None:
                 N_READS_INPUT = get_n_reads_bam(args.aligned_pooled_bam)
                 N_READS_AFTER_PREPROCESSING = N_READS_INPUT
             else:
                 N_READS_INPUT = get_n_reads_fastq(args.fastq_r1)
+                if args.split_interleaved_input:
+                    N_READS_INPUT /= 2
                 N_READS_AFTER_PREPROCESSING = get_n_reads_fastq(processed_output_filename)
 
             crispresso2_info['running_info']['finished_steps']['count_input_reads'] = (N_READS_INPUT, N_READS_AFTER_PREPROCESSING)
             CRISPRessoShared.write_crispresso_info(
                 crispresso2_info_file, crispresso2_info
             )
+            info('Done!', {'percent_complete': 8})
 
         # load gene annotation
         if args.gene_annotations:
@@ -645,11 +645,13 @@ def main():
             with open(args.amplicons_file, 'r') as amplicons_fin:
 
                 head_line = amplicons_fin.readline().strip()
+                if head_line == "":
+                    raise CRISPRessoShared.BadParameterException('Cannot parse header from amplicon file ' + args.amplicons_file)
                 while head_line[0] == "#":  # read past comments
                     head_line = amplicons_fin.readline()
                 header_els = head_line.split('\t')
 
-            head_lookup = CRISPRessoShared.get_crispresso_options_lookup()  # dict of qwc -> quantification_window_coordinates
+            head_lookup = CRISPRessoShared.get_crispresso_options_lookup("Core")  # dict of qwc -> quantification_window_coordinates
 
             # add legacy CRISPRessoPooled headers to the head_lookup
             # lowercase input header names for matching - they'll get fixed in the matching to default_input_amplicon_headers
@@ -663,8 +665,8 @@ def main():
             lowercase_default_amplicon_headers = {h.lower(): h for h in default_input_amplicon_headers}
 
             headers = []
+            unmatched_headers = []
             has_header = False
-            has_unmatched_header_el = False
             for head in header_els:
                 # Header based on header provided
                 # Look up long name (e.g. qwc -> quantification_window_coordinates)
@@ -678,21 +680,23 @@ def main():
 
                 match = difflib.get_close_matches(long_head, lowercase_default_amplicon_headers, n=1)
                 if not match:
-                    has_unmatched_header_el = True
-                    warn(f'Unable to find matches for header value "{head}". Using the default header values and order.')
+                    unmatched_headers.append(head)
                 else:
                     has_header = True
                     headers.append(lowercase_default_amplicon_headers[match[0]])
                     if args.debug:
                         info(f'Matching header {head} with {lowercase_default_amplicon_headers[match[0]]}.')
 
-            if not has_header or has_unmatched_header_el:
+            if len(headers) > 5 and not has_header:
+                raise CRISPRessoShared.BadParameterException('Incorrect number of columns provided without header.')
+            elif has_header and len(unmatched_headers) > 0:
+                raise CRISPRessoShared.BadParameterException('Unable to match headers: ' + str(unmatched_headers))
+
+            if not has_header:
                 # Default header
                 headers = []
                 for i in range(len(header_els)):
                     headers.append(default_input_amplicon_headers[i])
-                if len(headers) > 5:
-                    raise CRISPRessoShared.BadParameterException('Incorrect number of columns provided without header.')
 
             if args.debug:
                 info(f'Header variable names in order: {headers}')
@@ -722,7 +726,7 @@ def main():
                 raise Exception('The amplicon sequences must be distinct! (Duplicated entries: ' + str(duplicated_entries.values) + ')')
 
             if not len(df_template.amplicon_name.unique())==df_template.shape[0]:
-                duplicated_entries = df_template.amplicon_name[df_template.Name.duplicated()]
+                duplicated_entries = df_template.amplicon_name[df_template.amplicon_name.duplicated()]
                 raise Exception('The amplicon names must be distinct! (Duplicated names: ' + str(duplicated_entries.values) + ')')
 
             df_template=df_template.set_index('amplicon_name')
@@ -878,8 +882,24 @@ def main():
 
                 else:
                     warn('Skipping amplicon [%s] because no reads align to it\n'% idx)
-
             CRISPRessoMultiProcessing.run_crispresso_cmds(crispresso_cmds, n_processes_for_pooled, 'amplicon', args.skip_failed, start_end_percent=(16, 80))
+            # Initialize array to track failed runs
+            failed_batch_arr = []
+            failed_batch_arr_desc = []
+            for cmd in crispresso_cmds:
+
+                # Extract the folder name from the CRISPResso command
+                folder_name_regex = re.search(r'-o\s+\S+\s+--name\s+(\S+)', cmd)
+                if folder_name_regex:
+                    folder_name = os.path.join(OUTPUT_DIRECTORY, 'CRISPResso_on_%s' % folder_name_regex.group(1))
+                    failed_run_bool, failed_status_string = CRISPRessoShared.check_if_failed_run(folder_name, info)
+                    if failed_run_bool:
+                        failed_batch_arr.append(folder_name_regex.group(1))
+                        failed_batch_arr_desc.append(failed_status_string)
+
+            # Store the failed runs in crispresso2_info for later use
+            crispresso2_info['results']['failed_batch_arr'] = failed_batch_arr
+            crispresso2_info['results']['failed_batch_arr_desc'] = failed_batch_arr_desc
 
             df_template['n_reads']=n_reads_aligned_amplicons
             df_template['n_reads_aligned_%']=df_template['n_reads']/float(N_READS_ALIGNED)*100
@@ -1040,7 +1060,7 @@ def rreplace(s, old, new):
                 os.mkdir(MAPPED_REGIONS)
 
                 # if we should only demultiplex where amplicons aligned... (as opposed to the whole genome)
-                if RUNNING_MODE=='AMPLICONS_AND_GENOME' and args.demultiplex_only_at_amplicons:
+                if RUNNING_MODE=='AMPLICONS_AND_GENOME' and not args.demultiplex_genome_wide:
                     s1 = r'''samtools view -F 0x0004 %s __REGIONCHR__:__REGIONSTART__-__REGIONEND__ 2>>%s |''' % (bam_filename_genome, log_filename)+\
                     r'''awk 'BEGIN{OFS="\t";num_records=0;fastq_filename="__OUTPUTPATH__REGION___REGIONCHR_____REGIONSTART_____REGIONEND__.fastq";} \
                         { \
@@ -1052,7 +1072,7 @@ def rreplace(s, old, new):
                       system("gzip -f "fastq_filename);  \
                       record_log_str = "__REGIONCHR__\t__REGIONSTART__\t__REGIONEND__\t"num_records"\t"fastq_filename".gz\n"; \
                       print record_log_str > "__DEMUX_CHR_LOGFILENAME__"; \
-                    } '''
+                    } ' '''
                     cmd = (s1).replace('__OUTPUTPATH__', MAPPED_REGIONS)
                     cmd = cmd.replace("__MIN_READS__", str(args.min_reads_to_use_region))
                     with open(REPORT_ALL_DEPTH, 'w') as f:
@@ -1149,13 +1169,13 @@ def rreplace(s, old, new):
                                 n_reads_at_end = get_n_aligned_bam_region(bam_filename_genome, chr_str, curr_end-5, curr_end+5)
                                 while n_reads_at_end > 0:
                                     curr_end += 500  # look for another place with no reads
-                                    if curr_end >= curr_pos:
+                                    if curr_end >= chr_len:
                                         curr_end = chr_len
                                         break
                                     n_reads_at_end = get_n_aligned_bam_region(bam_filename_genome, chr_str, curr_end-5, curr_end+5)
 
-                                sub_chr_command = chr_cmd.replace("__REGION__", ":%d-%d "%(curr_pos, curr_end))
-                                chr_output_filename = _jp('MAPPED_REGIONS/%s_%s_%s.info' % (chr_str, curr_pos, chr_end))
+                                chr_output_filename = _jp('MAPPED_REGIONS/%s_%s_%s.info' % (chr_str, curr_pos, curr_end))
+                                sub_chr_command = chr_cmd.replace("__REGION__", ":%d-%d "%(curr_pos, curr_end)).replace("__DEMUX_CHR_LOGFILENAME__",chr_output_filename)
                                 chr_commands.append(sub_chr_command)
                                 chr_output_filenames.append(chr_output_filename)
                                 curr_pos = curr_end
@@ -1572,20 +1592,6 @@ def default_sigpipe():
         if not args.keep_intermediate:
              info('Removing Intermediate files...')
 
-             if not args.aligned_pooled_bam:
-                if args.fastq_r2!='':
-                    files_to_remove+=[processed_output_filename, flash_hist_filename, flash_histogram_filename,\
-                                flash_not_combined_1_filename, flash_not_combined_2_filename]
-                    if args.force_merge_pairs:
-                        files_to_remove.append(new_merged_filename)
-                        files_to_remove.append(old_flashed_filename)
-                else:
-                    files_to_remove+=[processed_output_filename]
-
-             if args.trim_sequences and args.fastq_r2!='':
-                 files_to_remove+=[output_forward_paired_filename, output_reverse_paired_filename,\
-                                                   output_forward_unpaired_filename, output_reverse_unpaired_filename]
-
              if RUNNING_MODE=='ONLY_GENOME' or RUNNING_MODE=='AMPLICONS_AND_GENOME':
                  if args.aligned_pooled_bam is None:
                      files_to_remove+=[bam_filename_genome]
@@ -1611,7 +1617,7 @@ def default_sigpipe():
                 report_name = _jp("CRISPResso2Pooled_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_pooled_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_pooled_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
@@ -1634,15 +1640,15 @@ def default_sigpipe():
                 this_freqs = []
                 this_names = []
                 with zipfile.ZipFile(allele_frequency_table_zip_filename, 'r') as archive:
-                    with archive.open(run_data['allele_frequency_table_filename'], 'r') as f:
-                        head = f.readline()
+                    with archive.open(run_data['running_info']['allele_frequency_table_filename'], 'r') as f:
+                        head = f.readline().decode('UTF-8')
                         head_els = head.rstrip().split("\t")
                         allele_ind = head_els.index('Aligned_Sequence')
                         freq_ind = head_els.index('%Reads')
 
                         new_allele_idx = 1
                         for line in f:
-                            line_els = line.split('\t')
+                            line_els = line.decode('UTF-8').split('\t')
                             allele_seq = line_els[allele_ind].replace('-', '')
                             allele_freq = float(line_els[freq_ind])
                             #add first allele -- then add other alleles if they are more frequent than the cutoff
diff --git a/CRISPResso2/CRISPRessoPooledWGSCompareCORE.py b/CRISPResso2/CRISPRessoPooledWGSCompareCORE.py
index 15f28624..538afc9f 100644
--- a/CRISPResso2/CRISPRessoPooledWGSCompareCORE.py
+++ b/CRISPResso2/CRISPRessoPooledWGSCompareCORE.py
@@ -11,7 +11,7 @@
 import sys
 from CRISPResso2 import CRISPRessoShared
 from CRISPResso2 import CRISPRessoMultiProcessing
-from CRISPResso2 import CRISPRessoReport
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 import traceback
 
 
@@ -231,7 +231,7 @@ def main():
 
         log_filename = _jp('CRISPRessoPooledWGSCompare_RUNNING_LOG.txt')
         logger.addHandler(logging.FileHandler(log_filename))
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoPooledWGSCompare_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoPooledWGSCompare_status.json')))
 
         with open(log_filename, 'w+') as outfile:
             outfile.write(
@@ -354,8 +354,12 @@ def main():
                 report_name = _jp("CRISPResso2PooledWGSCompare_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
+            empty_failed_runs_arr = []
+            empty_failed_runs_arr_desc = []
             CRISPRessoReport.make_multi_report(
                 processed_regions,
+                empty_failed_runs_arr,
+                empty_failed_runs_arr_desc,
                 processed_region_html_files,
                 report_name,
                 OUTPUT_DIRECTORY,
@@ -363,6 +367,7 @@ def main():
                 'CRISPREssoPooledWGSCompare Report<br>{0} vs {1}'.format(
                     sample_1_name, sample_2_name,
                 ),
+                logger,
             )
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
diff --git a/CRISPResso2/CRISPRessoReports/.gitattributes b/CRISPResso2/CRISPRessoReports/.gitattributes
new file mode 100644
index 00000000..d68b3d4c
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/.gitattributes
@@ -0,0 +1,2 @@
+*.html text eol=lf
+*.md text eol=lf
diff --git a/CRISPResso2/CRISPRessoReports/.github/workflows/.pylintrc b/CRISPResso2/CRISPRessoReports/.github/workflows/.pylintrc
new file mode 100644
index 00000000..1b892797
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/.github/workflows/.pylintrc
@@ -0,0 +1,7 @@
+[FORMAT]
+max-line-length=150
+max-args=15
+max-locals=40
+
+[MESSAGES CONTROL]
+disable = E0401, W0719
diff --git a/CRISPResso2/CRISPRessoReports/.github/workflows/pylint.yml b/CRISPResso2/CRISPRessoReports/.github/workflows/pylint.yml
new file mode 100644
index 00000000..5b7e7e11
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/.github/workflows/pylint.yml
@@ -0,0 +1,26 @@
+name: Pylint
+
+on:
+  push:
+    branches:
+      - '*'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.10"]
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install pylint
+    - name: Analysing the code with pylint
+      run: |
+        pylint --fail-under=9 $(git ls-files '*.py') --rcfile=/home/runner/work/CRISPRessoReports/CRISPRessoReports/.github/workflows/.pylintrc
diff --git a/CRISPResso2/CRISPRessoReport.py b/CRISPResso2/CRISPRessoReports/CRISPRessoReport.py
similarity index 60%
rename from CRISPResso2/CRISPRessoReport.py
rename to CRISPResso2/CRISPRessoReports/CRISPRessoReport.py
index 2b21db07..b9b9faeb 100644
--- a/CRISPResso2/CRISPRessoReport.py
+++ b/CRISPResso2/CRISPRessoReports/CRISPRessoReport.py
@@ -5,140 +5,222 @@
 '''
 
 import os
-from jinja2 import Environment, FileSystemLoader
+from jinja2 import Environment, FileSystemLoader, ChoiceLoader, make_logging_undefined
+from CRISPResso2.CRISPRessoReports.jinja_partials import generate_render_partial, render_partial
 from CRISPResso2 import CRISPRessoShared
 
+if CRISPRessoShared.is_C2Pro_installed():
+    from CRISPRessoPro import __version__ as CRISPRessoProVersion
+    import CRISPRessoPro
+    C2PRO_INSTALLED = True
+else:
+    C2PRO_INSTALLED = False
 
-def make_report_from_folder(crispresso_report_file, crispresso_folder, _ROOT):
+
+def get_jinja_loader(root, logger):
+    """
+    Get the Jinja2 environment for rendering templates.
+    """
+    undefined_logger = make_logging_undefined(logger=logger)
+    if C2PRO_INSTALLED:
+        return Environment(
+            loader=ChoiceLoader([
+                FileSystemLoader(os.path.join(root, 'CRISPRessoReports', 'templates')),
+                FileSystemLoader(os.path.join(os.path.dirname(CRISPRessoPro.__file__), 'templates')),
+            ]),
+            undefined=undefined_logger,
+        )
+    return Environment(
+        loader=FileSystemLoader(os.path.join(root, 'CRISPRessoReports', 'templates')),
+        undefined=undefined_logger,
+    )
+
+
+def render_template(template_name, jinja2_env, **data):
+    """Render a template with partials.
+
+    Parameters
+    ----------
+    template_name: str
+        The name of the template to render. For example, if you have a template
+        file called `templates/my_template.html` you would pass in
+        `my_template.html`.
+    jinja2_env: jinja2.Environment
+        The Jinja2 environment being used.
+    **data: keyword arguments of any type
+        Additional keyword arguments that are passed to the template.
+
+    Returns
+    -------
+    The rendered template.
+    """
+    def custom_partial_render(partial_template_name, **partial_data):
+        template = jinja2_env.get_template(partial_template_name)
+        partial_data.update(
+            render_partial=generate_render_partial(
+                custom_partial_render,
+            ),
+            is_default_user=False,
+            is_web=False,
+            C2PRO_INSTALLED=C2PRO_INSTALLED,
+        )
+        return template.render(**partial_data)
+    return render_partial(
+        template_name, custom_partial_render, **data,
+    )
+
+
+def make_report_from_folder(crispresso_report_file, crispresso_folder, _root):
     """
     Makes an html report for a crispresso run
 
     Parameters:
     crispresso_report_file (string): name of the html file to create
     crispresso_folder (string): path to the crispresso output
-    _ROOT (string): path to crispresso executables (for templates)
+    _root (string): path to crispresso executables (for templates)
 
     Returns:
     Nothin
     """
     run_data = CRISPRessoShared.load_crispresso_info(crispresso_folder)
-    make_report(run_data, crispresso_report_file, crispresso_folder, _ROOT)
-
-def make_report(run_data, crispresso_report_file, crispresso_folder, _ROOT):
-
-    #dicts for each amplicon fig_names[amp_name] = [list of fig names]
-    #                        fig_locs[amp_name][fig_name] = figure location
-    fig_names = {} #all except for the figure 1 (which is common to all amplicons)
-    fig_locs = {}
-    fig_titles = {}
-    fig_captions = {}
-    fig_datas = {}
-    sgRNA_based_fig_names = {}
-#    print('crispresso_report file: ' + crispresso_report_file + ' crispresso_folder : ' + crispresso_folder + ' root: ' + _ROOT)
-
-    def add_fig_if_exists(fig_name, fig_root, fig_title, fig_caption, fig_data,
-        amplicon_fig_names, amplicon_fig_locs, amplicon_fig_titles, amplicon_fig_captions, amplicon_fig_datas):
-            """
-            Helper function to add figure if the file exists
-            if fig at filename exists,
-            amplicon_figs[figname] is set to that file
-            """
-            #fullpath=os.path.join(crispresso_folder,fig_root+'.png')
-            fullpath=os.path.join(crispresso_folder, fig_root+'.png')
-#            print('adding file ' + fig_root + ' at ' + fullpath)
-            if os.path.exists(fullpath):
-                amplicon_fig_names.append(fig_name)
-                #amplicon_fig_locs[fig_name]=os.path.basename(fig_root+'.png')
-                amplicon_fig_locs[fig_name]=os.path.basename(fig_root)
-                amplicon_fig_titles[fig_name] = fig_title
-                amplicon_fig_captions[fig_name] = fig_caption
-                amplicon_fig_datas[fig_name] = []
-                for (data_caption, data_file) in fig_data:
-                    if os.path.exists(os.path.join(crispresso_folder, data_file)):
-                        amplicon_fig_datas[fig_name].append((data_caption, data_file))
-
-    global_fig_names= []
+    make_report(run_data, crispresso_report_file, crispresso_folder, _root)
+
+
+def add_fig_if_exists(fig, fig_name, fig_root, fig_title, fig_caption, fig_data,
+                      amplicon_fig_names, amplicon_figures, crispresso_folder, d3_nuc_quilt_names):
+    """
+        Helper function to add figure if the file exists
+        if fig at filename exists,
+        amplicon_figs[figname] is set to that file
+        """
+    # fullpath=os.path.join(crispresso_folder,fig_root+'.png')
+    pngfullpath = os.path.join(crispresso_folder, fig_root + '.png')
+    htmlfullpath = os.path.join(crispresso_folder, fig_root + '.html')
+    jsonfullpath = os.path.join(crispresso_folder, f'plot_{fig_root}.json')
+    #            print('adding file ' + fig_root + ' at ' + fullpath)
+    if os.path.exists(pngfullpath) or os.path.exists(htmlfullpath) or os.path.exists(jsonfullpath):
+        amplicon_fig_names.append(fig_name)
+        # amplicon_fig_locs[fig_name]=os.path.basename(fig_root+'.png')
+        amplicon_figures['locs'][fig_name] = os.path.basename(fig_root)
+        amplicon_figures['titles'][fig_name] = fig_title
+        amplicon_figures['captions'][fig_name] = fig_caption
+        amplicon_figures['datas'][fig_name] = []
+        for (data_caption, data_file) in fig_data:
+            if os.path.exists(os.path.join(crispresso_folder, data_file)):
+                amplicon_figures['datas'][fig_name].append((data_caption, data_file))
+        if os.path.exists(htmlfullpath):
+            with open(htmlfullpath, encoding='utf-8') as html:
+                html_string = "<div align='center'>"
+                html_string += html.read()
+                html_string += "</div>"
+            amplicon_figures['htmls'][fig_name] = html_string
+        elif os.path.exists(jsonfullpath) and C2PRO_INSTALLED:
+            root_name = fig_root.replace('.', '_').replace('-', '_')
+            d3_nuc_quilt_names.append(f"nuc_quilt_{root_name}")
+            with open(jsonfullpath, encoding='utf-8') as fig_json_fh:
+                amplicon_figures['htmls'][fig_name] = f"""
+                <div class="d-flex justify-content-between" style="max-height: 80vh; overflow-y: auto;" id="{f"nuc_quilt_{root_name}"}"></div>
+                <script type="text/javascript">const {f"nuc_quilt_{root_name}"} = {fig_json_fh.read().strip()}</script>
+                    """
+
+
+def assemble_figs(run_data, crispresso_folder):
+    """
+        Helper function create the data structre for the figures
+    """
+    figures = {'names': {}, 'locs': {}, 'titles': {}, 'captions': {}, 'datas': {}, 'htmls': {}, 'sgRNA_based_names': {}}
+    d3_nuc_quilt_names = []
+
+    global_fig_names = []
     for fig in ['1a', '1b', '1c', '1d', '5a', '6a', '8a', '11c']:
-        fig_name = 'plot_'+ fig
+        fig_name = 'plot_' + fig
         if fig_name + '_root' in run_data['results']['general_plots']:
-            add_fig_if_exists(fig_name, run_data['results']['general_plots'][fig_name + '_root'], 'Figure ' + fig, run_data['results']['general_plots'][fig_name + '_caption'], run_data['results']['general_plots'][fig_name+'_data'],
-                global_fig_names, fig_locs, fig_titles, fig_captions, fig_datas)
-
+            add_fig_if_exists(fig, fig_name, run_data['results']['general_plots'][fig_name + '_root'], 'Figure ' + fig,
+                              run_data['results']['general_plots'][fig_name + '_caption'],
+                              run_data['results']['general_plots'][fig_name + '_data'],
+                              global_fig_names, figures, crispresso_folder, d3_nuc_quilt_names)
 
     amplicons = []
     for amplicon_name in run_data['results']['ref_names']:
         amplicons.append(amplicon_name)
-        amplicon_fig_names = []
-        amplicon_fig_locs = {}
-        amplicon_fig_titles = {}
-        amplicon_fig_captions = {}
-        amplicon_fig_datas = {}
-
+        amplicon_figures = {'names': [], 'locs': {}, 'titles': {}, 'captions': {}, 'datas': {}, 'htmls': {}}
 
-
-        for fig in ['2a', '3a', '3b', '4a', '4b', '4c', '4d', '4e', '4f', '4g', '5', '6', '7', '8', '10a', '10b', '10c', '11a']:
-            fig_name = 'plot_'+ fig
+        for fig in ['2a', '3a', '3b', '4a', '4b', '4c', '4d', '4e', '4f', '4g', '5', '6', '7', '8', '10a', '10b', '10c',
+                    '11a']:
+            fig_name = 'plot_' + fig
             if fig_name + '_root' in run_data['results']['refs'][amplicon_name]:
-                add_fig_if_exists(fig_name, run_data['results']['refs'][amplicon_name][fig_name + '_root'], 'Figure ' + fig_name, run_data['results']['refs'][amplicon_name][fig_name + '_caption'], run_data['results']['refs'][amplicon_name][fig_name + '_data'],
-                        amplicon_fig_names, amplicon_fig_locs, amplicon_fig_titles, amplicon_fig_captions, amplicon_fig_datas)
+                add_fig_if_exists(fig, fig_name, run_data['results']['refs'][amplicon_name][fig_name + '_root'],
+                                  'Figure ' + fig_name,
+                                  run_data['results']['refs'][amplicon_name][fig_name + '_caption'],
+                                  run_data['results']['refs'][amplicon_name][fig_name + '_data'],
+                                  global_fig_names, amplicon_figures, crispresso_folder, d3_nuc_quilt_names)
 
         this_sgRNA_based_fig_names = {}
         for fig in ['2b', '9', '10d', '10e', '10f', '10g', '11b']:
-        #fig 2b's
+            # fig 2b's
             this_fig_names = []
-            if 'plot_'+fig+'_roots' in run_data['results']['refs'][amplicon_name]:
-                for idx, plot_root in enumerate(run_data['results']['refs'][amplicon_name]['plot_'+fig+'_roots']):
-                    fig_name = "plot_"+fig+"_" + str(idx)
-                    add_fig_if_exists(fig_name, plot_root, 'Figure ' + fig_name + ' sgRNA ' + str(idx+1), run_data['results']['refs'][amplicon_name]['plot_'+fig+'_captions'][idx], run_data['results']['refs'][amplicon_name]['plot_'+fig+'_datas'][idx],
-                        this_fig_names, amplicon_fig_locs, amplicon_fig_titles, amplicon_fig_captions, amplicon_fig_datas)
+            if 'plot_' + fig + '_roots' in run_data['results']['refs'][amplicon_name]:
+                for idx, plot_root in enumerate(run_data['results']['refs'][amplicon_name]['plot_' + fig + '_roots']):
+                    fig_name = "plot_" + fig + "_" + str(idx)
+                    add_fig_if_exists(fig, fig_name, plot_root, 'Figure ' + fig_name + ' sgRNA ' + str(idx + 1),
+                                      run_data['results']['refs'][amplicon_name]['plot_' + fig + '_captions'][idx],
+                                      run_data['results']['refs'][amplicon_name]['plot_' + fig + '_datas'][idx],
+                                      this_fig_names, amplicon_figures, crispresso_folder, d3_nuc_quilt_names)
             this_sgRNA_based_fig_names[fig] = this_fig_names
 
-        fig_names[amplicon_name] = amplicon_fig_names
-        sgRNA_based_fig_names[amplicon_name] = this_sgRNA_based_fig_names
+        figures['names'][amplicon_name] = amplicon_figures['names']
+        figures['sgRNA_based_names'][amplicon_name] = this_sgRNA_based_fig_names
+
+        figures['locs'][amplicon_name] = amplicon_figures['locs']
+        figures['titles'][amplicon_name] = amplicon_figures['titles']
+        figures['captions'][amplicon_name] = amplicon_figures['captions']
+        figures['datas'][amplicon_name] = amplicon_figures['datas']
+        figures['htmls'][amplicon_name] = amplicon_figures['htmls']
+    data = {'amplicons': amplicons, 'figures': figures, 'nuc_quilt_names': d3_nuc_quilt_names}
+    return data
 
-        fig_locs[amplicon_name] = amplicon_fig_locs
-        fig_titles[amplicon_name] = amplicon_fig_titles
-        fig_captions[amplicon_name] = amplicon_fig_captions
-        fig_datas[amplicon_name] = amplicon_fig_datas
+
+def make_report(run_data, crispresso_report_file, crispresso_folder, _root, logger):
+    """
+    Writes an HMTL report for a CRISPResso run
+    """
+    data = assemble_figs(run_data, crispresso_folder)
 
     report_display_name = ""
     if run_data['running_info']['args'].name != "":
         report_display_name = run_data['running_info']['args'].name
 
-
-    #find path between the report and the data (if the report is in another directory vs in the same directory as the data)
+    # find path between the report and the data (if the report is in another directory vs in the same directory as the data)
     crispresso_data_path = os.path.relpath(crispresso_folder, os.path.dirname(crispresso_report_file))
     if crispresso_data_path == ".":
         crispresso_data_path = ""
     else:
-        crispresso_data_path += "/";
+        crispresso_data_path += "/"
 
     report_data = {
-        'amplicons': amplicons,
-        'fig_names': fig_names,
-        'sgRNA_based_fig_names': sgRNA_based_fig_names,
-        'fig_locs': fig_locs,
-        'fig_titles': fig_titles,
-        'fig_captions': fig_captions,
-        'fig_datas': fig_datas,
+        'amplicons': data['amplicons'],
+        'figures': data['figures'],
         'run_data': run_data,
         'report_display_name': report_display_name,
         'crispresso_data_path': crispresso_data_path,
+        'nuc_quilt_names': data['nuc_quilt_names'],
     }
 
-    j2_env = Environment(loader=FileSystemLoader(os.path.join(_ROOT, 'templates')))
-    template = j2_env.get_template('report.html')
+    j2_env = get_jinja_loader(_root, logger)
 
-#    dest_dir = os.path.dirname(crispresso_report_file)
-#    shutil.copy2(os.path.join(_ROOT,'templates','CRISPResso_justcup.png'),dest_dir)
-#    shutil.copy2(os.path.join(_ROOT,'templates','favicon.ico'),dest_dir)
+    with open(crispresso_report_file, 'w', encoding="utf-8") as outfile:
+        outfile.write(render_template(
+            'report.html', j2_env, report_data=report_data, C2PRO_INSTALLED=C2PRO_INSTALLED,
+        ))
 
-    outfile = open(crispresso_report_file, 'w')
-    outfile.write(template.render(report_data=report_data))
-    outfile.close()
 
-def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info, batch_folder, _ROOT):
+def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info, batch_folder, _root, logger):
+    """
+    Makes a report for a CRIPSRessoBatch run
+    """
     batch_names = crispresso2_info['results']['completed_batch_arr']
+    failed_runs = crispresso2_info['results']['failed_batch_arr']
+    failed_runs_desc = crispresso2_info['results']['failed_batch_arr_desc']
     display_names = crispresso2_info['results']['batch_input_names']
 
     window_nuc_pct_quilts = crispresso2_info['results']['general_plots']['window_nuc_pct_quilt_plot_names']
@@ -181,6 +263,10 @@ def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info,
         allele_modification_heatmap_plot['datas'] = crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_datas']
     else:
         allele_modification_heatmap_plot['datas'] = {}
+    if 'allele_modification_heatmap_plot_divs' in crispresso2_info['results']['general_plots']:
+        allele_modification_heatmap_plot['divs'] = crispresso2_info['results']['general_plots']['allele_modification_heatmap_plot_divs']
+    else:
+        allele_modification_heatmap_plot['divs'] = {}
 
     allele_modification_line_plot = {}
     if 'allele_modification_line_plot_names' in crispresso2_info['results']['general_plots']:
@@ -203,24 +289,36 @@ def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info,
         allele_modification_line_plot['datas'] = crispresso2_info['results']['general_plots']['allele_modification_line_plot_datas']
     else:
         allele_modification_line_plot['datas'] = {}
+    if 'allele_modification_line_plot_divs' in crispresso2_info['results']['general_plots']:
+        allele_modification_line_plot['divs'] = crispresso2_info['results']['general_plots']['allele_modification_line_plot_divs']
+    else:
+        allele_modification_line_plot['divs'] = {}
 
     allele_modification_heatmap_plot['htmls'] = {}
     for heatmap_plot_name, heatmap_plot_path in allele_modification_heatmap_plot['paths'].items():
-        with open(heatmap_plot_path) as fh:
+        with open(heatmap_plot_path, encoding="utf-8") as fh:
             allele_modification_heatmap_plot['htmls'][heatmap_plot_name] = fh.read()
 
     allele_modification_line_plot['htmls'] = {}
     for line_plot_name, line_plot_path in allele_modification_line_plot['paths'].items():
-        with open(line_plot_path) as fh:
+        with open(line_plot_path, encoding="utf-8") as fh:
             allele_modification_line_plot['htmls'][line_plot_name] = fh.read()
 
+    summary_plot_htmls = {}
+    for plot_name in window_nuc_pct_quilts + nuc_pct_quilts:
+        if os.path.exists(os.path.join(batch_folder, f'{plot_name}.json')):
+            with open(os.path.join(batch_folder, f'{plot_name}.json'), encoding='utf-8') as window_nuc_pct_json_fh:
+                summary_plot_htmls[plot_name] = f"""
+            <div class="d-flex justify-content-between" style="max-height: 80vh; overflow-y: auto;" id="{plot_name}"></div>
+            <script type="text/javascript">const {plot_name} = {window_nuc_pct_json_fh.read().strip()}</script>
+            """
+
     #find path between the report and the data (if the report is in another directory vs in the same directory as the data)
     crispresso_data_path = os.path.relpath(batch_folder, os.path.dirname(crispressoBatch_report_file))
     if crispresso_data_path == ".":
         crispresso_data_path = ""
     else:
-        crispresso_data_path += "/";
-
+        crispresso_data_path += "/"
 
     sub_html_files = {}
     run_names = []
@@ -230,9 +328,9 @@ def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info,
         crispresso_folder = os.path.join(batch_folder, sub_folder)
         run_data = CRISPRessoShared.load_crispresso_info(crispresso_folder)
         if 'running_info' not in run_data:
-            raise Exception('CRISPResso run %s has no report. Cannot add to batch report.'% sub_folder)
+            raise Exception(f'CRISPResso run {sub_folder} has no report. Cannot add to batch report.')
 
-        this_sub_html_file = sub_folder+".html"
+        this_sub_html_file = sub_folder + ".html"
         if run_data['running_info']['args'].place_report_in_output_folder:
             this_sub_html_file = os.path.join(sub_folder, run_data['running_info']['report_filename'])
         sub_html_files[display_name] = this_sub_html_file
@@ -241,20 +339,25 @@ def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info,
 
     output_title = 'CRISPResso Batch Output'
     if crispresso2_info['running_info']['args'].name != '':
-        output_title += '<br/>{0}'.format(crispresso2_info['running_info']['args'].name)
+        output_title += f"<br/>{crispresso2_info['running_info']['args'].name}"
 
     make_multi_report(
         run_names,
+        failed_runs,
+        failed_runs_desc,
         sub_html_files,
         crispressoBatch_report_file,
         batch_folder,
-        _ROOT,
+        _root,
         output_title,
+        'batch',
+        logger,
         summary_plots={
             'names': summary_plot_names,
             'titles': summary_plot_titles,
             'labels': summary_plot_labels,
             'datas': summary_plot_datas,
+            'htmls': summary_plot_htmls,
         },
         window_nuc_pct_quilts=window_nuc_pct_quilts,
         nuc_pct_quilts=nuc_pct_quilts,
@@ -265,37 +368,51 @@ def make_batch_report_from_folder(crispressoBatch_report_file, crispresso2_info,
     )
 
 
-def make_pooled_report_from_folder(crispresso_report_file, crispresso2_info, folder, _ROOT):
+def make_pooled_report_from_folder(crispresso_report_file, crispresso2_info, folder, _root, logger):
+    """
+    Makes a report for a CRISPRessoPooled run
+    """
     names_arr = crispresso2_info['results']['good_region_names']
     output_title = 'CRISPResso Pooled Output'
     if crispresso2_info['running_info']['args'].name != '':
-        output_title += '<br/>{0}'.format(crispresso2_info['running_info']['args'].name)
-    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _ROOT)
+        output_title += f"<br/>{crispresso2_info['running_info']['args'].name}"
+    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _root, 'pooled', logger)
+
 
-def make_compare_report_from_folder(crispresso_report_file, crispresso2_info, folder, _ROOT):
+def make_compare_report_from_folder(crispresso_report_file, crispresso2_info, folder, _root, logger):
+    """
+    Makes a report for a CRISPRessoCompare run
+    """
     names_arr = []
     output_title = 'CRISPResso Compare Output'
     if crispresso2_info['running_info']['args'].name != '':
-        output_title += '<br/>{0}'.format(crispresso2_info['running_info']['args'].name)
-    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _ROOT)
+        output_title += "<br/>{crispresso2_info['running_info']['args'].name}"
+    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _root, 'compare', logger)
 
-def make_meta_report_from_folder(crispresso_report_file, crispresso2_info, folder, _ROOT):
+
+def make_meta_report_from_folder(crispresso_report_file, crispresso2_info, folder, _root, logger):
     names_arr = crispresso2_info['meta_names_arr']
     input_names = crispresso2_info['meta_input_names']
     output_title = 'CRISPresso Meta Output'
     if crispresso2_info['running_info']['args'].name != '':
-        output_title += '<br/>{0}'.format(crispresso2_info['running_info']['args'].name)
-    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _ROOT, display_names=input_names)
+        output_title += "<br/>{crispresso2_info['running_info']['args'].name}"
+    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _root, 'meta', logger,
+                                  display_names=input_names)
+
 
-def make_wgs_report_from_folder(crispresso_report_file, crispresso2_info, folder, _ROOT):
+def make_wgs_report_from_folder(crispresso_report_file, crispresso2_info, folder, _root, logger):
+    """
+    Makes a report for a CRISPRessoWGS run
+    """
     names_arr = crispresso2_info['results']['good_region_names']
-    display_names = crispresso2_info['results']['good_region_display_names']
     output_title = 'CRISPResso WGS Output'
     if crispresso2_info['running_info']['args'].name != '':
-        output_title += '<br/>{0}'.format(crispresso2_info['running_info']['args'].name)
-    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _ROOT, display_names)
+        output_title += "<br/>{crispresso2_info['running_info']['args'].name}"
+    make_multi_report_from_folder(crispresso2_info, names_arr, output_title, crispresso_report_file, folder, _root, 'wgs', logger)
 
-def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispresso_report_file,folder,_ROOT,display_names=None):
+
+def make_multi_report_from_folder(crispresso2_info, names_arr, report_name, crispresso_report_file, folder, _root, crispresso_tool, logger,
+                                  display_names=None):
     """
     Prepares information to make a report of multiple CRISPResso runs - like CRISPRessoWGS or CRISPRessoPooled
 
@@ -305,11 +422,13 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
     report_name (string): text to be shown at top of report
     crispresso_report_file (string): path to write report to
     folder (string): folder containing crispresso runs
-    _ROOT (string): location of crispresso assets (images, templates, etc)
-    display_names (dict): report_name->display_name; Titles to be shown for crispresso runs (if different from names_arr, e.g. if display_names have spaces or bad chars, they won't be the same as names_arr)
+    _root (string): location of crispresso assets (images, templates, etc)
+    logger (logging.Logger): logger to log messages to, mainly for undefined variables in Jinja2 templates
+    display_names (dict): report_name->display_name; Titles to be shown for crispresso runs
+        (if different from names_arr, e.g. if display_names have spaces or bad chars, they won't be the same as names_arr)
 
     Returns:
-    Nothin
+    Nothing
     """
 
     summary_plot_names = []
@@ -326,6 +445,14 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
         summary_plot_datas = crispresso2_info['results']['general_plots']['summary_plot_datas']
 
     run_names = []
+    if 'failed_batch_arr' in crispresso2_info['results']:
+        failed_runs = crispresso2_info['results']['failed_batch_arr']
+    else:
+        failed_runs = []
+    if 'failed_batch_arr' in crispresso2_info['results']:
+        failed_runs_desc = crispresso2_info['results']['failed_batch_arr_desc']
+    else:
+        failed_runs_desc = []
     sub_html_files = {}
     sub_2a_labels = {}
     sub_2a_pdfs = {}
@@ -335,15 +462,15 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
         if display_names is not None:
             display_name = display_names[name]
 
-        folder_name = 'CRISPResso_on_%s' % name
+        folder_name = f'CRISPResso_on_{name}'
         sub_folder = os.path.join(folder, folder_name)
         run_data = CRISPRessoShared.load_crispresso_info(sub_folder)
         if 'running_info' not in run_data:
-            raise Exception('CRISPResso run %s has no report. Cannot add to report.'% sub_folder)
+            raise Exception(f'CRISPResso run {sub_folder} has no report. Cannot add to report.')
 
         run_names.append(display_name)
 
-        this_sub_html_file = os.path.basename(folder_name)+".html"
+        this_sub_html_file = os.path.basename(folder_name) + ".html"
         if run_data['running_info']['args'].place_report_in_output_folder:
             this_sub_html_file = os.path.join(os.path.basename(sub_folder), run_data['running_info']['report_filename'])
         sub_html_files[display_name] = this_sub_html_file
@@ -352,9 +479,9 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
         this_sub_2a_pdfs = []
         for ref_name in run_data['results']['ref_names']:
             if 'plot_2a_root' in run_data['results']['refs'][ref_name]:
-                pdf_file = run_data['results']['refs'][ref_name]['plot_2a_root']+".pdf"
+                pdf_file = run_data['results']['refs'][ref_name]['plot_2a_root'] + ".pdf"
                 if os.path.exists(pdf_file):
-                    this_sub_2a_pdfs.append(run_data['results']['refs'][ref_name]['plot_2a_root']+".pdf")
+                    this_sub_2a_pdfs.append(run_data['results']['refs'][ref_name]['plot_2a_root'] + ".pdf")
                     this_sub_2a_labels.append("Nucleotide distribution across " + ref_name)
 
         sub_2a_labels[display_name] = this_sub_2a_labels
@@ -362,11 +489,15 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
 
     make_multi_report(
         run_names,
+        failed_runs,
+        failed_runs_desc,
         sub_html_files,
         crispresso_report_file,
         folder,
-        _ROOT,
+        _root,
         report_name,
+        crispresso_tool,
+        logger,
         summary_plots={
             'names': summary_plot_names,
             'titles': summary_plot_titles,
@@ -378,24 +509,23 @@ def make_multi_report_from_folder(crispresso2_info,names_arr,report_name,crispre
 
 def make_multi_report(
     run_names,
+    failed_runs,
+    failed_runs_desc,
     sub_html_files,
     crispresso_multi_report_file,
     crispresso_folder,
-    _ROOT,
+    _root,
     report_name,
-    window_nuc_pct_quilts=[],
-    nuc_pct_quilts=[],
-    window_nuc_conv_plots=[],
-    nuc_conv_plots=[],
-    summary_plots={
-        'names': [],
-        'titles': [],
-        'labels': [],
-        'datas': [],
-    },
-    compact_plots_to_show={},
-    allele_modification_heatmap_plot={},
-    allele_modification_line_plot={},
+    crispresso_tool,
+    logger,
+    window_nuc_pct_quilts=None,
+    nuc_pct_quilts=None,
+    window_nuc_conv_plots=None,
+    nuc_conv_plots=None,
+    summary_plots=None,
+    compact_plots_to_show=None,
+    allele_modification_heatmap_plot=None,
+    allele_modification_line_plot=None,
 ):
     """
     Makes an HTML report for a run containing multiple crispresso runs
@@ -406,8 +536,7 @@ def make_multi_report(
     crispresso_multi_report_file (string): path of file to write to
     report_name (string): description of report type to be shown at top of report
     crispresso_folder (string): absolute path to the crispresso output
-    _ROOT (string): absolute path to the crispresso executable
-
+    _root (string): absolute path to the crispresso executable
     summary_plots (dict): a dict with the following keys:
         names (list): list of plot names - keys for following dicts
         titles (dict): dict of plot_name->plot_title
@@ -429,11 +558,17 @@ def fill_default(dictionary, key, default_type=list):
         if key not in dictionary:
             dictionary[key] = default_type()
 
-    j2_env = Environment(
-        loader=FileSystemLoader(os.path.join(_ROOT, 'templates')),
-    )
+    j2_env = get_jinja_loader(_root, logger)
+
     j2_env.filters['dirname'] = dirname
-    template = j2_env.get_template('multiReport.html')
+    if crispresso_tool == 'batch':
+        template = 'batchReport.html'
+    elif crispresso_tool == 'pooled':
+        template = 'pooledReport.html'
+    elif crispresso_tool == 'wgs':
+        template = 'wgsReport.html'
+    else:
+        template = 'multiReport.html'
 
     crispresso_data_path = os.path.relpath(
         crispresso_folder, os.path.dirname(crispresso_multi_report_file),
@@ -443,6 +578,10 @@ def fill_default(dictionary, key, default_type=list):
     else:
         crispresso_data_path += "/"
 
+    if allele_modification_heatmap_plot is None:
+        allele_modification_heatmap_plot = {}
+    if allele_modification_line_plot is None:
+        allele_modification_line_plot = {}
     dictionaries = [
         allele_modification_heatmap_plot, allele_modification_line_plot,
     ]
@@ -452,6 +591,7 @@ def fill_default(dictionary, key, default_type=list):
         ('titles', list),
         ('labels', dict),
         ('datas', dict),
+        ('divs', dict)
     ]
     for dictionary in dictionaries:
         for key, default_type in keys_and_default_types:
@@ -460,32 +600,52 @@ def fill_default(dictionary, key, default_type=list):
                 key,
                 default_type,
             )
-
-    with open(crispresso_multi_report_file, 'w') as outfile:
-        outfile.write(template.render(
-            window_nuc_pct_quilts=window_nuc_pct_quilts,
-            nuc_pct_quilts=nuc_pct_quilts,
-            window_nuc_conv_plots=window_nuc_conv_plots,
-            nuc_conv_plots=nuc_conv_plots,
+    if summary_plots is None:
+        summary_plots={
+            'names': [],
+            'titles': [],
+            'labels': [],
+            'datas': [],
+            'htmls': [],
+        }
+    for html in sub_html_files:
+        sub_html_files[html] = crispresso_data_path + sub_html_files[html]
+    with open(crispresso_multi_report_file, 'w', encoding="utf-8") as outfile:
+        outfile.write(render_template(
+            template,
+            j2_env,
+            window_nuc_pct_quilts=[] if window_nuc_pct_quilts is None else window_nuc_pct_quilts,
+            nuc_pct_quilts=[] if nuc_pct_quilts is None else nuc_pct_quilts,
+            window_nuc_conv_plots=[] if window_nuc_conv_plots is None else window_nuc_conv_plots,
+            nuc_conv_plots=[] if nuc_conv_plots is None else nuc_conv_plots,
             crispresso_data_path=crispresso_data_path,
-            summary_plot_names=summary_plots['names'],
-            summary_plot_titles=summary_plots['titles'],
-            summary_plot_labels=summary_plots['labels'],
-            summary_plot_datas=summary_plots['datas'],
+            report_data={
+                'names': summary_plots['names'],
+                'titles': summary_plots['titles'],
+                'labels': summary_plots['labels'],
+                'datas': summary_plots['datas'],
+                'htmls': summary_plots['htmls'] if 'htmls' in summary_plots else [],
+                'crispresso_data_path': crispresso_data_path,
+            },
             run_names=run_names,
+            failed_runs=failed_runs,
+            failed_runs_desc=failed_runs_desc,
             sub_html_files=sub_html_files,
             report_name=report_name,
-            compact_plots_to_show=compact_plots_to_show,
+            compact_plots_to_show=[] if compact_plots_to_show is None else compact_plots_to_show,
             allele_modification_heatmap_plot_names=allele_modification_heatmap_plot['names'],
             allele_modification_heatmap_plot_htmls=allele_modification_heatmap_plot['htmls'],
             allele_modification_heatmap_plot_titles=allele_modification_heatmap_plot['titles'],
             allele_modification_heatmap_plot_labels=allele_modification_heatmap_plot['labels'],
             allele_modification_heatmap_plot_datas=allele_modification_heatmap_plot['datas'],
+            allele_modification_heatmap_plot_divs=allele_modification_heatmap_plot['divs'],
             allele_modification_line_plot_names=allele_modification_line_plot['names'],
             allele_modification_line_plot_htmls=allele_modification_line_plot['htmls'],
             allele_modification_line_plot_titles=allele_modification_line_plot['titles'],
             allele_modification_line_plot_labels=allele_modification_line_plot['labels'],
             allele_modification_line_plot_datas=allele_modification_line_plot['datas'],
+            allele_modification_line_plot_divs=allele_modification_line_plot['divs'],
+            C2PRO_INSTALLED=C2PRO_INSTALLED,
         ))
 
 
@@ -494,10 +654,11 @@ def make_aggregate_report(
     report_name,
     crispresso_report_file,
     crispresso_report_folder,
-    _ROOT,
+    _root,
     folder_arr,
     crispresso_html_reports,
-    compact_plots_to_show={},
+    logger,
+    compact_plots_to_show=None,
     display_names=None,
 ):
     """
@@ -508,14 +669,16 @@ def make_aggregate_report(
     report_name (string): text to be shown at top of report
     crispresso_report_file (string): path to write report to
     crispresso_report_folder (string): path containing aggregated plots, etc.
-    _ROOT (string): location of crispresso assets (images, templates, etc)
+    _root (string): location of crispresso assets (images, templates, etc)
     folder_arr (arr of strings): paths to the aggregated crispresso folders
     crispresso_html_reports (dict): folder->html_path; Paths to the aggregated crispresso run html reports
+    logger (logging.Logger): logger to log messages
     compact_plots_to_show (dict): name=>{'href': path to target(report) when user clicks on image, 'img': path to png image to show}
-    display_names (dict): folder->display_name; Titles to be shown for crispresso runs (if different from names_arr, e.g. if display_names have spaces or bad chars, they won't be the same as names_arr)
+    display_names (dict): folder->display_name; Titles to be shown for crispresso runs
+        (if different from names_arr, e.g. if display_names have spaces or bad chars, they won't be the same as names_arr)
 
     Returns:
-    Nothin
+    Nothing
     """
     summary_plots = {}
     if 'summary_plot_names' in crispresso2_info['results']['general_plots']:
@@ -589,7 +752,7 @@ def make_aggregate_report(
     run_names = []
     sub_html_files = {}
 
-    for idx, folder in enumerate(folder_arr):
+    for folder in folder_arr:
         display_name = folder
         if display_names is not None:
             display_name = display_names[folder]
@@ -597,7 +760,8 @@ def make_aggregate_report(
         run_names.append(display_name)
         sub_html_file = os.path.relpath(crispresso_html_reports[folder], crispresso_report_folder)
         sub_html_files[display_name] = sub_html_file
-
+    if compact_plots_to_show is None:
+        compact_plots_to_show = {}
     for compact_plot in compact_plots_to_show:
         old_href = compact_plots_to_show[compact_plot]['href']
         compact_plots_to_show[compact_plot]['href'] = os.path.relpath(old_href, crispresso_report_folder)
@@ -606,21 +770,29 @@ def make_aggregate_report(
 
     allele_modification_heatmap_plot['htmls'] = {}
     for heatmap_plot_name, heatmap_plot_path in allele_modification_heatmap_plot['paths'].items():
-        with open(heatmap_plot_path) as fh:
+        with open(heatmap_plot_path, encoding="utf-8") as fh:
             allele_modification_heatmap_plot['htmls'][heatmap_plot_name] = fh.read()
 
     allele_modification_line_plot['htmls'] = {}
     for line_plot_name, line_plot_path in allele_modification_line_plot['paths'].items():
-        with open(line_plot_path) as fh:
+        with open(line_plot_path, encoding="utf-8") as fh:
             allele_modification_line_plot['htmls'][line_plot_name] = fh.read()
 
+    # make_multi_report expects two arrays here for other calls of this function
+    empty_failed_runs = []
+    empty_failed_runs_desc = []
+
     make_multi_report(
         run_names,
+        empty_failed_runs,
+        empty_failed_runs_desc,
         sub_html_files,
         crispresso_report_file,
         crispresso_report_folder,
-        _ROOT,
+        _root,
         report_name,
+        'aggregate',
+        logger,
         window_nuc_pct_quilts=window_nuc_pct_quilts,
         nuc_pct_quilts=nuc_pct_quilts,
         summary_plots=summary_plots,
diff --git a/CRISPResso2/CRISPRessoReports/README.md b/CRISPResso2/CRISPRessoReports/README.md
new file mode 100644
index 00000000..44ff95b1
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/README.md
@@ -0,0 +1,209 @@
+# CRISPRessoReports
+
+This repo holds the shared reports code and HTML templates that are used by the CLI and web projects.
+
+Take care when committing into these files as not to mix unrelated git histories.
+
+## How do I work with this repo?
+
+Step 1 only needs to be done once per cloned repo, the other steps will need to be done more frequently.
+
+1. Add the remote to this repo to the "parent" repo (i.e. `CRISPResso2` or `C2Web`). **You should only have to do this once.**
+
+``` shell
+git remote add reports https://github.com/edilytics/CRISPRessoReports.git
+```
+
+*Note:* you can change the `reports` above to whatever you would like, just don't forget to replace `reports` with it in the other steps.
+
+2. In the parent repo, fetch the latest changes from `CRISPRessoReports`.
+
+``` shell
+git fetch reports
+```
+
+3. Checkout a `CRISPRessoReports` branch.
+
+``` shell
+git checkout -b master-reports reports/master
+```
+
+*Note:* you can obviously change the names of these branches (or select a branch other than `master`). I would recommend naming all branches from `CRISPRessoReports` with the same prefix or suffix (i.e. `*-reports`) because it can get confusing to keep all of the branches straight...
+
+4. Read `CRISPRessoReports` into the parent repo as a subtree. This is when the code in `CRISPRessoReports` will finally be added to the parent repo.
+
+**Very important**, switch to the branch in the parent repo where you want the code to be added! For example:
+
+``` shell
+git checkout <feature-branch>
+```
+
+Then, you will read the commits into wherever `CRISPRessoReports` are stored for that repo. **Note:** you should only have to do this if `CRISPRessoReports` has not been added to the parent repo, if it is already there, do not repeat this step.
+
+``` shell
+git read-tree --prefix=CRISPResso2/CRISPRessoReports -u master-reports
+```
+
+Run `git status` and you should see the files added!
+
+5. Stage and commit your files as you normally would.
+
+### How do I pull commits that are in `CRISPRessoReports` into the parent repo?
+
+1. In the parent repo, switch to the `<feature-branch>-reports` branch.
+
+``` shell
+git checkout <feature-branch>-reports
+```
+
+2. Pull the changes from `CRISPRessoReports`.
+
+``` shell
+git pull
+```
+
+You should see the updates that you are looking for.
+
+3. **Very important**, switch back to whichever branch you are working on in the parent repo.
+
+``` shell
+git checkout <feature-branch>
+```
+
+4. Merge the changes in and resolve any merge conflicts.
+
+``` shell
+git merge --squash -Xsubtree="CRISPResso2/CRISPRessoReports" --no-commit --allow-unrelated-histories <feature-branch>-reports
+```
+
+*Note:* You may need to change the value of the `-Xsubtree` parameter to match where `CRISPRessoReports` is located in the parent repo.
+
+5. Commit your changes and resolve merge conflicts.
+
+Also, note that the default commit message may have a summary of all commits, please shorten it to be descriptive of the most recent changes.
+
+### How do I push commits that are in my parent repo's `CRISPRessoReports` into the shared `CRISPRessoReports` repo?
+
+1. In the parent repo, switch to (or create) the branch on `CRISPRessoReports` that will have the changes you push.
+
+If you are creating a new branch based off of `CRISPRessoReports` master, run this to switch to the reports master branch:
+
+``` shell
+git checkout reports/master
+```
+
+Then, run to actually create (and switch to) the branch that you will be working with:
+
+``` shell
+git checkout -b <feature-branch>-reports
+```
+
+Or if you would like to push to an existing branch on `CRISPRessoReports`, run this:
+
+``` shell
+git checkout <feature-branch>-reports
+```
+
+2. Merge the changes in and resove any merge conflicts.
+
+``` shell
+git merge --squash -Xsubtree="CRISPResso2/CRISPRessoReports" --no-commit --allow-unrelated-histories <feature-branch>
+```
+
+*Note:* `<feature-branch>` is the branch of the parent repo that contains the changes inside the `CRISPRessoReports` sub-directory.
+
+3. Push to `CRISPRessoReports`.
+
+``` shell
+git push
+```
+
+4. Switch back to your branch on `CRISPResso` or `C2Web`.
+
+``` shell
+git checkout <feature-branch>
+```
+
+### I am working on a feature that requires changing `CRISPRessoReports`, what do I do?
+
+If a feature that you are working on requires changes to CRISPRessoReports, you will need to perform a few steps to get setup.
+
+1. Create a feature branch in the parent repo, based on the parent repo master.
+
+``` shell
+git checkout -b <feature-branch> origin/master
+```
+
+2. Create a feature branch on `CRISPRessoReports`.
+
+Checkout your local `CRISPRessoReports` master branch.
+
+``` shell
+git checkout master-reports
+```
+
+Pull in the latest changes.
+
+``` shell
+git pull
+```
+
+Create the `CRISPRessoReports` feature branch based on `reports/master`.
+
+``` shell
+git checkout -b <feature-branch>-reports reports/master
+```
+
+*Note:* if your branch is named `cool-feature` in the parent repo, then follow the convention of naming the corresponding `CRISPRessoReports` branch `cool-feature-reports`.
+
+If you run `git status` at this point you should see any directories in the parent repo as untracked files, this is normal and expected.
+
+3. Switch back to the feature-branch in the parent repo, and develop your changes.
+
+``` shell
+git checkout <feature-branch>
+```
+
+*Note:* you can mingle your changes in `CRISPRessoReports` and the parent repo in the same commits.
+
+4. Merge and push your changes up to `CRISPRessoReports`.
+
+Switch to the `<feature-branch>-reports` branch.
+
+``` shell
+git checkout <feature-branch>-reports
+```
+
+Merge the changes from the parent repo into the `<feature-branch>-reports` branch.
+
+``` shell
+git merge --squash -Xsubtree="CRISPResso2/CRISPRessoReports" --no-commit --allow-unrelated-histories <feature-branch>
+```
+
+# FAQ
+
+## There are lots of merge conflicts, how do I just accept all of the incoming changes?
+
+If you want to blindly accept all of the incoming changes, you can add the parameter `-Xtheirs` to the `git merge...` command and anything that was a merge conflict before, should now be overwritten by the incoming change.
+
+## I tired of typing `git merge --squash ...`, what can I do?!
+
+Typing out the `git merge...` command is long and is a big pain. Here are some shortcuts to add to your `.git/config` file in order to make this easier to type.
+
+``` git-config
+[alias]
+    # merge in branch and resolve merge conflicts
+    m = "!f() { git merge --squash -Xsubtree='CRISPResso2/CRISPRessoReports' --no-commit --allow-unrelated-histories $1; }; f"
+    # merge in branch and accept all of the incoming changes
+    mt = "!f() { git merge --squash -Xtheirs -Xsubtree='CRISPResso2/CRISPRessoReports' --no-commit --allow-unrelated-histories $1; }; f"
+```
+
+Now you can just run `git m <feature-branch>` to merge `<feature-branch>` into your current branch. Or run `git mt <feature-branch>` to accept all of the incoming changes.
+
+# Sources and helpful links
+
+- This method was heavily based off of what was described in [this blog post](http://johnatten.com/2013/03/16/git-subtree-merge-the-quick-version/)
+- If you want to know more about git merging, the manual is [very helpful](https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging)
+- If you messed up (merging the wrong branch), you can undo it using `git reset --hard <branch>`. **Beware:** this can cause you to lose work, so use with care. [Learn more here](https://stackoverflow.com/a/8888015/1947159).
+- If you need to rewrite git history, try using [git-filter-repo](https://github.com/newren/git-filter-repo)
+- After rewriting git history (from a mirror repo), if you can't push to GitHub, [try this](https://stackoverflow.com/a/34266401/1947159)
diff --git a/CRISPResso2/CRISPRessoReports/__init__.py b/CRISPResso2/CRISPRessoReports/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/CRISPResso2/CRISPRessoReports/jinja_partials.py b/CRISPResso2/CRISPRessoReports/jinja_partials.py
new file mode 100644
index 00000000..73ec8ce4
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/jinja_partials.py
@@ -0,0 +1,52 @@
+'''
+This file is derived from https://github.com/mikeckennedy/jinja_partials and is subject to the following license:
+
+MIT License
+
+Copyright (c) 2021 Michael Kennedy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+'''
+
+from functools import partial
+
+from markupsafe import Markup
+
+
+def render_partial(template_name, renderer=None, markup=True, **data):
+    """
+    Renders a partial template and returns the result. If `markup` is True, the result is wrapped in a `Markup` object.
+    """
+    if renderer is None:
+        if flask is None:
+            raise PartialsException('No renderer specified')
+        else:
+            renderer = flask.render_template
+
+    if markup:
+        return Markup(renderer(template_name, **data))
+
+    return renderer(template_name, **data)
+
+
+def generate_render_partial(renderer, markup=True):
+    """
+    Returns a partial function that renders a template using the specified renderer.
+    """
+    return partial(render_partial, renderer=renderer, markup=markup)
diff --git a/CRISPResso2/templates/CRISPResso_justcup.png b/CRISPResso2/CRISPRessoReports/templates/CRISPResso_justcup.png
similarity index 100%
rename from CRISPResso2/templates/CRISPResso_justcup.png
rename to CRISPResso2/CRISPRessoReports/templates/CRISPResso_justcup.png
diff --git a/CRISPResso2/templates/multiReport.html b/CRISPResso2/CRISPRessoReports/templates/batchReport.html
similarity index 51%
rename from CRISPResso2/templates/multiReport.html
rename to CRISPResso2/CRISPRessoReports/templates/batchReport.html
index 4f346efe..dc86a12f 100644
--- a/CRISPResso2/templates/multiReport.html
+++ b/CRISPResso2/CRISPRessoReports/templates/batchReport.html
@@ -1,5 +1,8 @@
 {% extends "layout.html" %}
 {% block head %}
+{% if C2PRO_INSTALLED %}
+<script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
+{% endif %}
 <style>
 .nav-tabs.amp-header {
   border-bottom:none !important;
@@ -44,6 +47,7 @@
 {% endblock %}
 
 {% block content %}
+<div class="row">
 <div class="col-sm-1"></div>
 <div class="col-sm-10">
 
@@ -53,31 +57,29 @@
           {% if run_names|length > 0 %}
             <div class='card text-center mb-2'>
               <div class='card-header'>
-                <h5>{{report_name}}</h5>
+                <h5 id="CRISPResso2_Batch_Output">{{report_name}}</h5>
               </div>
               <div class='card-body p-0'>
-                <div class="list-group list-group-flush" style='max-height:80vh;overflow-y:auto'>
+                <div class="list-group list-group-flush" style="max-height: 25vh; overflow-y: scroll;">
               {% for run_name in run_names %}
-	      <a href="{{crispresso_data_path}}{{sub_html_files[run_name]}}" class="list-group-item list-group-item-action">{{run_name}}</a>
+	      <a href="{{sub_html_files[run_name]}}" class="list-group-item list-group-item-action" id="{{run_name}}">{{run_name}}</a>
                 {% endfor %}
               </div>
             </div>
             </div>
           {% endif %}
 
+          {{render_partial('shared/partials/failed_runs.html', failed_runs=failed_runs, failed_runs_desc=failed_runs_desc)}}
+
           {% if window_nuc_pct_quilts|length > 0 %}
             <div class='card text-center mb-2'>
               <div class='card-header'>
-                <h5>Nucleotide percentages around guides</h5>
+                <h5 id="nucleotide-header">Nucleotide percentages around guides</h5>
               </div>
               <div class='card-body'>
                 {% for plot_name in window_nuc_pct_quilts %}
-                  <h5>{{summary_plot_titles[plot_name]}}</h5>
-		  <a href="{{crispresso_data_path}}{{plot_name}}.pdf"><img src="{{crispresso_data_path}}{{plot_name}}.png" width='80%' ></a>
-                  <label class="labelpadding">{{summary_plot_labels[plot_name]}}</label>
-  							  {% for (data_label,data_path) in summary_plot_datas[plot_name] %}
-							  <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
+                  <h5>{{report_data['titles'][plot_name]}}</h5>
+                  {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
                 {% endfor %}
               </div>
             </div>
@@ -86,16 +88,12 @@ <h5>{{summary_plot_titles[plot_name]}}</h5>
           {% if nuc_pct_quilts|length > 0 %}
             <div class='card text-center mb-2'>
               <div class='card-header'>
-                <h5>Nucleotide percentages in the entire amplicon</h5>
+                <h5 id="nucleotide-header-full-amplicon">Nucleotide percentages in the entire amplicon</h5>
               </div>
               <div class='card-body'>
                 {% for plot_name in nuc_pct_quilts %}
-                  <h5>{{summary_plot_titles[plot_name]}}</h5>
-		  <a href="{{crispresso_data_path}}{{plot_name}}.pdf"><img src="{{crispresso_data_path}}{{plot_name}}.png" width='80%' ></a>
-                  <label class="labelpadding">{{summary_plot_labels[plot_name]}}</label>
-  							  {% for (data_label,data_path) in summary_plot_datas[plot_name] %}
-							  <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
+                  <h5>{{report_data['titles'][plot_name] if plot_name in report_data['titles'] else ''}}</h5>
+                  {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
                 {% endfor %}
               </div>
             </div>
@@ -108,12 +106,8 @@ <h5>Conversion of target bases around guides</h5>
               </div>
               <div class='card-body'>
                 {% for plot_name in window_nuc_conv_plots %}
-                  <h5>{{summary_plot_titles[plot_name]}}</h5>
-		  <a href="{{crispresso_data_path}}{{plot_name}}.pdf"><img src="{{crispresso_data_path}}{{plot_name}}.png" width='80%' ></a>
-                  <label class="labelpadding">{{summary_plot_labels[plot_name]}}</label>
-  							  {% for (data_label,data_path) in summary_plot_datas[plot_name] %}
-							  <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
+                    <h5>{{report_data['titles'][plot_name] if plot_name in report_data['titles'] else ''}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
                 {% endfor %}
               </div>
             </div>
@@ -126,29 +120,21 @@ <h5>Conversion of target bases in the entire amplicon</h5>
               </div>
               <div class='card-body'>
                 {% for plot_name in nuc_conv_plots %}
-                  <h5>{{summary_plot_titles[plot_name]}}</h5>
-		  <a href="{{crispresso_data_path}}{{plot_name}}.pdf"><img src="{{crispresso_data_path}}{{plot_name}}.png" width='80%' ></a>
-                  <label class="labelpadding">{{summary_plot_labels[plot_name]}}</label>
-  							  {% for (data_label,data_path) in summary_plot_datas[plot_name] %}
-							  <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
+                    <h5>{{report_data['titles'][plot_name] if plot_name in report_data['titles'] else ''}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
                 {% endfor %}
               </div>
             </div>
           {% endif %}
 
-          {% if summary_plot_names|length > 0 %}
-            {% for plot_name in summary_plot_names %}
+          {% if report_data['names']|length > 0 %}
+            {% for plot_name in report_data['names'] %}
             <div class='card text-center mb-2'>
               <div class='card-header'>
-                <h5>{{summary_plot_titles[plot_name]}}</h5>
+                <h5>{{report_data['titles'][plot_name] if plot_name in report_data['titles'] else ''}}</h5>
               </div>
-              <div class='card-body' style='max-height:80vh;overflow-y:auto'>
-		      <a href="{{crispresso_data_path}}{{plot_name}}.pdf"><img src="{{crispresso_data_path}}{{plot_name}}.png" width='80%' ></a>
-                <label class="labelpadding">{{summary_plot_labels[plot_name]}}</label>
-							  {% for (data_label,data_path) in summary_plot_datas[plot_name] %}
-							  <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
+              <div class='card-body'>
+		        {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
               </div>
             </div>
             {% endfor %}
@@ -163,6 +149,7 @@ <h5>{{summary_plot_titles[plot_name]}}</h5>
                     <h5>{{allele_modification_heatmap_plot_titles[heatmap_plot_name]}}</h5>
                     <ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
                         <li class="nav-item" role="presentation">
+
                             <a class="nav-link active" data-bs-toggle="tab" id="{{modification_type}}-heatmap-tab" data-bs-target="#{{heatmap_plot_name}}" role="tab" aria-controls="{{heatmap_plot_name}}" aria-selected="true">Heatmap</a>
                         </li>
                         <li class="nav-item" role="presentation">
@@ -173,23 +160,24 @@ <h5>{{allele_modification_heatmap_plot_titles[heatmap_plot_name]}}</h5>
                 <div class="card-body">
                     <div class="tab-content" id="allele-modification-{{modification_type}}-tabs">
                         <div class="tab-pane fade show active" id="{{heatmap_plot_name}}" role="tabpanel" aria-labelledby="{{modification_type}}-heatmap-tab">
-                            {{allele_modification_heatmap_plot_htmls[heatmap_plot_name]}}
+                            {{allele_modification_heatmap_plot_htmls[heatmap_plot_name] | safe}}
                             <label class="labelpadding">{{allele_modification_heatmap_plot_labels[heatmap_plot_name]}}</label>
                             {% for (data_label, data_path) in allele_modification_heatmap_plot_datas[heatmap_plot_name] %}
-                                <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
+                                <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
                             {% endfor %}
                         </div>
                         <div class="tab-pane fade" id="{{line_plot_name}}" role="tabpanel" aria-labelledby="{{modification_type}}-line-tab">
-                            {{allele_modification_line_plot_htmls[line_plot_name]}}
+                            {{allele_modification_line_plot_htmls[line_plot_name] | safe}}
                             <label class="labelpadding">{{allele_modification_line_plot_labels[line_plot_name]}}</label>
                             {% for (data_label, data_path) in allele_modification_line_plot_datas[line_plot_name] %}
-                                <p class="m-0"><small>Data: <a href="{{crispresso_data_path}}{{data_path}}">{{data_label}}</a></small></p>
+                                <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
                             {% endfor %}
                         </div>
                     </div>
                 </div>
               </div>
               <script type="application/javascript">
+
                document.addEventListener("DOMContentLoaded", () => {
                    $("#{{modification_type}}-heatmap-tab").on("shown.bs.tab", (e) => {
                        let plot = document.getElementById("allele-modification-heatmap-{{modification_type}}");
@@ -204,82 +192,23 @@ <h5>{{allele_modification_heatmap_plot_titles[heatmap_plot_name]}}</h5>
                });
               </script>
             {% endfor %}
-              <!-- The below code is another failed attempt to make the heatmaps go fullscreen.
-                   To implement this behavior, wrap the `allele_modification_heatmap_plot_htmls[plot_name]` in a `div`
-                   of class `plotly-full-screen`. -->
-              <!-- <style>
-                   .plot-zoom {
-                   position: absolute;
-                   border: none;
-                   background-color: transparent;
-                   bottom: 0;
-                   right: 0;
-                   }
-                   .full-screen {
-                   position: fixed;
-                   height: 98vh !important;
-                   width: 98vw !important;
-                   left: 0;
-                   top: 0;
-                   z-index: 9999;
-                   overflow: hidden;
-                   }
-                   </style> -->
-              <!-- <script type="application/javascript">
-                   const plotZoom = (el) => {
-                   el = $(el);
-                   let parent = el.parent().parent();
-                   if (el.attr("data-full_screen") === "false") {
-                   parent.addClass("full-screen").trigger("resize").fadeOut().fadeIn();
-                   el.attr("data-full_screen", "true");
-                   Plotly.Plots.resize(parent.find(".plotly-graph-div")[0]);
-                   } else {
-                   parent.removeClass("full-screen").trigger("resize").fadeOut().fadeIn();
-                   el.attr("data-full_screen", "false");
-                   Plotly.Plots.resize(parent.find(".plotly-graph-div")[0]);
-                   }
-                   }
-                   document.addEventListener("DOMContentLoaded", () => {
-                   $(function() {
-                   $(".plotly-full-screen").append(`
-                   <div style="position: relative;">
-                   <button onclick=plotZoom(this) class="plot-zoom" data-full_screen="false" title="Full Screen">
-                   <i class="fa fa-expand-arrows-alt"></i>
-                   </button>
-                   </div>
-                   `);
-                   });
-                   });
-                   </script> -->
           {% endif %}
 
-          {% if compact_plots_to_show|length > 0 %}
-            <div class='card text-center mb-2'>
-              <div class='card-header'>
-                <h5>Summary Plots</h5>
-              </div>
-              <div class='card-body p-0'>
-                <div class="list-group list-group-flush" style='max-height:80vh;overflow-y:auto'>
-							  {% for compact_plot in compact_plots_to_show %}
-	      <a href="{{crispresso_data_path}}{{compact_plots_to_show[compact_plot]['href']}}" data-toggle="tooltip" title="{{compact_plot}}" class="list-group-item list-group-item-action p-0"><img src="{{crispresso_data_path}}{{compact_plots_to_show[compact_plot]['img']}}" width='100%' ></a>
-                {% endfor %}
-              </div>
-              </div>
-            </div>
-          {% endif %}
-
-
           </div>
 
         </div>
 
-	</div> {# jumbotron_content #} <!-- end jumbotron_content -->
-</div> {# jumbrotron #} <!-- end jumbotron -->
 
-</div> {# column #} <!-- end column -->
+        {{render_partial('shared/partials/report_footer_buttons.html', report_zip_filename=report_zip_filename, report_path=report_path)}}
 
-<div class="col-sm-1"></div>
+	</div>
+    <div class="col-sm-1"></div>
+</div>
 {% endblock %}
 
 {% block foot %}
+{% if C2PRO_INSTALLED %}
+  <script src="https://unpkg.com/d3@5"></script>
+  {{ render_partial('partials/batch_d3.html', nucleotide_quilt_slugs=(window_nuc_pct_quilts + nuc_pct_quilts))}}
+{% endif %}
 {% endblock %}
diff --git a/CRISPResso2/templates/favicon.ico b/CRISPResso2/CRISPRessoReports/templates/favicon.ico
similarity index 100%
rename from CRISPResso2/templates/favicon.ico
rename to CRISPResso2/CRISPRessoReports/templates/favicon.ico
diff --git a/CRISPResso2/CRISPRessoReports/templates/layout.html b/CRISPResso2/CRISPRessoReports/templates/layout.html
new file mode 100644
index 00000000..9e1b8c4c
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/layout.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>CRISPResso2</title>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="CRISPResso2: Analysis of genome editing outcomes from deep sequencing data">
+    <meta name="author" content="Kendell Clement and Luca Pinello">
+    <link href='https://fonts.googleapis.com/css?family=Montserrat|Ranga:700' rel='stylesheet' type='text/css'>
+        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.3.5/jquery.fancybox.min.css" />
+    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
+    <style>
+      body {
+        font-family: 'Montserrat', sans-serif !important;
+      {% if is_web %}
+        {% if not is_default_user %}
+          padding-top:60px;
+        {% endif %}
+      {% endif %}
+      }
+      @media (max-width:1015px)
+      {
+        #crispresso_h
+        {
+          font-size:5rem;
+          font-weight:300;
+          line-height:1.2;
+        }
+        #left_help_div
+        {
+          display:none
+        }
+        #top_help_div
+        {
+          display:block
+        }
+      }
+      @media (min-width:1015px)
+      {
+        #crispresso_h
+        {
+          font-size:8rem;
+          font-weight:300;
+          line-height:1.2;
+        }
+        #left_help_div
+        {
+          display:block
+        }
+        #top_help_div
+        {
+          display:none
+        }
+      }
+    </style>
+
+
+    <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
+    Remove this if you use the .htaccess -->
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+    <!-- Replace favicon.ico & apple-touch-icon.png in the root of your domain and delete these references -->
+    {% if is_web %}
+    <link rel="shortcut icon" href="/static/favicon.ico">
+    <link rel="stylesheet" href="/static/css/main.css">
+    <script src="/static/js/htmx-1.9.1.min.js"></script>
+    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
+    {% else %}
+    <link rel="shortcut icon" href="http://crispresso.pinellolab.org/static/favicon.ico" />
+    {% endif %}
+
+    <!-- Optional JavaScript -->  
+    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
+    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.3.5/jquery.fancybox.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
+
+
+  <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
+  {% block head %}{% endblock %}
+
+  </head>
+
+  <body class="m-2">
+    {% if is_web %}
+    <div id='left_help_div' style='position:fixed;top:50%;transform:translateY(-50%);left:1%;width:290px;z-index:99'>
+      {{ self.help_block() }}
+    </div>
+
+    {% with messages = get_flashed_messages(with_categories=true) %}
+      {% if messages %}
+        {% for category, message in messages %}
+          {% if category == "error" %}
+          <div class="alert alert-danger alert-dismissible fade show m-2" role="alert">
+            {{ message }}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+          </div>
+          {% else %}
+          <div class="alert alert-warning alert-dismissible fade show m-2" role="alert">
+            {{ message }}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+          </div>
+          {% endif %}
+        {% endfor %}
+      {% endif %}
+    {% endwith %}
+    {% endif %}
+
+  {# if default user (normal crispresso mode) #}
+  {% if not is_web or is_default_user %}
+  <div class="container">
+      <div class="row pb-2">
+        <div class="col-sm-1"></div>
+        {% if is_web %}
+          <div class="col-sm-3 crispresso_cup"> <a href='/'><img class='img-fluid' src="../../../static/imgs/CRISPResso_justcup.png" width="80%"></a>
+        {% else %}
+          <div class="col-sm-3 crispresso_cup"> <a href='https://crispresso2.pinellolab.org'><img class='img-fluid' src="https://crispresso.pinellolab.partners.org/static/imgs/CRISPResso_justcup.png" width="80%"></a>
+        {% endif %}
+          </div>
+        <div class="col-sm-7" >
+          <a href="{{'/' if is_web else 'https://crispresso2.pinellolab.org'}}" style="text-decoration:none !important; color:rgb(33, 37, 41)"><h1 id='crispresso_h' style="font-family:'Ranga', sans-serif">CRISPResso2 </h1></a>
+          <h3 style="margin-top: -0.5em;padding-right: 2em;">Analysis of genome editing outcomes from deep sequencing data</h3>
+        </div>
+        <div class="col-sm-1">
+        </div>
+      </div>
+
+
+    {% else %}
+      {# if doing user sessions #}
+  <nav class="navbar fixed-top ms-1 navbar-expand-md bg-light navbar-light">
+  <!-- Brand -->
+  <a class="navbar-brand" href="/">
+{{config['BANNER_TEXT']}}</a>
+
+  <!-- Toggler/collapsibe Button -->
+  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
+    <span class="navbar-toggler-icon"></span>
+  </button>
+  <!-- Navbar links -->
+    <div class="collapse navbar-collapse" id="collapsibleNavbar">
+      <ul class="navbar-nav ms-auto">
+      {% if not is_default_user %}
+        <li class="nav-item">
+           <a class="nav-link" href="{{url_for('home')}}">
+            <i class="fas fa-user"></i> {{ current_user.username}}
+          </a>
+        </li>
+        <li class="nav-item">
+           <a class="nav-link" href="{{url_for('home')}}">
+            Home
+          </a>
+        </li>
+      {% endif %}
+      {% block extranavs %}{% endblock %}
+      {% if current_user.role == "Admin" %}
+         <li class="nav-item">
+           <a class="nav-link" href="{{url_for('admin.index')}}">Admin</a>
+         </li>
+      {% endif %}
+      {% if not is_default_user and not current_user.is_authenticated %}
+         <li class="nav-item">
+           <a class="nav-link" href="{{url_for('login')}}">Log in</a>
+         </li>
+      {% else %}
+         <li class="nav-item">
+           <a class="nav-link" href="{{url_for('logout')}}">Logout</a>
+         </li>
+      {% endif %}
+      </ul>
+    </div>
+  </nav>
+  <div class="container">
+  {% endif %}
+
+      <div id='top_help_div'>
+        <div class="row">
+          <div class="col-sm-1"></div>
+          <div class='col-sm-10 pb-2 crispresso_body_div'>
+          {% block help_block %} {% endblock %}
+          </div>
+          <div class="col-sm-1"></div>
+        </div>
+      </div>
+
+      {% block content %}{% endblock %}
+
+      {% if not is_web or is_default_user %}
+      <div class="row">
+      <div class="col-sm-1"></div>
+      <footer class="col-sm-10 crispresso_body_div screen-only py-5">
+        <p class="text-center">
+      If you like CRISPResso2 please support us by citing it in your work: <br>
+          Clement K, Rees H, Canver MC, Gehrke JM, Farouni R, Hsu JY, Cole MA, Liu DR, Joung JK, Bauer DE, Pinello L.<br>
+          <a href="https://rdcu.be/boxVG" target="_blank">CRISPResso2 provides accurate and rapid genome editing sequence analysis. </a><br>
+          Nat Biotechnol. 2019 Mar; 37(3):224-226. doi: 10.1038/s41587-019-0032-3. PubMed PMID: 30809026.
+        </p>
+        <p class="text-center"><small> &copy; Copyright <a href="http://pinellolab.org">Kendell Clement and Luca Pinello</a></small></p>
+        <p class="text-center">
+          <small>
+        <a href="https://twitter.com/intent/tweet?text=CRISPResso2%20provides%20accurate%20and%20rapid%20genome%20editing%20sequence%20analysis%20%40kendellclement%20%40lucapinello%20%23CRISPR%20%23CRISPResso2"
+          class="twitter-share-button" data-via="kendellclement" data-size="large" data-count="none" data-hashtags="CRISPR" data-url="http://crispresso2.pinellolab.org">Tweet about CRISPresso2!</a>
+        </small>
+        </p>
+
+      </footer>
+      <div class="col-sm-1"></div>
+    </div>
+    {% else %}
+      <div class="row">
+      <div class="col-sm-1"></div>
+      <footer id="copyright_div" class="col-sm-10 crispresso_body_div py-5">
+        <p class="text-center"><small> &copy; Copyright <a href="http://edilytics.com">Edilytics</a></small></p>
+      </footer>
+      <div class="col-sm-1"></div>
+    </div>
+    {% endif %}
+    </div>
+    {% block foot %}{% endblock %}
+  </body>
+
+</html>
diff --git a/CRISPResso2/CRISPRessoReports/templates/multiReport.html b/CRISPResso2/CRISPRessoReports/templates/multiReport.html
new file mode 100644
index 00000000..2831d9fc
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/multiReport.html
@@ -0,0 +1,169 @@
+{% extends "layout.html" %}
+{% block head %}
+
+<script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
+<style>
+.nav-tabs.amp-header {
+  border-bottom:none !important;
+}
+
+.card-header.amp-header {
+  border-bottom:none !important;
+}
+.nav-tabs.amp-header .nav-link.active {
+  background-color:lightsteelblue;
+  border-bottom:lightsteelblue;
+}
+
+.tab-content.amp-body {
+  background-color:lightsteelblue;
+}
+
+@media print {
+   .tab-content > .tab-pane {
+    display: block !important;
+    opacity: 1 !important;
+    visibility: visible !important;
+		margin-bottom: 2em !important;
+		page-break-inside: avoid;
+  }
+  .nav-tabs {
+    display:none !important;
+    visibility:hidden !important;
+  }
+  .tab-content.amp-body {
+    background-color:transparent !important;
+  	border:None !important;
+  }
+}
+@media only screen and (max-width: 600px) {
+	.jumbotron img {
+		width:100%
+	}
+}
+</style>
+
+{% endblock %}
+
+{% block content %}
+<div class="row">
+<div class="col-sm-1"></div>
+<div class="col-sm-10">
+
+	<div class="jumbotron" style="background:rgba(0,0,0,0.0); padding:0px" >
+		<div id='jumbotron_content' >
+
+          {% if run_names|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>{{report_name}}</h5>
+              </div>
+              <div class='card-body p-0'>
+                <div class="list-group list-group-flush" style='max-height:80vh;overflow-y:auto'>
+              {% for run_name in run_names %}
+	              <a href="{{sub_html_files[run_name]}}" class="list-group-item list-group-item-action">{{run_name}}</a>
+                  {% endfor %}
+                </div>
+              </div>
+            </div>
+            {% endif %}
+
+            {{render_partial('shared/partials/failed_runs.html', failed_runs=failed_runs, failed_runs_desc=failed_runs_desc)}}
+
+          {% if window_nuc_pct_quilts|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>Nucleotide percentages around guides</h5>
+              </div>
+              <div class='card-body'>
+                {% for plot_name in window_nuc_pct_quilts %}
+                    <h5>{{report_data['titles'][plot_name]}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+                {% endfor %}
+              </div>
+            </div>
+          {% endif %}
+
+          {% if nuc_pct_quilts|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>Nucleotide percentages in the entire amplicon</h5>
+              </div>
+              <div class='card-body'>
+                {% for plot_name in nuc_pct_quilts %}
+                    <h5>{{report_data['titles'][plot_name]}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+                {% endfor %}
+              </div>
+            </div>
+          {% endif %}
+
+          {% if window_nuc_conv_plots|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>Conversion of target bases around guides</h5>
+              </div>
+              <div class='card-body'>
+                {% for plot_name in window_nuc_conv_plots %}
+                    <h5>{{report_data['titles'][plot_name]}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+                {% endfor %}
+              </div>
+            </div>
+          {% endif %}
+
+          {% if nuc_conv_plots|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>Conversion of target bases in the entire amplicon</h5>
+              </div>
+              <div class='card-body'>
+                {% for plot_name in nuc_conv_plots %}
+                    <h5>{{report_data['titles'][plot_name]}}</h5>
+		            {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+                {% endfor %}
+              </div>
+            </div>
+          {% endif %}
+
+          {% if report_data['names']|length > 0 %}
+            {% for plot_name in report_data['names'] %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>{{report_data['titles'][plot_name]}}</h5>
+              </div>
+              <div class='card-body' style='max-height:80vh;overflow-y:auto'>
+		        {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+              </div>
+            </div>
+            {% endfor %}
+          {% endif %}
+
+          {% if compact_plots_to_show|length > 0 %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>Summary Plots</h5>
+              </div>
+              <div class='card-body p-0'>
+                <div class="list-group list-group-flush" style='max-height:80vh;overflow-y:auto'>
+							  {% for compact_plot in compact_plots_to_show %}
+	              <a href="{{report_data['crispresso_data_path']}}{{compact_plots_to_show[compact_plot]['href']}}" data-toggle="tooltip" title="{{compact_plot}}" class="list-group-item list-group-item-action p-0"><img src="{{report_data['crispresso_data_path']}}{{compact_plots_to_show[compact_plot]['img']}}" width='100%' ></a>
+                  {% endfor %}
+                </div>
+              </div>
+            </div>
+            {% endif %}
+
+            {{render_partial('shared/partials/report_footer_buttons.html', report_zip_filename=report_zip_filename, report_path=report_path)}}
+
+	</div> {# jumbotron_content #} <!-- end jumbotron_content -->
+</div> {# jumbrotron #} <!-- end jumbotron -->
+
+</div> {# column #} <!-- end column -->
+
+<div class="col-sm-1"></div>
+</div>
+{% endblock %}
+
+{% block foot %}
+{% endblock %}
diff --git a/CRISPResso2/CRISPRessoReports/templates/pooledReport.html b/CRISPResso2/CRISPRessoReports/templates/pooledReport.html
new file mode 100644
index 00000000..5e17a4f9
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/pooledReport.html
@@ -0,0 +1,97 @@
+{% extends "layout.html" %}
+{% block head %}
+<style>
+.nav-tabs.amp-header {
+  border-bottom:none !important;
+}
+
+.card-header.amp-header {
+  border-bottom:none !important;
+}
+.nav-tabs.amp-header .nav-link.active {
+  background-color:lightsteelblue;
+  border-bottom:lightsteelblue;
+}
+
+.tab-content.amp-body {
+  background-color:lightsteelblue;
+}
+
+@media print {
+   .tab-content > .tab-pane {
+    display: block !important;
+    opacity: 1 !important;
+    visibility: visible !important;
+		margin-bottom: 2em !important;
+		page-break-inside: avoid;
+  }
+  .nav-tabs {
+    display:none !important;
+    visibility:hidden !important;
+  }
+  .tab-content.amp-body {
+    background-color:transparent !important;
+  	border:None !important;
+  }
+}
+@media only screen and (max-width: 600px) {
+	.jumbotron img {
+		width:100%
+	}
+}
+</style>
+
+{% if C2PRO_INSTALLED %}
+<script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
+{% endif %}
+
+{% endblock %}
+
+{% block content %}
+<div class="row">
+<div class="col-sm-1"></div>
+<div class="col-sm-10">
+
+	<div class="jumbotron" style="background:rgba(0,0,0,0.0); padding:0px" >
+		<div id='jumbotron_content' >
+
+            {% if run_names|length > 0 %}
+            <div class='card text-center mb-2'>
+                <div class='card-header'>
+                    <h5>{{report_name}}</h5>
+                </div>
+                <div class='card-body p-0'>
+                    <div class="list-group list-group-flush">
+                        {% for region_name in run_names %}
+	                        <a href="{{sub_html_files[region_name]}}" class="list-group-item list-group-item-action">{{region_name}}</a>
+                        {% endfor %}
+                    </div>
+                </div>
+            </div>
+            {% endif %}
+
+            {{render_partial('shared/partials/failed_runs.html', failed_runs=failed_runs, failed_runs_desc=failed_runs_desc)}}
+
+          {% if report_data['names']|length > 0 %}
+            {% for plot_name in report_data['names'] %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5 id="modification_summary_title">{{report_data['titles'][plot_name]}}</h5>
+              </div>
+              <div class='card-body' style='max-height:80vh;overflow-y:auto'>
+                {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+              </div>
+            </div>
+            {% endfor %}
+            {% endif %}
+
+            {{render_partial('shared/partials/report_footer_buttons.html', report_zip_filename=report_zip_filename, report_path=report_path)}}
+
+	</div> {# jumbotron_content #} <!-- end jumbotron_content -->
+</div> {# jumbrotron #} <!-- end jumbotron -->
+
+</div> {# column #} <!-- end column -->
+
+<div class="col-sm-1"></div>
+</div>
+{% endblock %}
diff --git a/CRISPResso2/CRISPRessoReports/templates/report.html b/CRISPResso2/CRISPRessoReports/templates/report.html
new file mode 100644
index 00000000..88795748
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/report.html
@@ -0,0 +1,733 @@
+{% extends "layout.html" %}
+{% block head %}
+<style>
+.nav-tabs.amp-header {
+  border-bottom:none !important;
+}
+
+.card-header.amp-header {
+  border-bottom:none !important;
+}
+.nav-tabs.amp-header .nav-link.active {
+  background-color:lightsteelblue;
+  border-bottom:lightsteelblue;
+}
+
+.pre-scrollable {
+    max-height: 340px;
+    overflow-y: auto;
+    background-color: #f8f8f8;
+    text-align: left
+}
+
+.tab-content.amp-body {
+  background-color:lightsteelblue;
+}
+
+@media only screen and (max-width: 600px) {
+	.jumbotron img {
+		width:100%
+	}
+    .print-only{
+      display: none !important;
+    }
+    .screen-only{
+      display: block;
+    }
+}
+
+@media print {
+  .tab-content > .tab-pane {
+   display: block !important;
+   opacity: 1 !important;
+   visibility: visible !important;
+   margin-bottom: .5em !important;
+ }
+ .nav-tabs {
+   display:none !important;
+   visibility:hidden !important;
+ }
+ .tab-content.amp-body {
+   background-color:transparent !important;
+   border:None !important;
+ }
+ .col-sm-10 {
+		margin-left: -15%;
+		width: 130% !important;
+	}
+ .breakinpage {
+   clear: both;
+   page-break-before: always !important;
+   display: block;
+ }
+ .print-only, .print-only *
+ {
+       display: block;
+   }
+ .screen-only, .screen-only *
+ {
+   display: none !important;
+ }
+ div {
+	border: none !important;
+ }
+}
+</style>
+
+{% if C2PRO_INSTALLED %}
+<script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
+{% endif %}
+
+{% endblock %}
+
+{% block content %}
+<div class="col-sm-1"></div>
+<center><div class="col-sm-10">
+	<div class="jumbotron" style="background:rgba(0,0,0,0.0); padding:0px" >
+		<div id='jumbotron_content' >
+			<div class='card text-center mb-2'>
+				<div class='card-header'>
+					{% if report_data['report_display_name'] != '' %}
+					<h5>{{report_data['report_display_name']}}</h5>
+					{% endif %}
+					{{ render_partial('shared/partials/guardrail_warnings.html', report_data=report_data) | safe}}
+					<h5>CRISPResso2 run information</h5>
+					<ul class="nav nav-tabs justify-content-center card-header-tabs" id="log-tab" role="tablist">
+						<li class="nav-item">
+						<button class="nav-link active" id="log_aln-tab" data-bs-toggle="tab" data-bs-target="#log_aln" role="tab" aria-controls="log_aln" aria-selected="true">Alignment statistics</button>
+						</li>
+						<li class="nav-item">
+						<button class="nav-link" id="log_params-tab" data-bs-toggle="tab" data-bs-target="#log_params" role="tab" aria-controls="log_params" aria-selected="false">Run Parameters</button>
+						</li>
+					</ul>
+				</div>
+				<div class='card-body'>
+					<div class='tab-content'>
+						<div class="tab-pane fade show active" id="log_aln" role="tabpanel">
+							{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_1a')}}
+						</div>
+						{{ render_partial('shared/partials/log_params.html', report_data=report_data) }}
+					</div>
+				</div>
+			</div>
+
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					<h5>Allele assignments</h5>
+					<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
+						<li class="nav-item">
+							<button class="nav-link active" id="aln_pie-tab" data-bs-toggle="tab" data-bs-target="#aln_pie" role="tab" aria-controls="aln_pie" aria-selected="true">Piechart</button>
+						</li>
+						<li class="nav-item">
+							<button class="nav-link" id="aln_bar-tab" data-bs-toggle="tab" data-bs-target="#aln_bar" role="tab" aria-controls="aln_bar" aria-selected="false">Barplot</button>
+						</li>
+    						{% if 'plot_1d' in report_data['figures']['locs'] %}
+						<li class="nav-item">
+							<button class="nav-link" id="aln_dsODN-tab" data-bs-toggle="tab" data-bs-target="#aln_dsODN" role="tab" aria-controls="aln_dsODN" aria-selected="false">dsODN</button>
+						</li>
+              			{% endif %}
+					</ul>
+				</div>
+				<div class='card-body'>
+					<div class="tab-content" id="tabContent">
+						<div class="tab-pane fade show active" id="aln_pie" role="tabpanel" aria-labelledby="aln_pie-tab">
+							{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_1b')}}
+						</div>
+						<div class="tab-pane fade" id="aln_bar" role="tabpanel" aria-labelledby="aln_bar-tab">
+							{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_1c')}}
+						</div>
+    						{% if 'plot_1d' in report_data['figures']['locs'] %}
+						<div class="tab-pane fade" id="aln_dsODN" role="tabpanel" aria-labelledby="aln_dsODN-tab">
+							{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_1d')}}
+						</div>
+              			{% endif %}
+					</div>
+				</div>
+			</div> {# end card #}
+
+        		{# start global coding sequence report #}
+			{% if 'plot_5a' in report_data['figures']['locs'] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					<h5>Global frameshift analysis</h5>
+				</div>
+				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_5a')}}
+				</div>
+			</div>
+			{% endif %}
+
+			{% if 'plot_6a' in report_data['figures']['locs'] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					<h5>Global frameshift mutagenesis profiles</h5>
+				</div>
+				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_6a')}}
+				</div>
+			</div>
+			{% endif %}
+
+			{% if 'plot_8a' in report_data['figures']['locs'] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					<h5>Global splicing analysis</h5>
+				</div>
+				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_8a')}}
+				</div>
+			</div>
+			{% endif %}
+			{# end of global coding sequence analysis #}
+
+			{# start hdr summary #}
+			{% if 'plot_4g' in report_data['figures']['locs'][report_data.amplicons[0]] or 'plot_4g' in report_data['figures']['htmls'][report_data.amplicons[0]] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+					<h5>HDR summary plot</h5>
+					{% else %}
+					<h5>HDR summary report (all reads aligned to {{report_data.amplicons[0]}})</h5>
+					{% endif %}
+				</div>
+				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4g', amplicon_name=report_data.amplicons[0])}}
+				</div>
+			</div>
+			{% endif %}
+            		{# end HDR summary #}
+
+			{# start prime editing report #}
+			{% if 'plot_11a' in report_data['figures']['locs'][report_data.amplicons[0]] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+					<h5>Prime editing report</h5>
+					{% else %}
+  					<h5>Prime editing report (all reads aligned to {{report_data.amplicons[0]}})</h5>
+					{% endif %}
+				</div>
+  				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_11a', amplicon_name=report_data.amplicons[0])}}
+				</div>
+			</div>
+			{% endif %}
+
+			{% if report_data.amplicons[0] in report_data['figures']['sgRNA_based_names'] and '11b' in report_data['figures']['sgRNA_based_names'][report_data.amplicons[0]] and report_data['figures']['sgRNA_based_names'][report_data.amplicons[0]]['11b']|length > 0 %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+					{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+					<h5>Prime editing summary plots at analysis positions</h5>
+					{% else %}
+  					<h5>Prime editing summary plots at analysis positions (aligned to {{report_data.amplicons[0]}})</h5>
+					{% endif %}
+				</div>
+				<div class='card-body'>
+					{% for fig_name in report_data['figures']['sgRNA_based_names'][report_data.amplicons[0]]['11b'] %}
+					<div class='mb-4'>
+						{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=report_data.amplicons[0])}}
+					</div>
+					{% endfor %}
+				</div>
+			</div>
+			{% endif %}
+			{# end plot 11b for prime editing #}
+
+			{% if 'plot_11c' in report_data['figures']['locs'] %}
+			<div class='card text-center mb-2 breakinpage'>
+				<div class='card-header'>
+  					<h5>Scaffold insertions</h5>
+				</div>
+  				<div class='card-body'>
+					{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_11c')}}
+				</div>
+			</div>
+			{% endif %}
+			{# end prime editing section #}
+
+			{% if report_data.amplicons|length == 1 %}
+          	<div> {# if only one amplicon, just a normal div #}
+          	{% else %}
+	        {# If there is more than one amplicon, print a navigation to show each amplicon #}
+			<p class="breakinpage">Reads are aligned to each amplicon sequence separately. Quantification and visualization of these reads are shown for each amplicon below:</p>
+			<div id='amplicons-card' class='card text-center mb-2'>
+				<div class='card-header amp-header'>
+					<h5>Amplicons</h5>
+					<ul class="nav nav-tabs amp-header card-header-tabs" id="nav-tab" role="tablist">
+					{% for amplicon_name in report_data.amplicons %}
+						{% if loop.index0 == 0 %}
+						<li class="nav-item">
+							<button class="nav-link active" id="pills_{{amplicon_name}}_tab" data-bs-toggle="tab" data-bs-target="#div_{{amplicon_name}}" role="tab" aria-controls="div_{{amplicon_name}}" aria-selected="true">{{amplicon_name}}</button>
+						</li>
+						{% else %}
+						<li class="nav-item">
+							<button class="nav-link" id="pills_{{amplicon_name}}_tab" data-bs-toggle="tab" data-bs-target="#div_{{amplicon_name}}" role="tab" aria-controls="div_{{amplicon_name}}" aria-selected="false">{{amplicon_name}}</button>
+						</li>
+						{% endif%}
+					{% endfor %}
+					</ul>
+				</div>
+				{% endif %} {# end if report contains more than one amplicon #}
+
+
+				{% if report_data.amplicons|length == 1 %} {# if only one amplicon, just a normal div #}
+				<div>
+				{% else %}
+				<div class="tab-content pt-3 px-3 amp-body card-body" id="nav-tabContent">
+				{% endif %}
+
+				{% for amplicon_name in report_data.amplicons %}
+					{% if report_data.amplicons|length == 1 %} {# if only one amplicon, just a normal div #}
+					<div>
+					{% elif loop.index0 == 0 %} {# if more than one amplicon, and the first, this is the active one #}
+					<div class="tab-pane fade show active" id="div_{{amplicon_name}}" role="tabpanel" aria-labelledby="pills_{{amplicon_name}}_tab">
+            			<div class="d-none d-print-block"><h3>Reads aligning to {{amplicon_name}}</h3></div> {# this bit appears in print mode #}
+					{% else %} {# otherwise inactive tab #}
+					<div class="tab-pane fade breakinpage" id="div_{{amplicon_name}}" role="tabpanel" aria-labelledby="pills_{{amplicon_name}}_tab">
+            			<div class="d-none d-print-block"><h3>Reads aligning to {{amplicon_name}}</h3> </div>{# this bit appears in print mode #}
+							{% endif %}
+
+		      				<div class='card text-center mb-2'>
+      							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+      								<h5>Nucleotide composition</h5>
+								{% else %}
+      								<h5>Nucleotide composition for {{amplicon_name}}</h5>
+								{% endif %}
+      							</div>
+      							<div class='card-body'>
+
+								{% if 'plot_2a' in report_data['figures']['htmls'][amplicon_name] %}
+									{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_2a', amplicon_name=amplicon_name)}}
+								{% elif 'plot_2a' in report_data['figures']['locs'][amplicon_name] %}
+									<div class='div_zoom_outer d-none d-md-block' style="height:100px;border:1px solid #DDDDDD;position:relative">
+										<div id="zoomview_nucs_{{amplicon_name}}" style="position:absolute;left:0;height:100%;width:100%;background-image: url({{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name]['plot_2a']}}.png);background-size:auto 100%;background-repeat:no-repeat;"></div>
+											<div class="d-lg-none" style="overflow-x:scroll;overflow-y:hidden;position:absolute;width:100%;height:100%">
+												<img src="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name]['plot_2a']}}.png" style='height:100%'>
+											</div>
+									</div>
+									<div style='position:relative;display:inline-block;width:95%'>
+										<a href="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name]['plot_2a']}}.pdf">
+    									<div id='zoomlens_nucs_{{amplicon_name}}' class="d-none d-lg-block" style='float: left;position: absolute;left: 0px;top: 0px;z-index: 1000;border: 1px solid #DDDDDD;height:100%;width:10%'></div>
+    										<img id='tozoom_nucs_{{amplicon_name}}' src="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name]['plot_2a']}}.png" width='100%' style='position:relative'></a>
+									</div>
+									<label class="labelpadding">
+										<span class='d-none d-md-block'>Hover your mouse over the bottom image to zoom in on a specific region.</span><br>
+											{{report_data['figures']['captions'][amplicon_name]['plot_2a']}}
+									</label>
+								{% for (data_label,data_path) in report_data['figures']['datas'][amplicon_name]['plot_2a'] %}
+									<p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
+								{% endfor %}
+								{% endif %}								
+								{% if 'plot_2b' in report_data['figures']['htmls'][amplicon_name] %}
+								  	{# report_data['figures']['htmls'][amplicon_name]['plot_2b']|safe #}
+									{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_2b', amplicon_name=amplicon_name)}}
+								{% elif amplicon_name in report_data['figures']['sgRNA_based_names'] and '2b' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+  									{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['2b'] %}
+									{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+  									{% endfor %}
+								{% endif %}
+            		</div>
+					</div> {# end card #}
+
+  						<div class='card text-center mb-2 breakinpage'>
+  							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+      							<h5>Modification lengths</h5>
+								{% else %}
+      							<h5>Modification lengths for {{amplicon_name}}</h5>
+								{% endif %}
+  								<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
+									{% if 'plot_3a' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_3a" role="tab" aria-controls="{{amplicon_name}}_3a" aria-selected="true">Summary</button>
+  						  			</li>
+            								{% endif %}
+									{% if 'plot_3b' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_3b" role="tab" aria-controls="{{amplicon_name}}_3b" aria-selected="false">Indels</button>
+  						  			</li>
+            						{% endif %}
+  								</ul>
+  							</div>
+  							<div class='card-body'>
+								<div class="tab-content" id="pills-tabContent">
+									{% if 'plot_3a' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<div class="tab-pane fade show active" id="{{amplicon_name}}_3a" role="tabpanel">
+							  			{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_3a', amplicon_name=amplicon_name)}}
+						  			</div>
+									{% endif %}
+
+									{% if 'plot_3b' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<div class="tab-pane fade" id="{{amplicon_name}}_3b" role="tabpanel">
+							  			{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_3b', amplicon_name=amplicon_name)}}
+						  			</div>
+									{% endif %}
+  								</div>
+  							</div> {# end card_body #}
+  						</div> {# end card #}
+
+						<div class='card text-center mb-2 breakinpage'>
+  							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+  								<h5>Indel characterization</h5>
+								{% else %}
+  								<h5>Indel characterization for {{amplicon_name}}</h5>
+								{% endif %}
+  								<ul class="nav nav-tabs justify-content-center card-header-tabs" id="indel-characterization-tabs" role="tablist">
+									{% if 'plot_4a' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4a" role="tab" aria-controls="{{amplicon_name}}_4a" aria-selected="true">All Modifications Combined</button>
+  						  			</li>
+            								{% endif %}
+									{% if 'plot_4b' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4b" role="tab" aria-controls="{{amplicon_name}}_4b" aria-selected="false">All Modifications by Type</button>
+  						  			</li>
+            								{% endif %}
+									{% if 'plot_4c' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4c" role="tab" aria-controls="{{amplicon_name}}_4c" aria-selected="false">Modifications in Quantification Window</button>
+  						  			</li>
+            								{% endif %}
+									{% if 'plot_4d' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4d" role="tab" aria-controls="{{amplicon_name}}_4d" aria-selected="false">Indel Lengths</button>
+  						  			</li>
+            								{% endif %}
+									{% if 'plot_4e' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4e" role="tab" aria-controls="{{amplicon_name}}_4e" aria-selected="false">All reads aligned to {{amplicon_name}}</button>
+  						  			</li>
+								 	{% endif %}
+									{% if 'plot_4f' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4f" role="tab" aria-controls="{{amplicon_name}}_4f" aria-selected="false">HDR reads aligned to {{amplicon_name}}</button>
+  						  			</li>
+									{% endif %}
+  								</ul>
+  							</div> {# end card head #}
+  							<div class='card-body'>
+  								<div class="tab-content" id="pills-tabContent">
+									{% if 'plot_4a' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_4a" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4a', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+									{% if 'plot_4b' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade show active" id="{{amplicon_name}}_4b" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4b', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+									{% if 'plot_4c' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_4c" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4c', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+
+									{% if 'plot_4d' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_4d" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4d', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+
+									{% if 'plot_4e' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_4e" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4e', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+
+									{% if 'plot_4f' in report_data['figures']['locs'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_4f" role="tabpanel">
+										{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_4f', amplicon_name=amplicon_name)}}
+									</div>
+									{% endif %}
+  								</div>
+  							</div> {# end card body #}
+  						</div> {# end card #}
+
+
+						{% if 'plot_5' in report_data['figures']['locs'][amplicon_name] %}
+						<div class='card text-center mb-2 breakinpage'>
+							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Frameshift analysis</h5>
+								{% else %}
+  								<h5>Frameshift analysis for {{amplicon_name}}</h5>
+								{% endif %}
+							</div>
+  							<div class='card-body'>
+								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_5', amplicon_name=amplicon_name)}}
+							</div>
+						</div>
+						{% endif %}
+
+						{% if 'plot_6' in report_data['figures']['locs'][amplicon_name] %}
+						<div class='card text-center mb-2 breakinpage'>
+							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Frameshift mutagenesis profiles</h5>
+								{% else %}
+  								<h5>Frameshift mutagenesis profiles for {{amplicon_name}}</h5>
+								{% endif %}
+							</div>
+  							<div class='card-body'>
+								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_6', amplicon_name=amplicon_name)}}
+							</div>
+						</div>
+						{% endif %}
+
+						{% if 'plot_7' in report_data['figures']['locs'][amplicon_name] %}
+						<div class='card text-center mb-2 breakinpage'>
+							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Non-coding mutations</h5>
+								{% else %}
+  								<h5>Coding mutations for {{amplicon_name}}</h5>
+								{% endif %}
+							</div>
+  							<div class='card-body'>
+								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_7', amplicon_name=amplicon_name)}}
+							</div>
+						</div>
+						{% endif %}
+
+						{% if 'plot_8' in report_data['figures']['locs'][amplicon_name] %}
+						<div class='card text-center mb-2 breakinpage'>
+							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Splicing</h5>
+								{% else %}
+  								<h5>Splicing for {{amplicon_name}}</h5>
+								{% endif %}
+							</div>
+  							<div class='card-body'>
+								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_8', amplicon_name=amplicon_name)}}
+							</div>
+						</div>
+						{% endif %}
+
+						{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '9' in report_data['figures']['sgRNA_based_names'][amplicon_name] and report_data['figures']['sgRNA_based_names'][amplicon_name]['9']|length > 0 %}
+						<div class='card text-center mb-2 breakinpage'>
+							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Allele plots</h5>
+								{% else %}
+  								<h5>Allele plots for {{amplicon_name}}</h5>
+								{% endif %}
+							</div>
+							<div class='card-body'>
+								{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['9'] %}
+                  				<div class='mb-4'>
+					  				{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+                				</div>
+								{% endfor %}
+							</div>
+						</div>
+						{% endif %}
+
+						{% if 'plot_10a' in report_data['figures']['locs'][amplicon_name] %}
+  						<div class='card text-center mb-2 breakinpage'>
+  							<div class='card-header'>
+								{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
+								<h5>Base editing</h5>
+								{% else %}
+  								<h5>Base editing for {{amplicon_name}}</h5>
+								{% endif %}
+  								<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
+									{% if 'plot_10a' in report_data['figures']['locs'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10a" role="tab" aria-controls="{{amplicon_name}}_10a" aria-selected="true">Substitution Frequencies</button>
+  						  			</li>
+									{% endif %}
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10d' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10d" role="tab" aria-controls="{{amplicon_name}}_10d" aria-selected="false">Nucleotide Frequencies</button>
+  						  			</li>
+									{% endif %}
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10e' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10e" role="tab" aria-controls="{{amplicon_name}}_10e" aria-selected="false">Base Proportions</button>
+  						  			</li>
+            						{% endif %}
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10f' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+  						  			<li class="nav-item">
+						  				<button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10f" role="tab" aria-controls="{{amplicon_name}}_10f" aria-selected="false">Non-reference Bases</button>
+  						  			</li>
+            						{% endif %}
+  								</ul>
+							</div>
+							<div class='card-body'>
+								<div class="tab-content" id="pills-tabContent">
+					  				<div class="tab-pane fade show active" id="{{amplicon_name}}_10a" role="tabpanel">
+                						<div class='mb-3'>
+											{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_10a', amplicon_name=amplicon_name)}}
+                						</div>
+										<div class="mb-3">
+											{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_10b', amplicon_name=amplicon_name)}}
+                                        </div>
+                                        {% if 'plot_10c' in report_data['figures']['locs'][amplicon_name] %}
+                                        <div class="mb-3">
+											{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name='plot_10c', amplicon_name=amplicon_name)}}
+										</div>
+                                        {% endif %}
+									</div>
+
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10d' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_10d" role="tabpanel">
+										{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['10d'] %}
+										<div class='mb-4'>
+											{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+										</div>
+										{% endfor %}
+									</div>
+									{% endif %}
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10e' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+					  				<div class="tab-pane fade" id="{{amplicon_name}}_10e" role="tabpanel">
+						  				{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['10e'] %}
+										<div class='mb-4'>
+			  								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+  										</div>
+										{% endfor %}
+              						</div>
+									{% endif %}
+									{% if amplicon_name in report_data['figures']['sgRNA_based_names'] and '10f' in report_data['figures']['sgRNA_based_names'][amplicon_name] %}
+									<div class="tab-pane fade" id="{{amplicon_name}}_10f" role="tabpanel">
+										{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['10f'] %}
+                  						<div class='mb-4'>
+			  								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+                  						</div>
+										{% endfor %}
+										{% for fig_name in report_data['figures']['sgRNA_based_names'][amplicon_name]['10g'] %}
+                  						<div class='mb-4'>
+			  								{{ render_partial('shared/partials/fig_reports.html', report_data=report_data, fig_name=fig_name, amplicon_name=amplicon_name)}}
+                    					</div>
+										{% endfor %}
+									</div>
+								</div>
+							{% endif %}
+							</div>
+						</div> {# end card #}
+					{% endif %} {# end base editing card #}
+					</div> {# end this amplicon tab #} <!--end amp tab -->
+				{% endfor %}
+				</div> {# tab content #} <!-- end tab content -->
+			</div> <!-- end card -->
+
+            {{render_partial('shared/partials/report_footer_buttons.html', report_zip_filename=report_zip_filename, report_path=report_path)}}
+		</div> {# jumbotron_content #} <!-- end jumbotron_content -->
+	</div> {# jumbrotron #} <!-- end jumbotron -->
+</div> {# column #} <!-- end column -->
+</center>
+
+<div class="col-sm-1"></div>
+{% endblock %}
+
+{% block foot %}
+<script>
+{% if not C2PRO_INSTALLED %}
+function updateZoom(e) {
+  /*prevent any other actions that may occur when moving over the image:*/
+//  e.preventDefault();
+  var img = e.target.imgObj
+  var view = e.target.viewObj
+  var lens = e.target.lensObj
+
+  if (!lens.hasWidthSet)
+  {
+    view_height = $('#'+view.id).height()
+    view_width = $('#'+view.id).width()
+    img_height = $('#'+img.id).height()
+    img_width = $('#'+img.id).width()
+    lens_height = $('#'+lens.id).height()
+    lens_width = $('#'+lens.id).width()
+
+    new_width = img_height * view_width/view_height  //make up for loss of precision
+    $('#'+lens.id).outerWidth(new_width)
+    lens.hasWidthSet = true
+
+    cx = view_width / new_width
+
+    view.view_height = view_height
+    view.view_width = view_width
+    img.img_height = img_height
+    img.img_width = img_width
+    lens.lens_height = lens_height
+    lens.lens_width = new_width
+
+  }
+
+  var pos, x, y;
+  /*get the cursor's x and y positions:*/
+  pos = getCursorPos(e,img);
+  /*calculate the position of the lens:*/
+  x = pos.x - (lens.lens_width / 2);
+  /*prevent the lens from being positioned outside the image:*/
+  if (x > img.img_width - lens.lens_width) {x = img.img_width - lens.lens_width;;
+  }
+  if (x < 0) {x = 0;}
+  lens.style.left = x + "px";
+  view.style.backgroundPosition = "-" + (x * cx) + "px 0px";
+}
+
+function getCursorPos(e,img) {
+  var a, x = 0, y = 0;
+  e = e || window.event;
+  /*get the x and y positions of the image:*/
+  a = img.getBoundingClientRect();
+  /*calculate the cursor's x and y coordinates, relative to the image:*/
+  x = e.pageX - a.left;
+  y = e.pageY - a.top;
+  /*consider any page scrolling:*/
+  x = x - window.pageXOffset;
+  y = y - window.pageYOffset;
+  return {x : x, y : y};
+}
+
+var passiveSupported = false;
+try {
+  var options = {
+    get passive() { // This function will be called when the browser
+                    //   attempts to access the passive property.
+      passiveSupported = true;
+    }
+  };
+
+  window.addEventListener("test", options, options);
+  window.removeEventListener("test", options, options);
+} catch(err) {
+  passiveSupported = false;
+}
+
+	{% for amplicon_name in report_data.amplicons %}
+		{% if 'plot_2a' in report_data['figures']['locs'][amplicon_name] %}
+		view = document.getElementById('zoomview_nucs_{{amplicon_name}}');
+		img = document.getElementById('tozoom_nucs_{{amplicon_name}}');
+		lens = document.getElementById('zoomlens_nucs_{{amplicon_name}}')
+
+		img.viewObj = view
+		img.lensObj = lens
+		img.imgObj = img
+
+		lens.viewObj = view
+		lens.lensObj = lens
+		lens.imgObj = img
+
+		lens.addEventListener("mousemove", updateZoom, passiveSupported? { passive: true } : false);
+		img.addEventListener("mousemove", updateZoom, passiveSupported? { passive: true } : false);
+		/*and also for touch screens:*/
+		lens.addEventListener("touchmove", updateZoom, passiveSupported? { passive: true } : false);
+		img.addEventListener("touchmove", updateZoom, passiveSupported? { passive: true } : false);
+
+		{% endif %}
+	{% endfor %}
+{% endif %}
+</script>
+
+
+{% if C2PRO_INSTALLED %}
+<script src="https://unpkg.com/d3@5"></script>
+  {{ render_partial('partials/batch_d3.html', nucleotide_quilt_slugs=(report_data['nuc_quilt_names'])) }}
+{% endif %}
+
+{% endblock %}
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/failed_runs.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/failed_runs.html
new file mode 100644
index 00000000..aab88602
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/failed_runs.html
@@ -0,0 +1,65 @@
+{% if failed_runs|length > 0 %}
+<div class='card text-center mb-2'>
+    <div class="card-header bg-danger d-flex justify-content-center align-items-center" title="Click to expand all error messages" onclick="toggleAllDescriptions()">
+    <!-- Center the title and button -->
+    <h5 id="failed_runs" class="mb-0 text-white">Failed Runs</h5>
+    <button class="btn btn-danger ml-2" id="failedRunToggleAll" >
+        <i class="fas fa-chevron-down" id="failedRunChevronIcon"></i>
+    </button>
+    </div>
+
+    <div class='card-body p-0'>
+      <div class="list-group list-group-flush" style="max-height: 25vh; overflow-y: scroll;">
+        {% for failed_run in failed_runs %}
+        {# Toggle the description visibility on click #}
+        <a href="javascript:void(0)" class="list-group-item list-group-item-action failed-run-name bg-light text-dark"
+        id="failed_run_{{loop.index0}}" title="Click to expand error message" onclick="toggleDescription({{loop.index0}})">
+          {{failed_run}}
+        </a>
+        {# Initially hide the description and add a light background with dark text for readability #}
+        <div class="list-group-item text-dark failed-run-desc" id="failed_run_desc_{{loop.index0}}" style="display: none;">
+          {{failed_runs_desc[loop.index0]}}
+        </div>
+        {% endfor %}
+      </div>
+    </div>
+</div>
+
+<script>
+  // Function to toggle individual descriptions
+  function toggleDescription(index) {
+    var desc = document.getElementById("failed_run_desc_" + index);
+    desc.style.display = desc.style.display === "none" ? "block" : "none";
+    updateChevron();
+  }
+
+  // Function to toggle all descriptions
+  function toggleAllDescriptions() {
+    var descs = document.querySelectorAll('.failed-run-desc');
+    var allHidden = Array.from(descs).every(desc => desc.style.display === "none");
+
+    descs.forEach(desc => {
+      desc.style.display = allHidden ? "block" : "none";
+    });
+
+    updateChevron();
+  }
+
+  // Function to update the chevron direction based on the visibility of the descriptions
+  function updateChevron() {
+    var descs = document.querySelectorAll('.failed-run-desc');
+    var allHidden = Array.from(descs).every(desc => desc.style.display === "none");
+    var failedRunChevronIcon = document.getElementById("failedRunChevronIcon");
+    if (allHidden) {
+      failedRunChevronIcon.classList.remove("fa-chevron-up");
+      failedRunChevronIcon.classList.add("fa-chevron-down");
+    } else {
+      failedRunChevronIcon.classList.remove("fa-chevron-down");
+      failedRunChevronIcon.classList.add("fa-chevron-up");
+    }
+  }
+
+  // Call updateChevron on page load to set the initial chevron direction
+  document.addEventListener('DOMContentLoaded', updateChevron);
+</script>
+{% endif %}
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_reports.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_reports.html
new file mode 100644
index 00000000..96e93e5f
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_reports.html
@@ -0,0 +1,36 @@
+<div id="fig_{{fig_name}}" class="d-flex flex-column">
+    {# Set the width based on the plot #}
+    {%- if fig_name in ['plot_1a', 'plot_1b', 'plot_1c', 'plot_1d', 'plot_3a', 'plot_4a', 'plot_4b', 'plot_4c', 'plot_4e', 'plot_4f', 'plot_5a', 'plot_7', 'plot_8', 'plot_8a', 'plot_11c'] -%}
+        {% set width = '40%' %}
+    {%- elif fig_name in ['plot_10b', 'plot_10c'] -%}
+        {% set width = '35%' %}
+    {%- elif fig_name in ['plot_10a'] -%}
+        {% set width = '70%' %}
+    {%- else -%}
+        {% set width = '100%' %}
+    {%- endif -%}
+
+    {%- if amplicon_name is defined -%}
+        {%- if 'htmls' in report_data['figures'] and fig_name in report_data['figures']['htmls'][amplicon_name] -%}
+            {{report_data['figures']['htmls'][amplicon_name][fig_name]|safe}}
+        {%- elif fig_name in report_data['figures']['locs'][amplicon_name] -%}
+            <a href="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][amplicon_name][fig_name]}}.png" width='{{width}}'></a>
+        {% endif -%}
+        <label class="labelpadding">{{report_data['figures']['captions'][amplicon_name][fig_name]}}</label>
+        {%- for (data_label,data_path) in report_data['figures']['datas'][amplicon_name][fig_name] %}
+            <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
+        {%- endfor -%}
+    {%- else %}
+        {%- if 'htmls' in report_data['figures'] and fig_name in report_data['figures']['htmls'] -%}
+            {{report_data['figures']['htmls'][fig_name]|safe}}
+        {%- elif fig_name in report_data['figures']['locs'] -%}
+            <a href="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['figures']['locs'][fig_name]}}.png" width='{{width}}'></a>
+        {% endif -%}
+        {% if fig_name in report_data['figures']['captions'] and fig_name in report_data['figures']['datas'] %}
+            <label class="labelpadding">{{report_data['figures']['captions'][fig_name]}}</label>
+            {%- for (data_label,data_path) in report_data['figures']['datas'][fig_name] %}
+                <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
+            {%- endfor -%}
+        {%- endif %}
+    {%- endif %}
+</div>
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_summaries.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_summaries.html
new file mode 100644
index 00000000..096828d3
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/fig_summaries.html
@@ -0,0 +1,16 @@
+<div id="fig_summary_{{plot_name}}">
+    {% if 'htmls' in report_data and plot_name in report_data['htmls']%}
+        {{report_data['htmls'][plot_name]|safe}}
+    {% else %}
+        {% if plot_name in ['Nucleotide_conversion_map', 'Nucleotide_percentage_quilt'] %}
+            <a href="{{report_data['crispresso_data_path']}}{{plot_name}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{plot_name}}.png" width='100%'></a>
+        {% else %}
+            <a href="{{report_data['crispresso_data_path']}}{{plot_name}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{plot_name}}.png" width='70%'></a>
+        {% endif %}
+    {% endif %}
+    <label class="labelpadding">{{report_data['labels'][plot_name]}}</label>
+    {% for (data_label,data_path) in report_data['datas'][plot_name] %}
+        <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
+    {% endfor %}
+    <br>
+</div>
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/guardrail_warnings.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/guardrail_warnings.html
new file mode 100644
index 00000000..49b74978
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/guardrail_warnings.html
@@ -0,0 +1,5 @@
+{% if 'guardrails_htmls' in report_data['run_data']['results'] and report_data['run_data']['results']['guardrails_htmls']|length > 0 %}
+	{% for message in report_data['run_data']['results']['guardrails_htmls'] %}
+		{{message | safe}}
+	{% endfor %}
+{% endif %}
\ No newline at end of file
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/log_params.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/log_params.html
new file mode 100644
index 00000000..ad56e16a
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/log_params.html
@@ -0,0 +1,30 @@
+<div class="tab-pane fade text-start screen-only" id="log_params" role="tabpanel">
+  <p><strong>CRISPResso version:</strong> {{report_data['run_data']['running_info']['version']}}</p>
+  <p><strong>Run completed:</strong> {{report_data['run_data']['running_info']['end_time_string']}}</p>
+  <p><strong>Amplicon sequence:</strong> <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args']['amplicon_seq']}}</pre></p>
+  {% if report_data['run_data']['running_info']['args']['guide_seq'] != '' %}
+    <p><strong>Guide sequence:</strong> <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args']['guide_seq']}}</pre></p>
+  {% endif %}
+  <p><strong>Command used:</strong> <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['command_used']}}</pre></p>
+  <p><strong>Parameters:</strong> <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args_string']}}</pre></p>
+  {% if is_web and 'metadata' in report_data and report_data['metadata'].keys() %}
+  <p><strong>Metadata:</strong></p>
+  <table class="table table-bordered table-hover table-striped">
+    <thead class="thead-light">
+      <tr>
+        <th>Key</th>
+        <th>Value</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for key in report_data['metadata'].keys() %}
+      <tr>
+        <td>{{key}}</td>
+        <td>{{report_data['metadata'][key]}}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+  {% endif %}
+  <p class='text-center m-0'><small><a href="{{report_data['crispresso_data_path']}}{{report_data['run_data']['running_info']['log_filename']}}">Running log</a></small></p>
+</div>
diff --git a/CRISPResso2/CRISPRessoReports/templates/shared/partials/report_footer_buttons.html b/CRISPResso2/CRISPRessoReports/templates/shared/partials/report_footer_buttons.html
new file mode 100644
index 00000000..fa6ab71d
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/shared/partials/report_footer_buttons.html
@@ -0,0 +1,9 @@
+<div align="center" class='p-3'>
+  {% if is_web -%}
+    {%- if report_zip_filename -%}
+      <a href="/reports_data/{{report_zip_filename}}" class="btn btn-primary" role="button"><i class="fas fa-download"></i> Download report</a>
+    {% endif %}
+      <a href="{{report_path}}" class="btn btn-primary" role="button"><i class="fas fa-link"></i> Link to report</a>
+  {% endif %}
+  <button class='btn btn-primary hidden-print' onclick='window.print();'><i class="fas fa-print"></i> Print</button>
+</div>
diff --git a/CRISPResso2/CRISPRessoReports/templates/wgsReport.html b/CRISPResso2/CRISPRessoReports/templates/wgsReport.html
new file mode 100644
index 00000000..8e1e35be
--- /dev/null
+++ b/CRISPResso2/CRISPRessoReports/templates/wgsReport.html
@@ -0,0 +1,94 @@
+{% extends "layout.html" %}
+{% block head %}
+<script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
+<style>
+.nav-tabs.amp-header {
+  border-bottom:none !important;
+}
+
+.card-header.amp-header {
+  border-bottom:none !important;
+}
+.nav-tabs.amp-header .nav-link.active {
+  background-color:lightsteelblue;
+  border-bottom:lightsteelblue;
+}
+
+.tab-content.amp-body {
+  background-color:lightsteelblue;
+}
+
+@media print {
+   .tab-content > .tab-pane {
+    display: block !important;
+    opacity: 1 !important;
+    visibility: visible !important;
+		margin-bottom: 2em !important;
+		page-break-inside: avoid;
+  }
+  .nav-tabs {
+    display:none !important;
+    visibility:hidden !important;
+  }
+  .tab-content.amp-body {
+    background-color:transparent !important;
+  	border:None !important;
+  }
+}
+@media only screen and (max-width: 600px) {
+	.jumbotron img {
+		width:100%
+	}
+}
+</style>
+
+{% endblock %}
+
+{% block content %}
+<div class="row">
+<div class="col-sm-1"></div>
+<div class="col-sm-10">
+
+	<div class="jumbotron" style="background:rgba(0,0,0,0.0); padding:0px" >
+		<div id='jumbotron_content' >
+
+            <div class="card text-center mb-2">
+                <div class="card-header">
+                    <h5 id="{{report_name}}">{{report_name}}</h5>
+                </div>
+                <div class="card-body p-0">
+                    <div class="list-group list-group-flush">
+                        {% for region_name in run_names %}
+                        <a href="{{sub_html_files[region_name]}}" class="list-group-item list-group-item-action" id="{{region_name}}">{{region_name}}</a>
+                        {% endfor %}
+                    </div>
+                </div>
+            </div>
+
+            {{render_partial('shared/partials/failed_runs.html', failed_runs=failed_runs, failed_runs_desc=failed_runs_desc)}}
+
+            {% if report_data['names']|length > 0 %}
+            {% for plot_name in report_data['names'] %}
+            <div class='card text-center mb-2'>
+              <div class='card-header'>
+                <h5>{{report_data['titles'][plot_name]}}</h5>
+              </div>
+              <div class='card-body' style='max-height:80vh;overflow-y:auto'>
+		        {{ render_partial('shared/partials/fig_summaries.html', report_data=report_data, plot_name=plot_name) }}
+              </div>
+            </div>
+            {% endfor %}
+          {% endif %}
+
+          {{render_partial('shared/partials/report_footer_buttons.html', report_zip_filename=report_zip_filename, report_path=report_path)}}
+	</div> {# jumbotron_content #} <!-- end jumbotron_content -->
+</div> {# jumbrotron #} <!-- end jumbotron -->
+
+</div> {# column #} <!-- end column -->
+
+<div class="col-sm-1"></div>
+</div>
+{% endblock %}
+
+{% block foot %}
+{% endblock %}
diff --git a/CRISPResso2/CRISPRessoShared.py b/CRISPResso2/CRISPRessoShared.py
index 7d39dfde..fb13e000 100644
--- a/CRISPResso2/CRISPRessoShared.py
+++ b/CRISPResso2/CRISPRessoShared.py
@@ -9,6 +9,11 @@
 import errno
 import gzip
 import json
+import sys
+import textwrap
+import importlib.util
+from pathlib import Path
+
 import numpy as np
 import os
 import pandas as pd
@@ -19,73 +24,90 @@
 import subprocess as sb
 import unicodedata
 import logging
+from inspect import getmodule, stack
 
 from CRISPResso2 import CRISPResso2Align
 from CRISPResso2 import CRISPRessoCOREResources
 
-__version__ = "2.2.13"
+__version__ = "2.3.2"
 
 
 ###EXCEPTIONS############################
-class FlashException(Exception):
+class FastpException(Exception):
     pass
 
-class TrimmomaticException(Exception):
-    pass
 
 class NoReadsAlignedException(Exception):
     pass
 
+
 class AlignmentException(Exception):
     pass
 
+
 class SgRNASequenceException(Exception):
     pass
 
+
 class NTException(Exception):
     pass
 
+
 class ExonSequenceException(Exception):
     pass
 
+
 class DuplicateSequenceIdException(Exception):
     pass
 
+
 class NoReadsAfterQualityFilteringException(Exception):
     pass
 
+
 class BadParameterException(Exception):
     pass
 
+
 class AutoException(Exception):
     pass
 
+
 class OutputFolderIncompleteException(Exception):
     pass
 
+
 class InstallationException(Exception):
     pass
 
+
 class InputFileFormatException(Exception):
     pass
 
+
+class PlotException(Exception):
+    pass
+
+
 #########################################
 
 class StatusFormatter(logging.Formatter):
     def format(self, record):
         record.percent_complete = ''
         if record.args and 'percent_complete' in record.args:
-            record.percent_complete = '{0:.2f}% '.format(record.args['percent_complete'])
+            record.percent_complete = float(record.args['percent_complete'])
             self.last_percent_complete = record.percent_complete
         elif hasattr(self, 'last_percent_complete'): # if we don't have a percent complete, use the last one
             record.percent_complete = self.last_percent_complete
+        else:
+            record.percent_complete = 0.0
         return super().format(record)
 
 
 class StatusHandler(logging.FileHandler):
     def __init__(self, filename):
         super().__init__(filename, 'w')
-        self.setFormatter(StatusFormatter('%(percent_complete)s%(message)s'))
+        self.setFormatter(StatusFormatter('{\n  "message": "%(message)s",\n  "percent_complete": %(percent_complete)s\n}'))
 
     def emit(self, record):
         """Overwrite the existing file and write the new log."""
@@ -100,8 +122,8 @@ def emit(self, record):
 class LogStreamHandler(logging.StreamHandler):
     def __init__(self, stream=None):
         super().__init__(stream)
-        self.setFormatter(logging.Formatter(
-            '%(levelname)-5s @ %(asctime)s:\n\t %(message)s \n',
+        self.setFormatter(StatusFormatter(
+            '%(levelname)-5s @ %(asctime)s (%(percent_complete).1f%% done):\n\t %(message)s \n',
             datefmt='%a, %d %b %Y %H:%M:%S',
         ))
         self.setLevel(logging.INFO)
@@ -121,250 +143,62 @@ def set_console_log_level(logger, level, debug=False):
             break
 
 
-def getCRISPRessoArgParser(parser_title="CRISPResso Parameters", required_params=[], suppress_params=[]):
-    parser = argparse.ArgumentParser(description=parser_title, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+class CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
+    def _split_lines(self, text, width):
+        if text.startswith('R|'):
+            return list(map(
+                lambda x: textwrap.fill(x, width, subsequent_indent=' ' * 24),
+                text[2:].splitlines(),
+            ))
+        return argparse.HelpFormatter._split_lines(self, text, width)
+
+
+def getCRISPRessoArgParser(tool, parser_title="CRISPResso Parameters"):
+    parser = argparse.ArgumentParser(description=parser_title, formatter_class=CustomHelpFormatter)
     parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
-    parser.add_argument('-r1', '--fastq_r1', type=str, help='First fastq file', default='',
-                        required='fastq_r1' in required_params)
-    parser.add_argument('-r2', '--fastq_r2', type=str, help='Second fastq file for paired end reads', default='')
-    parser.add_argument('-a', '--amplicon_seq', type=str,
-                        help='Amplicon Sequence (can be comma-separated list of multiple sequences)',
-                        required='amplicon_seq' in required_params)
-    parser.add_argument('-an', '--amplicon_name', type=str,
-                        help='Amplicon Name (can be comma-separated list of multiple names, corresponding to amplicon sequences given in --amplicon_seq',
-                        default='Reference')
-    parser.add_argument('-amas', '--amplicon_min_alignment_score', type=str,
-                        help='Amplicon Minimum Alignment Score; score between 0 and 100; sequences must have at least this homology score with the amplicon to be aligned (can be comma-separated list of multiple scores, corresponding to amplicon sequences given in --amplicon_seq)',
-                        default="")
-    parser.add_argument('--default_min_aln_score', '--min_identity_score', type=int,
-                        help='Default minimum homology score for a read to align to a reference amplicon', default=60)
-    parser.add_argument('--expand_ambiguous_alignments',
-                        help='If more than one reference amplicon is given, reads that align to multiple reference amplicons will count equally toward each amplicon. Default behavior is to exclude ambiguous alignments.',
-                        action='store_true')
-    parser.add_argument('--assign_ambiguous_alignments_to_first_reference',
-                        help='If more than one reference amplicon is given, ambiguous reads that align with the same score to multiple amplicons will be assigned to the first amplicon. Default behavior is to exclude ambiguous alignments.',
-                        action='store_true')
-    parser.add_argument('-g', '--guide_seq', '--sgRNA',
-                        help="sgRNA sequence, if more than one, please separate by commas. Note that the sgRNA needs to be input as the guide RNA sequence (usually 20 nt) immediately adjacent to but not including the PAM sequence (5' of NGG for SpCas9). If the PAM is found on the opposite strand with respect to the Amplicon Sequence, ensure the sgRNA sequence is also found on the opposite strand. The CRISPResso convention is to depict the expected cleavage position using the value of the parameter '--quantification_window_center' nucleotides from the 3' end of the guide. In addition, the use of alternate nucleases besides SpCas9 is supported. For example, if using the Cpf1 system, enter the sequence (usually 20 nt) immediately 3' of the PAM sequence and explicitly set the '--cleavage_offset' parameter to 1, since the default setting of -3 is suitable only for SpCas9.",
-                        default='')
-    parser.add_argument('-gn', '--guide_name', help="sgRNA names, if more than one, please separate by commas.",
-                        default='')
-    parser.add_argument('-fg', '--flexiguide_seq',
-                        help="sgRNA sequence (flexible) (can be comma-separated list of multiple flexiguides). The flexiguide sequence will be aligned to the amplicon sequence(s), as long as the guide sequence has homology as set by --flexiguide_homology.")
-    parser.add_argument('-fh', '--flexiguide_homology', type=int,
-                        help="flexiguides will yield guides in amplicons with at least this homology to the flexiguide sequence.",
-                        default=80)
-    parser.add_argument('-fgn', '--flexiguide_name', help="flexiguide name", default='')
-    parser.add_argument('--discard_guide_positions_overhanging_amplicon_edge',
-                        help="If set, for guides that align to multiple positions, guide positions will be discarded if plotting around those regions would included bp that extend beyond the end of the amplicon. ",
-                        action='store_true')
-    parser.add_argument('-e', '--expected_hdr_amplicon_seq', help='Amplicon sequence expected after HDR', default='')
-    parser.add_argument('-c', '--coding_seq',
-                        help='Subsequence/s of the amplicon sequence covering one or more coding sequences for frameshift analysis. If more than one (for example, split by intron/s), please separate by commas.',
-                        default='')
-
-    # quality filtering options
-    parser.add_argument('-q', '--min_average_read_quality', type=int,
-                        help='Minimum average quality score (phred33) to keep a read', default=0)
-    parser.add_argument('-s', '--min_single_bp_quality', type=int,
-                        help='Minimum single bp score (phred33) to keep a read', default=0)
-    parser.add_argument('--min_bp_quality_or_N', type=int,
-                        help='Bases with a quality score (phred33) less than this value will be set to "N"', default=0)
-
-    # output options
-    parser.add_argument('--file_prefix', help='File prefix for output plots and tables', default='')
-    parser.add_argument('-n', '--name',
-                        help='Output name of the report (default: the name is obtained from the filename of the fastq file/s used in input)',
-                        default='')
-    parser.add_argument("--suppress_amplicon_name_truncation",help="If set, amplicon names will not be truncated when creating output filename prefixes. If not set, amplicon names longer than 21 characters will be truncated when creating filename prefixes.",
-                        action='store_true')
-    parser.add_argument('-o', '--output_folder', help='Output folder to use for the analysis (default: current folder)',
-                        default='')
-    parser.add_argument('-v', '--verbosity', type=int, help='Verbosity level of output to the console (1-4), 4 is the most verbose', default=3)
-
-    ## read preprocessing params
-    parser.add_argument('--split_interleaved_input', '--split_paired_end',
-                        help='Splits a single fastq file containing paired end reads into two files before running CRISPResso',
-                        action='store_true')
-    parser.add_argument('--trim_sequences', help='Enable the trimming of Illumina adapters with Trimmomatic',
-                        action='store_true')
-    parser.add_argument('--trimmomatic_command', type=str, help='Command to run trimmomatic', default='trimmomatic')
-    parser.add_argument('--trimmomatic_options_string', type=str,
-                        help='Override options for Trimmomatic, e.g. "ILLUMINACLIP:/data/NexteraPE-PE.fa:0:90:10:0:true"',
-                        default='')
-    parser.add_argument('--flash_command', type=str, help='Command to run flash', default='flash')
-    parser.add_argument('--min_paired_end_reads_overlap', type=int,
-                        help='Parameter for the FLASH read merging step. Minimum required overlap length between two reads to provide a confident overlap. ',
-                        default=10)
-    parser.add_argument('--max_paired_end_reads_overlap', type=int,
-                        help='Parameter for the FLASH merging step.  Maximum overlap length expected in approximately 90%% of read pairs. Please see the FLASH manual for more information.',
-                        default=100)
-    parser.add_argument('--stringent_flash_merging',
-                        help='Use stringent parameters for flash merging. In the case where flash could merge R1 and R2 reads ambiguously, the expected overlap is calculated as 2*average_read_length - amplicon_length. The flash parameters for --min-overlap and --max-overlap will be set to prefer merged reads with length within 10bp of the expected overlap. These values override the --min_paired_end_reads_overlap or --max_paired_end_reads_overlap CRISPResso parameters.',
-                        action='store_true')
-    parser.add_argument('--force_merge_pairs', help=argparse.SUPPRESS,
-                        action='store_true')  # help=Force-merges R1 and R2 if they cannot be merged using flash (use with caution -- may create non-biological apparent indels at the joining site)
-
-    # quantification window params
-    parser.add_argument('-w', '--quantification_window_size', '--window_around_sgrna', type=str,
-                        help='Defines the size (in bp) of the quantification window extending from the position specified by the "--cleavage_offset" or "--quantification_window_center" parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp. Multiple quantification window sizes (corresponding to each guide specified by --guide_seq) can be specified with a comma-separated list.',
-                        default='1')
-    parser.add_argument('-wc', '--quantification_window_center', '--cleavage_offset', type=str,
-                        help="Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17 to only include mutations near the 5' end of the sgRNA. Multiple quantification window centers (corresponding to each guide specified by --guide_seq) can be specified with a comma-separated list.",
-                        default='-3')
-    #    parser.add_argument('--cleavage_offset', type=str, help="Predicted cleavage position for cleaving nucleases with respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. The default value of -3 is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. To suppress the cleavage offset, enter 'N'.", default=-3)
-    parser.add_argument('--exclude_bp_from_left', type=int,
-                        help='Exclude bp from the left side of the amplicon sequence for the quantification of the indels',
-                        default=15)
-    parser.add_argument('--exclude_bp_from_right', type=int,
-                        help='Exclude bp from the right side of the amplicon sequence for the quantification of the indels',
-                        default=15)
-    parser.add_argument('--use_legacy_insertion_quantification',
-                        help='If set, the legacy insertion quantification method will be used (i.e. with a 1bp quantification window, indels at the cut site and 1bp away from the cut site would be quantified). By default (if this parameter is not set) with a 1bp quantification window, only insertions at the cut site will be quantified.',
-                        action='store_true')
-
-    parser.add_argument('--ignore_substitutions',
-                        help='Ignore substitutions events for the quantification and visualization',
-                        action='store_true')
-    parser.add_argument('--ignore_insertions', help='Ignore insertions events for the quantification and visualization',
-                        action='store_true')
-    parser.add_argument('--ignore_deletions', help='Ignore deletions events for the quantification and visualization',
-                        action='store_true')
-    parser.add_argument('--discard_indel_reads',
-                        help='Discard reads with indels in the quantification window from analysis',
-                        action='store_true')
-
-    # alignment parameters
-    parser.add_argument('--needleman_wunsch_gap_open', type=int, help='Gap open option for Needleman-Wunsch alignment',
-                        default=-20)
-    parser.add_argument('--needleman_wunsch_gap_extend', type=int,
-                        help='Gap extend option for Needleman-Wunsch alignment', default=-2)
-    parser.add_argument('--needleman_wunsch_gap_incentive', type=int,
-                        help='Gap incentive value for inserting indels at cut sites', default=1)
-    parser.add_argument('--needleman_wunsch_aln_matrix_loc', type=str,
-                        help='Location of the matrix specifying substitution scores in the NCBI format (see ftp://ftp.ncbi.nih.gov/blast/matrices/)',
-                        default='EDNAFULL')
-    parser.add_argument('--aln_seed_count', type=int, default=5,
-                        help=argparse.SUPPRESS)  # help='Number of seeds to test whether read is forward or reverse',default=5)
-    parser.add_argument('--aln_seed_len', type=int, default=10,
-                        help=argparse.SUPPRESS)  # help='Length of seeds to test whether read is forward or reverse',default=10)
-    parser.add_argument('--aln_seed_min', type=int, default=2,
-                        help=argparse.SUPPRESS)  # help='number of seeds that must match to call the read forward/reverse',default=2)
-
-    # plotting parameters
-    parser.add_argument('--plot_histogram_outliers',
-                        help="If set, all values will be shown on histograms. By default (if unset), histogram ranges are limited to plotting data within the 99 percentile.",
-                        action='store_true')
-
-    # allele plot parameters
-    parser.add_argument('--plot_window_size', '--offset_around_cut_to_plot', type=int,
-                        help='Defines the size of the window extending from the quantification window center to plot. Nucleotides within plot_window_size of the quantification_window_center for each guide are plotted.',
-                        default=20)
-    parser.add_argument('--min_frequency_alleles_around_cut_to_plot', type=float,
-                        help='Minimum %% reads required to report an allele in the alleles table plot.', default=0.2)
-    parser.add_argument('--expand_allele_plots_by_quantification',
-                        help='If set, alleles with different modifications in the quantification window (but not necessarily in the plotting window (e.g. for another sgRNA)) are plotted on separate lines, even though they may have the same apparent sequence. To force the allele plot and the allele table to be the same, set this parameter. If unset, all alleles with the same sequence will be collapsed into one row.',
-                        action='store_true')
-    parser.add_argument('--allele_plot_pcts_only_for_assigned_reference',
-                        help='If set, in the allele plots, the percentages will show the percentage as a percent of reads aligned to the assigned reference. Default behavior is to show percentage as a percent of all reads.',
-                        action='store_true')
-    parser.add_argument('-qwc', '--quantification_window_coordinates', type=str,
-                        help='Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the "--quantification_window_center", "--cleavage_offset", "--window_around_sgrna" or "--window_around_sgrna" values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separted by the dash sign (e.g. "start-stop"), and multiple ranges can be separated by the underscore (_). ' +
-                             'A value of 0 disables this filter. (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 5th-10th bp in the first reference and the 5th-10th and 20th-30th bp in the second reference)',
-                        default=None)
-    parser.add_argument('--annotate_wildtype_allele', type=str,
-                        help='Wildtype alleles in the allele table plots will be marked with this string (e.g. **).',
-                        default='')
-
-    # output parameters
-    parser.add_argument('--keep_intermediate', help='Keep all the  intermediate files', action='store_true')
-    parser.add_argument('--dump', help='Dump numpy arrays and pandas dataframes to file for debugging purposes',
-                        action='store_true')
-    parser.add_argument('--write_detailed_allele_table',
-                        help='If set, a detailed allele table will be written including alignment scores for each read sequence.',
-                        action='store_true')
-    parser.add_argument('--fastq_output', help='If set, a fastq file with annotations for each read will be produced.',
-                        action='store_true')
-    parser.add_argument('--bam_output', help='If set, a bam file with alignments for each read will be produced.',
-                        action='store_true')
-    parser.add_argument('-x', '--bowtie2_index', type=str, help='Basename of Bowtie2 index for the reference genome',
-                        default='')
-    parser.add_argument('--zip_output', help="If set, the output will be placed in a zip folder.", action='store_true')
-
-    # report style parameters
-    parser.add_argument('--max_rows_alleles_around_cut_to_plot', type=int,
-                        help='Maximum number of rows to report in the alleles table plot.', default=50)
-    parser.add_argument('--suppress_report', help='Suppress output report', action='store_true')
-    parser.add_argument('--place_report_in_output_folder',
-                        help='If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output.',
-                        action='store_true')
-    parser.add_argument('--suppress_plots', help='Suppress output plots', action='store_true')
-    parser.add_argument('--write_cleaned_report', action='store_true',
-                        help=argparse.SUPPRESS)  # trims working directories from output in report (for web access)
-
-    # base editor parameters
-    parser.add_argument('--base_editor_output',
-                        help='Outputs plots and tables to aid in analysis of base editor studies.', action='store_true')
-    parser.add_argument('--conversion_nuc_from',
-                        help='For base editor plots, this is the nucleotide targeted by the base editor', default='C')
-    parser.add_argument('--conversion_nuc_to',
-                        help='For base editor plots, this is the nucleotide produced by the base editor', default='T')
-
-    # prime editing parameters
-    parser.add_argument('--prime_editing_pegRNA_spacer_seq', type=str,
-                        help="pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the given sequence.",
-                        default='')
-    parser.add_argument('--prime_editing_pegRNA_extension_seq', type=str,
-                        help="Extension sequence used in prime editing. The sequence should be given in the RNA 5'->3' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS).",
-                        default='')
-    parser.add_argument('--prime_editing_pegRNA_extension_quantification_window_size', type=int,
-                        help="Quantification window size (in bp) at flap site for measuring modifications anchored at the right side of the extension sequence. Similar to the --quantification_window parameter, the total length of the quantification window will be 2x this parameter. Default: 5bp (10bp total window size)",
-                        default=5)
-    parser.add_argument('--prime_editing_pegRNA_scaffold_seq', type=str,
-                        help="If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that the RT template directly follows this sequence. A common value is 'GGCACCGAGUCGGUGC'.",
-                        default='')
-    parser.add_argument('--prime_editing_pegRNA_scaffold_min_match_length', type=int,
-                        help="Minimum number of bases matching scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences.",
-                        default=1)
-    parser.add_argument('--prime_editing_nicking_guide_seq', type=str,
-                        help="Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence",
-                        default='')
-    parser.add_argument('--prime_editing_override_prime_edited_ref_seq', type=str,
-                        help="If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence.",
-                        default='')
-    parser.add_argument('--prime_editing_override_sequence_checks',
-                        help="If set, checks to assert that the prime editing guides and extension sequence are in the proper orientation are not performed. This may be useful if the checks are failing inappropriately, but the user is confident that the sequences are correct.",
-                        action='store_true')
-
-    # special running modes
-    parser.add_argument('--crispresso1_mode', help='Parameter usage as in CRISPResso 1', action='store_true')
-    parser.add_argument('--dsODN', help='Label reads with the dsODN sequence provided', default='')
-    parser.add_argument('--auto', help='Infer amplicon sequence from most common reads', action='store_true')
-    parser.add_argument('--debug', help='Show debug messages', action='store_true')
-    parser.add_argument('--no_rerun',
-                        help="Don't rerun CRISPResso2 if a run using the same parameters has already been finished.",
-                        action='store_true')
-    parser.add_argument('-p', '--n_processes', type=str, help='Specify the number of processes to use for analysis.\
-    Please use with caution since increasing this parameter will significantly increase the memory required to run CRISPResso. Can be set to \'max\'.',
-                        default='1')
-
-    # processing of aligned bam files
-    if 'bam_input' not in suppress_params:
-        parser.add_argument('--bam_input', type=str, help='Aligned reads for processing in bam format', default='')
-    if 'bam_chr_loc' not in suppress_params:
-        parser.add_argument('--bam_chr_loc', type=str,
-                        help='Chromosome location in bam for reads to process. For example: "chr1:50-100" or "chrX".',
-                        default='')
-
-    # deprecated params
-    parser.add_argument('--save_also_png', default=False,
-                        help=argparse.SUPPRESS)  # help='Save also .png images in addition to .pdf files') #depreciated -- now pngs are automatically created. Pngs can be suppressed by '--suppress_report'
 
+    # Getting the directory of the current script
+    current_dir = Path(__file__).parent
+
+    # Adjusting the path to point directly to the args.json file
+    json_path = current_dir / 'args.json'
+
+    with open(json_path, 'r') as json_file:
+        args_dict = json.load(json_file)
+        args_dict = args_dict["CRISPResso_args"]
+    type_mapper = {
+        "str": str,
+        "int": int,
+        "float": float,
+    }
+
+    for key, value in args_dict.items():
+        # print(key, value)
+        tools = value.get('tools', [])  # Default to empty list if 'tools' is not found
+        if tool in tools:
+            action = value.get('action')  # Use None as default if 'action' is not found
+            required = value.get('required', False)  # Use False as default if 'required' is not found
+            default = value.get('default')  # Use None as default if 'default' is not found
+            type_value = value.get('type', 'str')  # Assume 'str' as default type if 'type' is not specified
+            arg_help = value.get('help', '') if value.get('help') != "SUPPRESS" else argparse.SUPPRESS
+
+            # Determine the correct function based on conditions
+            if action:
+                parser.add_argument(*value['keys'], help=arg_help, action=action)
+            elif required and default is None:  # Checks if 'required' is true and 'default' is not provided
+                parser.add_argument(*value['keys'], help=arg_help, type=type_mapper[type_value], required=True)
+            elif required:  # Checks if 'required' is true (default is provided, as checked above)
+                parser.add_argument(*value['keys'], help=arg_help, default=default, type=type_mapper[type_value], required=True)
+            else:  # Case when neither 'action' nor 'required' conditions are met
+                # Here, it handles the case where default might be None, which is a valid scenario
+                kwargs = {'help': arg_help, 'type': type_mapper[type_value]}
+                if default is not None: kwargs['default'] = default  # Add 'default' only if it's specified
+                parser.add_argument(*value['keys'], **kwargs)
     return parser
 
 
-def get_crispresso_options():
-    parser = getCRISPRessoArgParser(parser_title="Temp Params", required_params=[])
+def get_core_crispresso_options():
+    parser = getCRISPRessoArgParser("Core")
     crispresso_options = set()
     d = parser.__dict__['_option_string_actions']
     for key in d.keys():
@@ -374,7 +208,7 @@ def get_crispresso_options():
     return crispresso_options
 
 
-def get_crispresso_options_lookup():
+def get_crispresso_options_lookup(tool):
     ##dict to lookup abbreviated params
     #    crispresso_options_lookup = {
     #    'r1':'fastq_r1',
@@ -384,7 +218,7 @@ def get_crispresso_options_lookup():
     #    .....
     # }
     crispresso_options_lookup = {}
-    parser = getCRISPRessoArgParser(parser_title="Temp Params", required_params=[])
+    parser = getCRISPRessoArgParser(tool)
     d = parser.__dict__['_option_string_actions']
     for key in d.keys():
         d2 = d[key].__dict__['dest']
@@ -523,7 +357,7 @@ def clean_filename(filename):
     validFilenameChars = "+-_.()%s%s" % (string.ascii_letters, string.digits)
     filename = slugify(str(filename).replace(' ', '_'))
     cleanedFilename = unicodedata.normalize('NFKD', filename)
-    return(''.join(c for c in cleanedFilename if c in validFilenameChars))
+    return (''.join(c for c in cleanedFilename if c in validFilenameChars))
 
 def check_file(filename):
     try:
@@ -612,7 +446,7 @@ def assert_fastq_format(file_path, max_lines_to_check=100):
     params:
         file_path: path to fastq file
         max_lines_to_check: number of lines to check in the file
-    returns:   
+    returns:
         True if the file is in the correct format
     """
 
@@ -861,7 +695,7 @@ def write_crispresso_info(crispresso_output_file, crispresso2_info):
 
     """
     with open(crispresso_output_file, 'w') as fh:
-        json.dump(crispresso2_info, fh, cls=CRISPRessoJSONEncoder)
+        json.dump(crispresso2_info, fh, cls=CRISPRessoJSONEncoder, indent=2)
 
 
 def get_command_output(command):
@@ -888,7 +722,7 @@ def get_command_output(command):
             break
 
 
-def get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, flash_command, max_paired_end_reads_overlap, min_paired_end_reads_overlap, split_interleaved_input=False, debug=False):
+def get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, fastp_command, min_paired_end_reads_overlap, split_interleaved_input=False, debug=False):
     """
     Get the most frequent amplicon from a fastq file (or after merging a r1 and r2 fastq file).
 
@@ -898,8 +732,7 @@ def get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, fla
     fastq_r1: path to fastq r1 (can be gzipped)
     fastq_r2: path to fastq r2 (can be gzipped)
     number_of_reads_to_consider: number of reads from the top of the file to examine
-    min_paired_end_reads_overlap: min overlap in bp for flashing (merging) r1 and r2
-    max_paired_end_reads_overlap: max overlap in bp for flashing (merging) r1 and r2
+    min_paired_end_reads_overlap: min overlap in bp for merging r1 and r2
 
     returns:
     list of amplicon strings sorted by order in format:
@@ -951,22 +784,29 @@ def get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, fla
 
     view_cmd_1 = 'cat'
     if fastq_r1.endswith('.gz'):
-        view_cmd_1 = 'zcat'
+        view_cmd_1 = 'gunzip -c'
     file_generation_command = "%s %s | head -n %d " % (view_cmd_1, fastq_r1, number_of_reads_to_consider * 4)
 
     if fastq_r2:
         view_cmd_2 = 'cat'
         if fastq_r2.endswith('.gz'):
-            view_cmd_2 = 'zcat'
-        max_overlap_param = ""
+            view_cmd_2 = 'gunzip -c'
         min_overlap_param = ""
-        if max_paired_end_reads_overlap:
-            max_overlap_param = "--max-overlap=" + str(max_paired_end_reads_overlap)
         if min_paired_end_reads_overlap:
-            min_overlap_param = "--min-overlap=" + str(min_paired_end_reads_overlap)
-        file_generation_command = "bash -c 'paste <(%s \"%s\") <(%s \"%s\")' | head -n %d | paste - - - - | awk -v OFS=\"\\n\" -v FS=\"\\t\" '{print($1,$3,$5,$7,$2,$4,$6,$8)}' | %s - --interleaved-input --allow-outies %s %s --to-stdout 2>/dev/null " % (
-        view_cmd_1, fastq_r1, view_cmd_2, fastq_r2, number_of_reads_to_consider * 4, flash_command, max_overlap_param,
-        min_overlap_param)
+            min_overlap_param = "--overlap_len_require {0}".format(min_paired_end_reads_overlap)
+        file_generation_command = "{paste} | {head} | paste - - - - | {awk} | {fastp}".format(
+            paste="bash -c 'paste <({view_cmd_1} \"{fastq_r1}\") <({view_cmd_2} \"{fastq_r2}\")'".format(
+                view_cmd_1=view_cmd_1, fastq_r1=fastq_r1, view_cmd_2=view_cmd_2, fastq_r2=fastq_r2,
+            ),
+            head='head -n {num_reads}'.format(
+                num_reads=number_of_reads_to_consider * 4,
+            ),
+            awk="awk -v OFS=\"\\n\" -v FS=\"\\t\" '{{print($1,$3,$5,$7,$2,$4,$6,$8)}}'",
+            fastp='{fastp_command} --disable_adapter_trimming --disable_trim_poly_g --disable_quality_filtering --disable_length_filtering --stdin --interleaved_in --merge {min_overlap_param} --stdout 2>/dev/null'.format(
+                fastp_command=fastp_command,
+                min_overlap_param=min_overlap_param,
+            ),
+        )
     count_frequent_cmd = file_generation_command + " | awk '((NR-2)%4==0){print $1}' | sort | uniq -c | sort -nr "
 
     def default_sigpipe():
@@ -995,17 +835,71 @@ def default_sigpipe():
 
     return seq_lines
 
+def check_if_failed_run(folder_name, info):
+    """
+    Check the output folder for a info.json file and a status.txt file to see if the run completed successfully or not
+
+    input:
+    folder_name: path to output folder
+    info: logger
+
+
+    returns:
+    bool True if run completed successfully, False otherwise
+    string describing why it failed
+    """
+
+    run_data_file = os.path.join(folder_name, 'CRISPResso2_info.json')
+    status_info = os.path.join(folder_name, 'CRISPResso_status.json')
+    if not os.path.isfile(run_data_file) or not os.path.isfile(status_info):
+        info("Skipping folder '%s'. Cannot find run data status file at '%s'."%(folder_name, run_data_file))
+        if "CRISPRessoPooled" in folder_name:
+            unit = "amplicon"
+        elif "CRISPRessoWGS" in folder_name:
+            unit = "region"
+        else:
+            unit = "sample"
+
+        return True, f"CRISPResso failed for this {unit}! Please check your input files and parameters."
+    else:
+        with open(status_info) as fh:
+            try:
+                status_dict = json.load(fh)
+                if status_dict['percent_complete'] != 100.0:
+                    info("Skipping folder '%s'. Run is not complete (%s)." % (folder_name, status_dict['status']))
+                    return True, str(status_dict['message'])
+                else:
+                    return False, ""
+            except:
+                pass
+
+        with open(status_info) as fh:
+                try:
+                    file_contents = fh.read()
+                    search_result = re.search(r'(\d+\.\d+)% (.+)', file_contents)
+                    if search_result:
+                        percent_complete, status = search_result.groups()
+                        if percent_complete != '100.00':
+                            info("Skipping folder '%s'. Run is not complete (%s)." % (folder_name, status))
+                            return True, status
+                    else:
+                        return True, file_contents
+                except Exception as e:
+                    print(e)
+                    info("Skipping folder '%s'. Cannot parse status file '%s'." % (folder_name, status_info))
+                    return True, "Cannot parse status file '%s'." % (status_info)
+        return False, ""
+
 
-def guess_amplicons(fastq_r1,fastq_r2,number_of_reads_to_consider,flash_command,max_paired_end_reads_overlap,min_paired_end_reads_overlap,aln_matrix,needleman_wunsch_gap_open,needleman_wunsch_gap_extend,split_interleaved_input=False,min_freq_to_consider=0.2,amplicon_similarity_cutoff=0.95):
+def guess_amplicons(fastq_r1, fastq_r2, number_of_reads_to_consider, fastp_command, min_paired_end_reads_overlap, aln_matrix, needleman_wunsch_gap_open, needleman_wunsch_gap_extend, split_interleaved_input=False, min_freq_to_consider=0.2, amplicon_similarity_cutoff=0.95):
     """
     guesses the amplicons used in an experiment by examining the most frequent read (giant caveat -- most frequent read should be unmodified)
     input:
     fastq_r1: path to fastq r1 (can be gzipped)
     fastq_r2: path to fastq r2 (can be gzipped)
     number_of_reads_to_consider: number of reads from the top of the file to examine
-    flash_command: command to call flash
-    min_paired_end_reads_overlap: min overlap in bp for flashing (merging) r1 and r2
-    max_paired_end_reads_overlap: max overlap in bp for flashing (merging) r1 and r2
+    fastp_command: command to call fastp
+    min_paired_end_reads_overlap: min overlap in bp for merging r1 and r2
     aln_matrix: matrix specifying alignment substitution scores in the NCBI format
     needleman_wunsch_gap_open: alignment penalty assignment used to determine similarity of two sequences
     needleman_wunsch_gap_extend: alignment penalty assignment used to determine similarity of two sequences
@@ -1016,7 +910,7 @@ def guess_amplicons(fastq_r1,fastq_r2,number_of_reads_to_consider,flash_command,
     returns:
     list of putative amplicons
     """
-    seq_lines = get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, flash_command, max_paired_end_reads_overlap, min_paired_end_reads_overlap, split_interleaved_input=split_interleaved_input)
+    seq_lines = get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, fastp_command, min_paired_end_reads_overlap, split_interleaved_input=split_interleaved_input)
 
     curr_amplicon_id = 1
 
@@ -1061,11 +955,11 @@ def guess_amplicons(fastq_r1,fastq_r2,number_of_reads_to_consider,flash_command,
     return amplicon_seq_arr
 
 
-def guess_guides(amplicon_sequence,fastq_r1,fastq_r2,number_of_reads_to_consider,flash_command,max_paired_end_reads_overlap,
-            min_paired_end_reads_overlap,exclude_bp_from_left,exclude_bp_from_right,
-            aln_matrix,needleman_wunsch_gap_open,needleman_wunsch_gap_extend,
-            min_edit_freq_to_consider=0.1,min_edit_fold_change_to_consider=3,
-            pam_seq="NGG", min_pct_subs_in_base_editor_win=0.8,split_interleaved_input=False):
+def guess_guides(amplicon_sequence, fastq_r1, fastq_r2, number_of_reads_to_consider, fastp_command,
+            min_paired_end_reads_overlap, exclude_bp_from_left, exclude_bp_from_right,
+            aln_matrix, needleman_wunsch_gap_open, needleman_wunsch_gap_extend,
+            min_edit_freq_to_consider=0.1, min_edit_fold_change_to_consider=3,
+            pam_seq="NGG", min_pct_subs_in_base_editor_win=0.8, split_interleaved_input=False):
     """
     guesses the guides used in an experiment by identifying the most-frequently edited positions, editing types, and PAM sites
     input:
@@ -1073,9 +967,8 @@ def guess_guides(amplicon_sequence,fastq_r1,fastq_r2,number_of_reads_to_consider
     fastq_r1: path to fastq r1 (can be gzipped)
     fastq_r2: path to fastq r2 (can be gzipped)
     number_of_reads_to_consider: number of reads from the top of the file to examine
-    flash_command: command to call flash
+    fastp_command: command to call fastp
     min_paired_end_reads_overlap: min overlap in bp for flashing (merging) r1 and r2
-    max_paired_end_reads_overlap: max overlap in bp for flashing (merging) r1 and r2
     exclude_bp_from_left: number of bp to exclude from the left side of the amplicon sequence for the quantification of the indels
     exclude_bp_from_right: number of bp to exclude from the right side of the amplicon sequence for the quantification of the indels
     aln_matrix: matrix specifying alignment substitution scores in the NCBI format
@@ -1091,7 +984,7 @@ def guess_guides(amplicon_sequence,fastq_r1,fastq_r2,number_of_reads_to_consider
     tuple of (putative guide, boolean is_base_editor)
     or (None, None)
     """
-    seq_lines = get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, flash_command, max_paired_end_reads_overlap, min_paired_end_reads_overlap,split_interleaved_input=split_interleaved_input)
+    seq_lines = get_most_frequent_reads(fastq_r1, fastq_r2, number_of_reads_to_consider, fastp_command, min_paired_end_reads_overlap,split_interleaved_input=split_interleaved_input)
 
     amp_len = len(amplicon_sequence)
     gap_incentive = np.zeros(amp_len + 1, dtype=int)
@@ -1252,6 +1145,61 @@ def force_merge_pairs(r1_filename, r2_filename, output_filename):
     return (lineCount)
 
 
+def split_interleaved_fastq(fastq_filename, output_filename_r1, output_filename_r2):
+    """Split an interleaved fastq file into two files, one for each read pair.
+
+    This assumes that the input fastq file is interleaved, i.e. that the reads are ordered as follows:
+        R1
+        R2
+        R1
+        R2
+        ...
+
+    And results in two files, one for each read pair:
+        output_filename_r1
+            R1
+            R1
+            ...
+        output_filename_r2
+            R2
+            R2
+            ...
+
+    Parameters
+    ----------
+    fastq_filename : str
+        Path to the input fastq file.
+    output_filename_r1 : str
+        Path to the output fastq file for r1.
+    output_filename_r2 : str
+        Path to the output fastq file for r2.
+
+    Returns
+    -------
+    output_filename_r1 : str
+        Path to the output fastq file for r1.
+    output_filename_r2 : str
+        Path to the output fastq file for r2.
+    """
+    if fastq_filename.endswith('.gz'):
+        fastq_handle = gzip.open(fastq_filename, 'rt')
+    else:
+        fastq_handle = open(fastq_filename)
+
+    try:
+        fastq_splitted_outfile_r1 = gzip.open(output_filename_r1, 'wt')
+        fastq_splitted_outfile_r2 = gzip.open(output_filename_r2, 'wt')
+        [fastq_splitted_outfile_r1.write(line) if (i % 8 < 4) else fastq_splitted_outfile_r2.write(line) for i, line in enumerate(fastq_handle)]
+    except:
+        raise BadParameterException('Error in splitting read pairs from a single file')
+    finally:
+        fastq_handle.close()
+        fastq_splitted_outfile_r1.close()
+        fastq_splitted_outfile_r2.close()
+
+    return output_filename_r1, output_filename_r2
+
+
 ######
 # allele modification functions
 ######
@@ -1340,6 +1288,7 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
     this_sgRNA_plot_idxs : list of indices to be plotted for each sgRNA
     this_sgRNA_mismatches: list of mismatches between the guide and the amplicon
     this_sgRNA_names : list of names for each sgRNA (to disambiguate in case a sequence aligns to multiple positions)
+    this_sgRNA_include_idxs : list of indices to be included in quantification per guide
     this_include_idxs : list of indices to be included in quantification
     this_exclude_idxs : list of indices to be excluded from quantification
     """
@@ -1352,6 +1301,7 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
     this_sgRNA_plot_idxs = []
     this_sgRNA_mismatches = []
     this_sgRNA_names = []
+    this_sgRNA_include_idxs = []
     this_include_idxs = []
     this_exclude_idxs = []
 
@@ -1384,7 +1334,7 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
         rv_matches = re.finditer(rv_regex, ref_seq, flags=re.IGNORECASE)
 
         # for every match, append:
-        # this_sgRNA_cut_points, this_sgRNA_intervals,this_sgRNA_mismatches,this_sgRNA_names,this_sgRNA_sequences,include_idxs
+        # this_sgRNA_cut_points, this_sgRNA_intervals,this_sgRNA_mismatches,this_sgRNA_names,this_sgRNA_sequences,this_sgRNA_include_idxs,include_idxs
         for m in fw_matches:
             cut_p = m.start() + offset_fw
             if discard_guide_positions_overhanging_amplicon_edge and (
@@ -1399,6 +1349,9 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
                 st = max(0, cut_p - quantification_window_sizes[guide_idx] + 1)
                 en = min(ref_seq_length - 1, cut_p + quantification_window_sizes[guide_idx] + 1)
                 this_include_idxs.extend(range(st, en))
+                this_sgRNA_include_idxs.append(list(range(st, en)))
+            else:
+                this_sgRNA_include_idxs.append([])
 
             this_sgRNA_name = guide_names[guide_idx]
             if match_count == 1:
@@ -1428,6 +1381,9 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
                 st = max(0, cut_p - quantification_window_sizes[guide_idx] + 1)
                 en = min(ref_seq_length - 1, cut_p + quantification_window_sizes[guide_idx] + 1)
                 this_include_idxs.extend(range(st, en))
+                this_sgRNA_include_idxs.append(list(range(st, en)))
+            else:
+                this_sgRNA_include_idxs.append([])
 
             this_sgRNA_name = guide_names[guide_idx]
             if match_count == 1:
@@ -1446,7 +1402,7 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
     # create mask of positions in which to include/exclude indels for the quantification window
     # first, if exact coordinates have been given, set those
     given_include_idxs = []
-    if quantification_window_coordinates is not None:
+    if quantification_window_coordinates is not None and quantification_window_coordinates != "0":
         coordinate_include_idxs = []
         theseCoords = str(quantification_window_coordinates).split("_")
         for coord in theseCoords:
@@ -1463,11 +1419,13 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
                     theseCoords) + "'. Coordinates must be given in the form start-end e.g. 5-10 . Please check the --analysis_window_coordinate parameter.")
         given_include_idxs = coordinate_include_idxs
         this_include_idxs = coordinate_include_idxs
+        this_sgRNA_include_idxs = [[]] * len(this_sgRNA_include_idxs)
     # otherwise, take quantification centers + windows calculated for each guide above
     elif this_sgRNA_cut_points and len(this_include_idxs) > 0:
         given_include_idxs = this_include_idxs
     else:
         this_include_idxs = range(ref_seq_length)
+        this_sgRNA_include_idxs = [[]] * len(this_sgRNA_include_idxs)
 
     # flatten the arrays to avoid errors with old numpy library
     this_include_idxs = np.ravel(this_include_idxs)
@@ -1507,7 +1465,7 @@ def get_amplicon_info_for_guides(ref_seq, guides, guide_mismatches, guide_names,
     this_include_idxs = np.sort(list(this_include_idxs))
     this_exclude_idxs = np.sort(list(this_exclude_idxs))
 
-    return this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_include_idxs, this_exclude_idxs
+    return this_sgRNA_sequences, this_sgRNA_intervals, this_sgRNA_cut_points, this_sgRNA_plot_cut_points, this_sgRNA_plot_idxs, this_sgRNA_mismatches, this_sgRNA_names, this_sgRNA_include_idxs, this_include_idxs, this_exclude_idxs
 
 
 def set_guide_array(vals, guides, property_name):
@@ -1536,6 +1494,46 @@ def set_guide_array(vals, guides, property_name):
     return ret_array
 
 
+def get_relative_coordinates(to_sequence, from_sequence):
+    """Given an alignment, get the relative coordinates of the second sequence to the first.
+
+    For example, from_sequence[i] matches to to_sequence[inds[i]]. A `-1`
+    indicates a gap at the beginning of `to_sequence`.
+
+    Parameters
+    ----------
+    to_sequence : str
+        The alignment of the first sequence (where the coordinates are relative to)
+    from_sequence : str
+        The alignment of the second sequence
+
+    Returns
+    -------
+    s1inds_gap_left : list of int
+        The relative coordinates of the second sequence to the first, where gaps
+        in the first sequence are filled with the left value.
+    s1inds_gap_right : list of int
+        The relative coordinates of the second sequence to the first, where gaps
+        in the first sequence are filled with the right value.
+    """
+    s1inds_gap_left = []
+    s1inds_gap_right = []
+    s1idx_left = -1
+    s1idx_right = 0
+    s2idx = -1
+    for ix in range(len(to_sequence)):
+        if to_sequence[ix] != "-":
+            s1idx_left += 1
+        if from_sequence[ix] != "-":
+            s2idx += 1
+            s1inds_gap_left.append(s1idx_left)
+            s1inds_gap_right.append(s1idx_right)
+        if to_sequence[ix] != "-":
+            s1idx_right += 1
+
+    return s1inds_gap_left, s1inds_gap_right
+
+
 def get_alignment_coordinates(to_sequence, from_sequence, aln_matrix, needleman_wunsch_gap_open,
                               needleman_wunsch_gap_extend):
     """
@@ -1557,23 +1555,7 @@ def get_alignment_coordinates(to_sequence, from_sequence, aln_matrix, needleman_
                                                         gap_open=needleman_wunsch_gap_open,
                                                         gap_extend=needleman_wunsch_gap_extend,
                                                         gap_incentive=this_gap_incentive)
-    #    print(fws1)
-    #    print(fws2)
-    s1inds_l = []
-    s1inds_r = []
-    s1ix_l = -1
-    s1ix_r = 0
-    s2ix = -1
-    for ix in range(len(fws1)):
-        if fws1[ix] != "-":
-            s1ix_l += 1
-        if fws2[ix] != "-":
-            s2ix += 1
-            s1inds_l.append(s1ix_l)
-            s1inds_r.append(s1ix_r)
-        if fws1[ix] != "-":
-            s1ix_r += 1
-    return s1inds_l, s1inds_r
+    return get_relative_coordinates(fws1, fws2)
 
 
 def get_mismatches(seq_1, seq_2, aln_matrix, needleman_wunsch_gap_open, needleman_wunsch_gap_extend):
@@ -1675,6 +1657,38 @@ def get_sgRNA_mismatch_vals(seq1, seq2, start_loc, end_loc, coords_l, coords_r,
     return list(set(this_mismatches))
 
 
+def get_quant_window_ranges_from_include_idxs(include_idxs):
+    """Given a list of indexes, return the ranges that those indexes include.
+
+    Parameters
+    ----------
+    include_idxs: list
+       A list of indexes included in the quantification window, for example
+       `[20, 21, 22, 35, 36, 37]`.
+
+    Returns
+    -------
+    list
+       A list of tuples representing the ranges included in the quantification
+       window, for example `[(20, 22), (35, 37)]`. If there is a single index, it
+       will be reported as `[(20, 20)]`.
+    """
+    quant_ranges = []
+    if include_idxs is None or len(include_idxs) == 0:
+        return quant_ranges
+    start_idx = include_idxs[0]
+    last_idx = include_idxs[0]
+    for idx in include_idxs[1:]:
+        if idx == last_idx + 1:
+            last_idx = idx
+        else:
+            quant_ranges.append((start_idx, last_idx))
+            start_idx = idx
+            last_idx = idx
+    quant_ranges.append((start_idx, last_idx))
+    return quant_ranges
+
+
 ######
 # terminal functions
 ######
@@ -1732,7 +1746,7 @@ def get_crispresso_header(description, header_str):
                 term_width) + "\n" + output_line
 
     output_line += '\n' + ('[CRISPResso version ' + __version__ + ']').center(term_width) + '\n' + (
-        '[Note that starting in version 2.3.0 FLASh and Trimmomatic will be replaced by fastp for read merging and trimming. Accordingly, the --flash_command and --trimmomatic_command parameters will be replaced with --fastp_command. Also, --trimmomatic_options_string will be replaced with --fastp_options_string.\n\nAlso in version 2.3.0, when running CRISPRessoPooled in mixed-mode (amplicon file and genome are provided) the default behavior will be as if the --demultiplex_only_at_amplicons parameter is provided. This change means that reads and amplicons do not need to align to the exact locations.]').center(
+        '[Note that as of version 2.3.0 FLASh and Trimmomatic have been replaced by fastp for read merging and trimming. Accordingly, the --flash_command and --trimmomatic_command parameters have been replaced with --fastp_command. Also, --trimmomatic_options_string has been replaced with --fastp_options_string.\n\nAlso in version 2.3.2, when running CRISPRessoPooled in mixed-mode (amplicon file and genome are provided) the default behavior will be as if the --demultiplex_only_at_amplicons parameter is provided. This change means that reads and amplicons do not need to align to the exact locations.]').center(
         term_width) + "\n" + ('[For support contact k.clement@utah.edu or support@edilytics.com]').center(term_width) + "\n"
 
     description_str = ""
@@ -1804,3 +1818,522 @@ def zip_results(results_folder):
         )
     sb.call(cmd_to_zip, shell=True)
     return
+
+
+def is_C2Pro_installed():
+    try:
+        spec = importlib.util.find_spec("CRISPRessoPro")
+        if spec is None:
+            return False
+        else:
+            return True
+    except:
+        return False
+
+
+def check_custom_config(args):
+    """Check if the config_file argument was provided. If so load the configurations from the file, otherwise load default configurations.
+
+    Parameters:
+    -------------
+    args : dict
+        All arguments passed into the crispresso run.
+
+    Returns:
+    -------------
+    config : dict
+        A dict with a 'colors' key that contains hex color values for different report items as well as a 'guardrails' key that contains the guardrail values.
+    """
+    config =  {
+        "colors": {
+            'Substitution': '#0000FF',
+            'Insertion': '#008000',
+            'Deletion': '#FF0000',
+            'A': '#7FC97F',
+            'T': '#BEAED4',
+            'C': '#FDC086',
+            'G': '#FFFF99',
+            'N': '#C8C8C8',
+            '-': '#1E1E1E',
+        },
+        "guardrails": {
+            'min_total_reads': 10000,
+            'aligned_cutoff': 0.9,
+            'alternate_alignment': 0.3,
+            'min_ratio_of_mods_in_to_out': 0.01,
+            'modifications_at_ends': 0.01,
+            'outside_window_max_sub_rate': 0.002,
+            'max_rate_of_subs': 0.3,
+            'guide_len': 19,
+            'amplicon_len': 50,
+            'amplicon_to_read_length': 1.5
+        }
+    }
+
+    logger = logging.getLogger(getmodule(stack()[1][0]).__name__)
+    if not is_C2Pro_installed():
+        return config
+
+    if args.config_file:
+        try:
+            with open(args.config_file, "r") as json_file:
+                custom_config = json.load(json_file)
+
+            if 'guardrails' in custom_config.keys():
+                for key in config['guardrails']:
+                    if key not in custom_config['guardrails']:
+                        logger.warn(f"Value for {key} not provided, defaulting.")
+                        custom_config['guardrails'][key] = config['guardrails'][key]
+                for key in custom_config['guardrails']:
+                    if key not in config['guardrails']:
+                        logger.warn(f"Key {key} is not a recognized guardrail parameter, skipping.")
+            else:
+                logger.warn("Json file does not contain the guardrails key. Defaulting all values.")
+                custom_config['guardrails'] = config['guardrails']
+
+            if 'colors' in custom_config.keys():
+                for key in config['colors']:
+                    if key not in custom_config['colors']:
+                        logger.warn(f"Value for {key} not provided, defaulting")
+                        custom_config['colors'][key] = config['colors'][key]
+            else:
+                logger.warn("Json file does not contain the colors key. Defaulting all values.")
+                custom_config['colors'] = config['colors']
+
+            return custom_config
+        except Exception:
+            if args.config_file:
+                logger.warn("Cannot read config file '%s', defaulting config parameters." % args.config_file)
+            else:
+                logger.warn("No config file provided, defaulting config parameters.")
+    return config
+
+
+def safety_check(crispresso2_info, aln_stats, guardrails):
+    """Check the results of analysis for potential issues and warns the user.
+
+    Parameters
+    ----------
+    crispresso2_info : dict
+        Dictionary of values describing the analysis
+    aln_stats : dict
+        Dictionary of alignment statistics and modification frequency and location
+    guardrails : dict
+        Contains the following:
+            min_total_reads : int
+                Cutoff value for total reads aligned
+            alignedCutoff : float
+                Check value for the percentage of reads aligned vs not aligned
+            alternateAlignment : float
+                Percentage of variance from expected reads to trigger guardrail
+            minRatioOfModsInToOut : float
+                Float representing the acceptable ratio of modifications in the window to out
+            modificationsAtEnds : float
+                The ratio of reads with modifications at the 0 or -1 spot
+            outsideWindowMaxSubRate : float
+                Ratio of subs allowed outside of the window
+            maxRateOfSubs : float
+                Allowed rate of subs accross the entire read
+            guide_len : int
+                Minimum guide length
+            amplicon_len : int
+                Minimum amplicon length
+            ampliconToReadLen : float
+                Comparison value between amplicons and reads
+    """
+    logger = logging.getLogger(getmodule(stack()[1][0]).__name__)
+    messageHandler = GuardrailMessageHandler(logger)
+
+    # Get amplicon and guide sequences and lengths
+    amplicons = {}
+    guide_groups = set()
+    for name in crispresso2_info['results']['ref_names']:
+        amplicons[name] = crispresso2_info['results']['refs'][name]['sequence_length']
+        guide_groups.update(crispresso2_info['results']['refs'][name]['sgRNA_sequences'])
+    unique_guides = {guide: len(guide) for guide in guide_groups}
+
+    totalReadsGuardrail = TotalReadsGuardrail(messageHandler, guardrails['min_total_reads'])
+    totalReadsGuardrail.safety(aln_stats['N_TOT_READS'])
+
+    overallReadsAlignedGuard = OverallReadsAlignedGuardrail(messageHandler, guardrails['aligned_cutoff'])
+    overallReadsAlignedGuard.safety(aln_stats['N_TOT_READS'], (aln_stats['N_CACHED_ALN'] + aln_stats['N_COMPUTED_ALN']))
+
+    disproportionateReadsAlignedGuardrail = DisproportionateReadsAlignedGuardrail(messageHandler, guardrails['aligned_cutoff'])
+    disproportionateReadsAlignedGuardrail.safety(aln_stats['N_TOT_READS'], crispresso2_info['results']['alignment_stats']['counts_total'])
+
+    lowRatioOfModsInWindowToOut = LowRatioOfModsInWindowToOutGuardrail(messageHandler, guardrails['min_ratio_of_mods_in_to_out'])
+    lowRatioOfModsInWindowToOut.safety(aln_stats['N_MODS_IN_WINDOW'], aln_stats['N_MODS_OUTSIDE_WINDOW'])
+
+    highRateOfModificationAtEndsGuardrail = HighRateOfModificationAtEndsGuardrail(messageHandler, guardrails['modifications_at_ends'])
+    highRateOfModificationAtEndsGuardrail.safety((aln_stats['N_CACHED_ALN'] + aln_stats['N_COMPUTED_ALN']), aln_stats['N_READS_IRREGULAR_ENDS'])
+
+    highRateOfSubstitutionsOutsideWindowGuardrail = HighRateOfSubstitutionsOutsideWindowGuardrail(messageHandler, guardrails['outside_window_max_sub_rate'])
+    highRateOfSubstitutionsOutsideWindowGuardrail.safety(aln_stats['N_GLOBAL_SUBS'], aln_stats['N_SUBS_OUTSIDE_WINDOW'])
+
+    highRateOfSubstitutions = HighRateOfSubstitutionsGuardrail(messageHandler, guardrails['max_rate_of_subs'])
+    highRateOfSubstitutions.safety(aln_stats['N_MODS_IN_WINDOW'], aln_stats['N_MODS_OUTSIDE_WINDOW'], aln_stats['N_GLOBAL_SUBS'])
+
+    shortAmpliconSequence = ShortSequenceGuardrail(messageHandler, guardrails['amplicon_len'], 'amplicon')
+    shortAmpliconSequence.safety(amplicons)
+
+    shortGuideSequence = ShortSequenceGuardrail(messageHandler, guardrails['guide_len'], 'guide')
+    shortGuideSequence.safety(unique_guides)
+
+    longAmpliconShortReadsGuardrail = LongAmpliconShortReadsGuardrail(messageHandler, guardrails['amplicon_to_read_length'])
+    longAmpliconShortReadsGuardrail.safety(amplicons, aln_stats['READ_LENGTH'])
+
+    crispresso2_info['results']['guardrails'] = messageHandler.get_messages()
+
+    return messageHandler.get_html_messages()
+
+
+class GuardrailMessageHandler:
+    """Class to handle message storage and display for guardrails"""
+    def __init__(self, logger):
+        """Create the message handler with an empty message array to collect html divs for the report
+
+        Parameters:
+        ------------
+        logger : logger
+            logger object used to display messages
+        """
+        self.logger = logger
+        self.html_messages = []
+        self.messages = {}
+
+    def display_warning(self, guardrail, message):
+        """Send the message to the logger to be displayed
+
+        Parameters:
+        -----------
+        message : string
+            Related guardrail message
+        """
+        self.logger.warning(message)
+        self.messages[guardrail] = message
+
+    def report_warning(self, message):
+        """Create and store the html message to display on the report
+
+        Parameters:
+        -----------
+        message : string
+            Related guardrail message
+        """
+        html_warning = '<div class="alert alert-danger"><strong>Guardrail Warning!</strong>{0}</div>'.format(message)
+        self.html_messages.append(html_warning)
+
+    def get_messages(self):
+        """Return the messages accumulated by the message handler"""
+        return self.messages
+
+    def get_html_messages(self):
+        """Return the html messages accumulated by the message handler"""
+        return self.html_messages
+
+
+class TotalReadsGuardrail:
+    """Guardrail class: check that the number of reads are above a minimum"""
+    def __init__(self, messageHandler, minimum):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        minimum : int
+            The comparison integer to determine if there are sufficent reads
+        """
+        self.messageHandler = messageHandler
+        self.minimum = minimum
+        self.message = " Low number of total reads: <{}.".format(minimum)
+
+    def safety(self, total_reads):
+        """Safety check, if total is below minimum send warnings
+
+        Parameters:
+        -----------
+        total_reads : int
+            The total reads, unaligned and aligned
+        """
+        if total_reads < self.minimum:
+            self.message = self.message + " Total reads: {}.".format(total_reads)
+            self.messageHandler.display_warning('TotalReadsGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class OverallReadsAlignedGuardrail:
+    """Guardrail class: check if enough reads are aligned"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The float representation of percentage of minimum reads to be aligned
+        """
+        self.messageHandler = messageHandler
+        self.message = " <={val}% of reads were aligned.".format(val=(cutoff * 100))
+        self.cutoff = cutoff
+
+    def safety(self, total_reads, n_read_aligned):
+        """Safety check, if total_reads divided by n_reads_aligned is lower than the cutoff
+
+        Parameters:
+        -----------
+        total_reads : int
+            Total reads, unaligned and aligned
+        n_read_aligned : int
+            Total aligned reads
+        """
+        if total_reads == 0:
+            return
+        if (n_read_aligned/total_reads) <= self.cutoff:
+            self.message = self.message + " Total reads: {}, Aligned reads: {}.".format(total_reads, n_read_aligned)
+            self.messageHandler.display_warning('OverallReadsAlignedGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class DisproportionateReadsAlignedGuardrail:
+    """Guardrail class: check if the distribution of reads is roughly even across amplicons"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The float representation of the accepted percentage deviation from the expected distribution
+        """
+        self.messageHandler = messageHandler
+        self.message = " Disproportionate percentages of reads were aligned to amplicon: "
+        self.cutoff = cutoff
+
+    def safety(self, n_read_aligned, reads_aln_amplicon):
+        """Safety check, if total_reads divided by n_reads_aligned is higher or lower than the total_reads divided by the number of amplicons
+
+        Parameters:
+        -----------
+        total_reads : int
+            Total reads, unaligned and aligned
+        reads_aln_amplicon : dict
+            A dictionary with the names of amplicons as the key and the number of reads aligned as the value
+        """
+        if len(reads_aln_amplicon.keys()) <= 1:
+            return
+        expected_per_amplicon = n_read_aligned / len(reads_aln_amplicon.keys())
+        for amplicon, aligned in reads_aln_amplicon.items():
+            if aligned <= (expected_per_amplicon * self.cutoff) or aligned >= (expected_per_amplicon * (1 - self.cutoff)):
+                amplicon_message = self.message + amplicon + ", Percent of aligned reads aligned to this amplicon: {}%.".format(round((aligned/n_read_aligned) * 100, 2))
+                self.messageHandler.display_warning('DisproportionateReadsAlignedGuardrail', amplicon_message)
+                self.messageHandler.report_warning(amplicon_message)
+
+
+class LowRatioOfModsInWindowToOutGuardrail:
+    """Guardrail class: check the ratio of modifications in the quantification window to out of it"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The float representation of the maximum percentage of modifications outside of the quantification window
+        """
+        self.messageHandler = messageHandler
+        self.message = " <={}% of modifications were inside of the quantification window.".format(cutoff * 100)
+        self.cutoff = cutoff
+
+    def safety(self, mods_in_window, mods_outside_window):
+        """Safety check, if the modifications in the window are below a ratio of the total
+
+        Parameters:
+        -----------
+        mods_in_window : int
+            The number of mods in the quantification window
+        mods_outside_window : int
+            The number of mods outside of the quantification window
+        """
+        total_mods = mods_in_window + mods_outside_window
+        if total_mods == 0:
+            return
+        if ((mods_in_window / total_mods) <= self.cutoff):
+            self.message = self.message + " Total modifications: {}, Modifications in window: {}, Modifications outside window: {}.".format(total_mods, mods_in_window, mods_outside_window)
+            self.messageHandler.display_warning('LowRatioOfModsInWindowToOutGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class HighRateOfModificationAtEndsGuardrail:
+    """Guardrail class: check the ratio of modifications in the quantification window to out of it"""
+    def __init__(self, messageHandler, percentage_start_end):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        percentage_start_end : float
+            The float representation of the maximum percentage reads that have modifications on either end
+        """
+        self.messageHandler = messageHandler
+        self.message = " >={}% of reads have modifications at the start or end.".format(round(percentage_start_end * 100, 2))
+        self.percent = percentage_start_end
+
+    def safety(self, total_reads, irregular_reads):
+        """Safety check, comparison between the number of irregular reads to total reads
+
+        Parameters:
+        -----------
+        total_reads : int
+            The number of mods in the quantification window
+        irregular_reads : int
+            The number of mods outside of the quantification window
+        """
+        if total_reads == 0:
+            return
+        if (irregular_reads / total_reads) >= self.percent:
+            self.message = self.message + " Total reads: {}, Irregular reads: {}.".format(total_reads, irregular_reads)
+            self.messageHandler.display_warning('HighRateOfModificationAtEndsGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class HighRateOfSubstitutionsOutsideWindowGuardrail:
+    """Guardrail class: check the ratio of global substitutions to substitutions outside of the quantification window"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The float representation of how many of the total substitutions can be outside of the quantification window
+        """
+        self.messageHandler = messageHandler
+        self.message = " >={}% of substitutions were outside of the quantification window.".format(cutoff * 100)
+        self.cutoff = cutoff
+
+    def safety(self, global_subs, subs_outside_window):
+        """Safety check, comparison between the number of global subs to subs outside of the quantification window
+
+        Parameters:
+        -----------
+        global_subs : int
+            The number of mods in the quantification window
+        subs_outside_window : int
+            The number of mods outside of the quantification window
+        """
+        if global_subs == 0:
+            return
+        if ((subs_outside_window / global_subs) >= self.cutoff):
+            self.message = self.message + " Total substitutions: {}, Substitutions outside window: {}.".format(global_subs, subs_outside_window)
+            self.messageHandler.display_warning('HighRateOfSubstitutionsOutsideWindowGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class HighRateOfSubstitutionsGuardrail:
+    """Guardrail class: check the ratio of global substitutions to total modifications"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The float representation of how many of the total modifications can be subsitutions
+        """
+        self.messageHandler = messageHandler
+        self.message = " >={}% of modifications were substitutions. This could potentially indicate poor sequencing quality.".format(cutoff * 100)
+        self.cutoff = cutoff
+
+    def safety(self, mods_in_window, mods_outside_window, global_subs):
+        """Safety check, comparison between subsitutions and total modifications
+
+        Parameters:
+        -----------
+        mods_in_window : int
+            Modifications inside of the quantification window
+        mods_outside_window : int
+            Modifications outside of the quantification window
+        global_subs : int
+            Total subsitutions across all reads
+        """
+        total_mods = mods_in_window + mods_outside_window
+        if total_mods == 0:
+            return
+        if ((global_subs / total_mods) >= self.cutoff):
+            self.message = self.message + " Total modifications: {}, Substitutions: {}.".format(total_mods, global_subs)
+            self.messageHandler.display_warning('HighRateOfSubstitutionsGuardrail', self.message)
+            self.messageHandler.report_warning(self.message)
+
+
+class ShortSequenceGuardrail:
+    """Guardrail class: Check to make sure that sequences (amplicons and guides) are above a certain length"""
+    def __init__(self, messageHandler, cutoff, sequence_type):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : int
+            Integer value to measure the length of the sequence
+        sequence_type : string
+            Amplicon or guide
+        """
+        self.messageHandler = messageHandler
+        self.cutoff = cutoff
+        self.message = f" {string.capwords(sequence_type)} length <{cutoff}: "
+
+    def safety(self, sequences):
+        """Safety check, comparison between sequence lengths and minimum lengths
+
+        Parameters:
+        -----------
+        sequences : dict
+            Dictionary with the name of the sequence as the key and the length of the sequence as the value
+        """
+        for name, length in sequences.items():
+            if length < self.cutoff:
+                sequence_message = self.message + name + ", Length: {}.".format(length)
+                self.messageHandler.display_warning('ShortSequenceGuardrail', sequence_message)
+                self.messageHandler.report_warning(sequence_message)
+
+
+class LongAmpliconShortReadsGuardrail:
+    """Guardrail class: Check to make sure that the reads are close in size to amplicons"""
+    def __init__(self, messageHandler, cutoff):
+        """Assign variables and create guardrail message
+
+        Parameters:
+        -----------
+        messageHandler : GuardrailMessagehandler
+            Guardrail message handler to create and display warnings
+        cutoff : float
+            The value multiplied by the read length to make sure the amplicon isn't too much longer than the reads.
+
+        """
+        self.messageHandler = messageHandler
+        self.cutoff = cutoff
+        self.message = " Amplicon length is greater than {}x the length of the reads: ".format(cutoff)
+
+    def safety(self, amplicons, read_len):
+        """Safety check, comparison between amplicon length and read length
+
+        Parameters:
+        -----------
+        amplicons : dict
+            Dictionary with the name of the amplicon as the key and the length of the sequence as the value
+        read_len : int
+            Average length of reads
+        """
+        for name, length in amplicons.items():
+            if length > (read_len * self.cutoff):
+                sequence_message = self.message + name + ", Amplicon length: {}, Read length: {}.".format(length, read_len)
+                self.messageHandler.display_warning('LongAmpliconShortReadsGuardrail', sequence_message)
+                self.messageHandler.report_warning(sequence_message)
diff --git a/CRISPResso2/CRISPRessoWGSCORE.py b/CRISPResso2/CRISPRessoWGSCORE.py
index 07d4d8de..26e7f246 100644
--- a/CRISPResso2/CRISPRessoWGSCORE.py
+++ b/CRISPResso2/CRISPRessoWGSCORE.py
@@ -18,9 +18,7 @@
 import unicodedata
 from CRISPResso2 import CRISPRessoShared
 from CRISPResso2 import CRISPRessoMultiProcessing
-from CRISPResso2 import CRISPRessoReport
-from CRISPResso2 import CRISPRessoPlot
-
+from CRISPResso2.CRISPRessoReports import CRISPRessoReport
 
 import logging
 
@@ -203,14 +201,18 @@ def write_trimmed_fastq(in_bam_filename, bpstart, bpend, out_fastq_filename):
         n_reasd (int): number of reads written to the output fastq file
     """
     p = sb.Popen(
-                'samtools view %s | cut -f1,4,6,10,11' % in_bam_filename,
-                stdout = sb.PIPE,
-                stderr = sb.STDOUT,
-                shell=True
-                )
-
-    output=p.communicate()[0].decode('utf-8')
-    n_reads=0
+        f'samtools view {in_bam_filename} | cut -f1,4,6,10,11',
+        stdout=sb.PIPE,
+        stderr=sb.PIPE,
+        shell=True,
+        text=True,
+    )
+
+    output, stderr = p.communicate()
+    n_reads = 0
+    if stderr:
+        logger.debug('Stderr from samtools view:')
+        logger.debug(stderr)
 
     with gzip.open(out_fastq_filename, 'wt') as outfile:
         for line in output.split('\n'):
@@ -349,31 +351,18 @@ def print_stacktrace_if_debug():
             ))
             sys.exit()
 
-        parser = CRISPRessoShared.getCRISPRessoArgParser(parser_title = 'CRISPRessoWGS Parameters', required_params=[], 
-                    suppress_params=['bam_input',
-                                   'bam_chr_loc'
-                                   'fastq_r1', 
-                                   'fastq_r2', 
-                                   'amplicon_seq', 
-                                   'amplicon_name', 
-                                   ])
-
-        #tool specific optional
-        parser.add_argument('-b', '--bam_file', type=str,  help='WGS aligned bam file', required=True, default='bam filename' )
-        parser.add_argument('-f', '--region_file', type=str,  help='Regions description file. A BED format  file containing the regions to analyze, one per line. The REQUIRED\
-        columns are: chr_id(chromosome name), bpstart(start position), bpend(end position), the optional columns are:name (an unique indentifier for the region), guide_seq, expected_hdr_amplicon_seq,coding_seq, see CRISPResso help for more details on these last 3 parameters)', required=True)
-        parser.add_argument('-r', '--reference_file', type=str, help='A FASTA format reference file (for example hg19.fa for the human genome)', default='', required=True)
-        parser.add_argument('--min_reads_to_use_region',  type=float, help='Minimum number of reads that align to a region to perform the CRISPResso analysis', default=10)
-        parser.add_argument('--skip_failed',  help='Continue with pooled analysis even if one sample fails', action='store_true')
-        parser.add_argument('--gene_annotations', type=str, help='Gene Annotation Table from UCSC Genome Browser Tables (http://genome.ucsc.edu/cgi-bin/hgTables?command=start), \
-        please select as table "knownGene", as output format "all fields from selected table" and as file returned "gzip compressed"', default='')
-        parser.add_argument('--crispresso_command', help='CRISPResso command to call', default='CRISPResso')
+        parser = CRISPRessoShared.getCRISPRessoArgParser("WGS", parser_title = 'CRISPRessoWGS Parameters')
 
         args = parser.parse_args()
 
+        if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+            from CRISPResso2 import CRISPRessoPlot
+        else:
+            from CRISPRessoPro import plot as CRISPRessoPlot
+
         CRISPRessoShared.set_console_log_level(logger, args.verbosity, args.debug)
 
-        crispresso_options = CRISPRessoShared.get_crispresso_options()
+        crispresso_options = CRISPRessoShared.get_core_crispresso_options()
         options_to_ignore = {'fastq_r1', 'fastq_r2', 'amplicon_seq', 'amplicon_name', 'output_folder', 'name', 'zip_output'}
         crispresso_options_for_wgs = list(crispresso_options-options_to_ignore)
 
@@ -389,7 +378,7 @@ def print_stacktrace_if_debug():
         except:
             warn('Folder %s already exists.' % OUTPUT_DIRECTORY)
 
-        logger.addHandler(CRISPRessoShared.StatusHandler(_jp('CRISPRessoWGS_status.txt')))
+        logger.addHandler(CRISPRessoShared.StatusHandler(os.path.join(OUTPUT_DIRECTORY, 'CRISPRessoWGS_status.json')))
 
         info('Checking dependencies...')
 
@@ -624,7 +613,7 @@ def set_filenames(row):
             cols_to_print = ["chr_id", "bpstart", "bpend", "sgRNA", "Expected_HDR", "Coding_sequence", "sequence", "n_reads", "bam_file_with_reads_in_region", "fastq_file_trimmed_reads_in_region"]
             if args.gene_annotations:
                 cols_to_print.append('gene_overlapping')
-            df_regions.fillna('NA').to_csv(report_reads_aligned_filename, sep='\t', columns = cols_to_print, index_label="Name")
+            df_regions.infer_objects(copy=False).fillna('NA').to_csv(report_reads_aligned_filename, sep='\t', columns = cols_to_print, index_label="Name")
 
             #save progress
             crispresso2_info['running_info']['finished_steps']['generation_of_fastq_files_for_each_amplicon'] = True
@@ -677,15 +666,19 @@ def set_filenames(row):
         header_el_count = len(header_els)
         empty_line_els = [np.nan]*(header_el_count-1)
         n_reads_index = header_els.index('Reads_total') - 1
+        failed_batch_arr = []
+        failed_batch_arr_desc = []
         for idx, row in df_regions.iterrows():
             run_name = CRISPRessoShared.slugify(str(idx))
             folder_name = 'CRISPResso_on_%s' % run_name
 
+            failed_run_bool, failed_run_desc = CRISPRessoShared.check_if_failed_run(_jp(folder_name), info)
             all_region_names.append(run_name)
             all_region_read_counts[run_name] = row.n_reads
 
-            run_file = os.path.join(_jp(folder_name), 'CRISPResso2_info.json')
-            if not os.path.exists(run_file):
+            if failed_run_bool:
+                failed_batch_arr.append(run_name)
+                failed_batch_arr_desc.append(failed_run_desc)
                 warn('Skipping the folder %s: not enough reads, incomplete, or empty folder.'% folder_name)
                 this_els = empty_line_els[:]
                 this_els[n_reads_index] = row.n_reads
@@ -737,6 +730,8 @@ def set_filenames(row):
         else:
             df_summary_quantification.fillna('NA').to_csv(samples_quantification_summary_filename, sep='\t', index=None)
 
+        crispresso2_info['results']['failed_batch_arr'] = failed_batch_arr
+        crispresso2_info['results']['failed_batch_arr_desc'] = failed_batch_arr_desc
         crispresso2_info['results']['alignment_stats']['samples_quantification_summary_filename'] = os.path.basename(samples_quantification_summary_filename)
         crispresso2_info['results']['regions'] = df_regions
         crispresso2_info['results']['all_region_names'] = all_region_names
@@ -780,7 +775,7 @@ def set_filenames(row):
                 report_name = _jp("CRISPResso2WGS_report.html")
             else:
                 report_name = OUTPUT_DIRECTORY+'.html'
-            CRISPRessoReport.make_wgs_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT)
+            CRISPRessoReport.make_wgs_report_from_folder(report_name, crispresso2_info, OUTPUT_DIRECTORY, _ROOT, logger)
             crispresso2_info['running_info']['report_location'] = report_name
             crispresso2_info['running_info']['report_filename'] = os.path.basename(report_name)
 
diff --git a/CRISPResso2/args.json b/CRISPResso2/args.json
new file mode 100644
index 00000000..4f28b1b6
--- /dev/null
+++ b/CRISPResso2/args.json
@@ -0,0 +1,879 @@
+{
+    "CRISPResso_args": {
+        "fastq_r1": {
+            "keys": ["-r1", "--fastq_r1"],
+            "help": "First fastq file",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Pooled"]
+        },
+        "fastq_r2": {
+            "keys": ["-r2", "--fastq_r2"],
+            "help": "Second fastq file for paired end reads",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Pooled"]
+        },
+        "amplicon_seq": {
+            "name": "Amplicon Sequence",
+            "keys": ["-a", "--amplicon_seq"],
+            "help": "Amplicon Sequence (can be comma-separated list of multiple sequences)",
+            "type": "str",
+            "tools": ["Core", "Batch", "Pooled"]
+        },
+        "amplicon_name": {
+            "name": "Amplicon Name",
+            "keys": ["-an", "--amplicon_name"],
+            "help": "Amplicon Name (can be comma-separated list of multiple names, corresponding to amplicon sequences given in --amplicon_seq",
+            "type": "str",
+            "default": "Reference",
+            "tools": ["Core", "Batch", "Pooled"]
+        },
+        "amplicon_min_alignment_score": {
+            "keys": ["-amas", "--amplicon_min_alignment_score"],
+            "help": "Amplicon Minimum Alignment Score; score between 0 and 100; sequences must have at least this homology score with the amplicon to be aligned (can be comma-separated list of multiple scores, corresponding to amplicon sequences given in --amplicon_seq)",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "default_min_aln_score": {
+            "name": "Default Minimum Alignment Score",
+            "keys": ["--default_min_aln_score", "--min_identity_score"],
+            "help": "Default minimum homology score for a read to align to a reference amplicon",
+            "type": "int",
+            "default": 60,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "expand_ambiguous_alignments": {
+            "keys": ["--expand_ambiguous_alignments"],
+            "help": "If more than one reference amplicon is given, reads that align to multiple reference amplicons will count equally toward each amplicon. Default behavior is to exclude ambiguous alignments.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "assign_ambiguous_alignments_to_first_reference": {
+            "keys": ["--assign_ambiguous_alignments_to_first_reference"],
+            "help": "If more than one reference amplicon is given, ambiguous reads that align with the same score to multiple amplicons will be assigned to the first amplicon. Default behavior is to exclude ambiguous alignments.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "guide_seq": {
+            "keys": ["-g", "--guide_seq", "--sgRNA"],
+            "help": "sgRNA sequence, if more than one, please separate by commas. Note that the sgRNA needs to be input as the guide RNA sequence (usually 20 nt) immediately adjacent to but not including the PAM sequence (5' of NGG for SpCas9). If the PAM is found on the opposite strand with respect to the Amplicon Sequence, ensure the sgRNA sequence is also found on the opposite strand. The CRISPResso convention is to depict the expected cleavage position using the value of the parameter '--quantification_window_center' nucleotides from the 3' end of the guide. In addition, the use of alternate nucleases besides SpCas9 is supported. For example, if using the Cpf1 system, enter the sequence (usually 20 nt) immediately 3' of the PAM sequence and explicitly set the '--cleavage_offset' parameter to 1, since the default setting of -3 is suitable only for SpCas9.",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "guide_name": {
+            "keys": ["-gn", "--guide_name"],
+            "help": "sgRNA names, if more than one, please separate by commas.",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flexiguide_seq": {
+            "keys": ["-fg", "--flexiguide_seq"],
+            "help": "sgRNA sequence (flexible) (can be comma-separated list of multiple flexiguides). The flexiguide sequence will be aligned to the amplicon sequence(s), as long as the guide sequence has homology as set by --flexiguide_homology.",
+            "type": "str",
+            "default": "None",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flexiguide_homology": {
+            "keys": ["-fh", "--flexiguide_homology"],
+            "help": "flexiguides will yield guides in amplicons with at least this homology to the flexiguide sequence.",
+            "type": "int",
+            "default": 80,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flexiguide_name": {
+            "keys": ["-fgn", "--flexiguide_name"],
+            "help": "flexiguide name",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flexiguide_gap_open_penalty": {
+            "keys": ["--flexiguide_gap_open_penalty"],
+            "help": "",
+            "type": "int",
+            "default": -20,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flexiguide_gap_extend_penalty": {
+            "keys": ["--flexiguide_gap_extend_penalty"],
+            "help": "",
+            "type": "int",
+            "default": -2,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "discard_guide_positions_overhanging_amplicon_edge": {
+            "keys": ["--discard_guide_positions_overhanging_amplicon_edge"],
+            "help": "If set, for guides that align to multiple positions, guide positions will be discarded if plotting around those regions would included bp that extend beyond the end of the amplicon.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "expected_hdr_amplicon_seq": {
+            "name": "Expected HDR Amplicon Sequence",
+            "keys": ["-e", "--expected_hdr_amplicon_seq"],
+            "help": "Amplicon sequence expected after HDR",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "coding_seq": {
+            "name": "Exon Specification Coding Sequence/s",
+            "keys": ["-c", "--coding_seq"],
+            "help": "Subsequence/s of the amplicon sequence covering one or more coding sequences for frameshift analysis. If more than one (for example, split by intron/s), please separate by commas.",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "config_file": {
+            "keys": ["--config_file"],
+            "help": "File path to JSON file with config elements",
+            "type": "str",
+            "default": "None",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "min_average_read_quality": {
+            "name": "Minimum Average Read Quality (phred33 Scale)",
+            "keys": ["-q", "--min_average_read_quality"],
+            "help": "Minimum average quality score (phred33) to keep a read",
+            "type": "int",
+            "default": 0,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "min_single_bp_quality": {
+            "name": "Minimum Single bp Quality (phred33 Scale)",
+            "keys": ["-s", "--min_single_bp_quality"],
+            "help": "Minimum single bp score (phred33) to keep a read",
+            "type": "int",
+            "default": 0,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "min_bp_quality_or_N": {
+            "name": "Minimum bp Quality or N (phred33 Scale)",
+            "keys": ["--min_bp_quality_or_N"],
+            "help": "Bases with a quality score (phred33) less than this value will be set to 'N'",
+            "type": "int",
+            "default": 0,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "file_prefix": {
+            "keys": ["--file_prefix"],
+            "help": "File prefix for output plots and tables",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "name": {
+            "name": "Sample Name",
+            "keys": ["-n", "--name"],
+            "help": "Output name of the report (default: the name is obtained from the filename of the fastq file/s used in input)",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "suppress_amplicon_name_truncation": {
+            "keys": ["--suppress_amplicon_name_truncation"],
+            "help": "If set, amplicon names will not be truncated when creating output filename prefixes. If not set, amplicon names longer than 21 characters will be truncated when creating filename prefixes.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "output_folder": {
+            "keys": ["-o", "--output_folder"],
+            "help": "Output folder to use for the analysis (default: current folder)",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "verbosity": {
+            "keys": ["-v", "--verbosity"],
+            "help": "Verbosity level of output to the console (1-4) 4 is the most verbose",
+            "type": "int",
+            "default": 3,
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "split_interleaved_input": {
+            "keys": ["--split_interleaved_input", "--split_paired_end"],
+            "help": "Splits a single fastq file containing paired end reads into two files before running CRISPResso",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled"]
+        },
+        "trim_sequences": {
+            "name": "Trimming Adapter",
+            "keys": ["--trim_sequences"],
+            "help": "Enable the trimming with fastp",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "trimmomatic_command": {
+            "keys": ["--trimmomatic_command"],
+            "help": "DEPRECATED in v2.3.0, use `--fastp_command`",
+            "type": "str",
+            "default": "None",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "trimmomatic_options_string": {
+            "keys": ["--trimmomatic_options_string"],
+            "help": "DEPRECATED in v2.3.0, use `--fastp_options_string`",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "flash_command": {
+            "keys": ["--flash_command"],
+            "help": "DEPRECATED in v2.3.0, use `--fastp_command`",
+            "type": "str",
+            "default": "None",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "fastp_command": {
+            "keys": ["--fastp_command"],
+            "help": "Command to run fastp",
+            "type": "str",
+            "default": "fastp",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "fastp_options_string": {
+            "keys": ["--fastp_options_string"],
+            "help": "Override options for fastp, e.g. `--length_required 70 --umi`",
+            "type": "str",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "min_paired_end_reads_overlap": {
+            "keys": ["--min_paired_end_reads_overlap"],
+            "help": "Parameter for the fastp read merging step. Minimum required overlap length between two reads to provide a confident overlap",
+            "type": "int",
+            "default": 10,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "max_paired_end_reads_overlap": {
+            "keys": ["--max_paired_end_reads_overlap"],
+            "help": "DEPRECATED in v2.3.0",
+            "type": "str",
+            "default": "None",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "stringent_flash_merging": {
+            "keys": ["--stringent_flash_merging"],
+            "help": "DEPRECATED in v2.3.0",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "force_merge_pairs": {
+            "keys": ["--force_merge_pairs"],
+            "action": "store_true",
+            "help": "SUPPRESS",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "quantification_window_size": {
+            "name": "Quantification Window Size",
+            "keys": ["-w", "--quantification_window_size", "--window_around_sgrna"],
+            "help": "Defines the size (in bp) of the quantification window extending from the position specified by the '--cleavage_offset' or '--quantification_window_center' parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp. Multiple quantification window sizes (corresponding to each guide specified by --guide_seq) can be specified with a comma-separated list.",
+            "type": "str",
+            "default": "1",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "quantification_window_center": {
+            "name": "Quantification Window Center",
+            "keys": ["-wc", "--quantification_window_center", "--cleavage_offset"],
+            "help": "Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17 to only include mutations near the 5' end of the sgRNA. Multiple quantification window centers (corresponding to each guide specified by --guide_seq) can be specified with a comma-separated list.",
+            "type": "str",
+            "default": "-3",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "exclude_bp_from_left": {
+            "name": "Exclude bp From Left",
+            "keys": ["--exclude_bp_from_left"],
+            "help": "Exclude bp from the left side of the amplicon sequence for the quantification of the indels",
+            "type": "int",
+            "default": 15,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "exclude_bp_from_right": {
+            "name": "Exclude bp From Right",
+            "keys": ["--exclude_bp_from_right"],
+            "help": "Exclude bp from the right side of the amplicon sequence for the quantification of the indels",
+            "type": "int",
+            "default": 15,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "use_legacy_insertion_quantification": {
+            "keys": ["--use_legacy_insertion_quantification"],
+            "help": "If set, the legacy insertion quantification method will be used (i.e. with a 1bp quantification window, indels at the cut site and 1bp away from the cut site would be quantified). By default (if this parameter is not set) with a 1bp quantification window, only insertions at the cut site will be quantified.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "ignore_substitutions": {
+            "name": "Ignore Substitutions",
+            "keys": ["--ignore_substitutions"],
+            "help": "Ignore substitutions events for the quantification and visualization",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "ignore_insertions": {
+            "name": "Ignore Insertions",
+            "keys": ["--ignore_insertions"],
+            "help": "Ignore insertions events for the quantification and visualization",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "ignore_deletions": {
+            "name": "Ignore Deletions",
+            "keys": ["--ignore_deletions"],
+            "help": "Ignore deletions events for the quantification and visualization",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "discard_indel_reads": {
+            "keys": ["--discard_indel_reads"],
+            "help": "Discard reads with indels in the quantification window from analysis",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "needleman_wunsch_gap_open": {
+            "keys": ["--needleman_wunsch_gap_open"],
+            "help": "Gap open option for Needleman-Wunsch alignment",
+            "type": "int",
+            "default": -20,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "needleman_wunsch_gap_extend": {
+            "keys": ["--needleman_wunsch_gap_extend"],
+            "help": "Gap extend option for Needleman-Wunsch alignment",
+            "type": "int",
+            "default": -2,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "needleman_wunsch_gap_incentive": {
+            "keys": ["--needleman_wunsch_gap_incentive"],
+            "help": "Gap incentive value for inserting indels at cut sites",
+            "type": "int",
+            "default": 1,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "needleman_wunsch_aln_matrix_loc": {
+            "name": "Needleman Wunsch Alignment Matrix Location",
+            "keys": ["--needleman_wunsch_aln_matrix_loc"],
+            "help": "Location of the matrix specifying substitution scores in the NCBI format (see ftp://ftp.ncbi.nih.gov/blast/matrices/)",
+            "type": "str",
+            "default": "EDNAFULL",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "aln_seed_count": {
+            "keys": ["--aln_seed_count"],
+            "help": "SUPPRESS",
+            "type": "int",
+            "default": 5,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "aln_seed_len": {
+            "keys": ["--aln_seed_len"],
+            "help": "SUPPRESS",
+            "type": "int",
+            "default": 10,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "aln_seed_min": {
+            "keys": ["--aln_seed_min"],
+            "help": "SUPPRESS",
+            "type": "int",
+            "default": 2,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "plot_histogram_outliers": {
+            "keys": ["--plot_histogram_outliers"],
+            "help": "If set, all values will be shown on histograms. By default (if unset), histogram ranges are limited to plotting data within the 99 percentile.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "plot_window_size": {
+            "name": "Plot Window Size",
+            "keys": ["--plot_window_size", "--offset_around_cut_to_plot"],
+            "type": "int",
+            "help": "Defines the size of the window extending from the quantification window center to plot. Nucleotides within plot_window_size of the quantification_window_center for each guide are plotted.",
+            "default": 20,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "min_frequency_alleles_around_cut_to_plot": {
+            "keys": ["--min_frequency_alleles_around_cut_to_plot"],
+            "type": "float",
+            "help": "Minimum %% reads required to report an allele in the alleles table plot.",
+            "default": 0.2,
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "expand_allele_plots_by_quantification": {
+            "keys": ["--expand_allele_plots_by_quantification"],
+            "help": "If set, alleles with different modifications in the quantification window (but not necessarily in the plotting window (e.g. for another sgRNA)) are plotted on separate lines, even though they may have the same apparent sequence. To force the allele plot and the allele table to be the same, set this parameter. If unset, all alleles with the same sequence will be collapsed into one row.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "allele_plot_pcts_only_for_assigned_reference": {
+            "name": "Allele Plot Percentages Only for Assigned Reference",
+            "keys": ["--allele_plot_pcts_only_for_assigned_reference"],
+            "help": "If set, in the allele plots, the percentages will show the percentage as a percent of reads aligned to the assigned reference. Default behavior is to show percentage as a percent of all reads.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "quantification_window_coordinates": {
+            "keys": ["-qwc", "--quantification_window_coordinates"],
+            "type": "str",
+            "help": "Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the '--quantification_window_center', '--cleavage_offset', '--window_around_sgrna' or '--window_around_sgrna' values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separted by the dash sign (e.g. 'start-stop'), and multiple ranges can be separated by the underscore (_) (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 6th-11th bp in the first reference and the 6th-11th and 21st-31st bp in the second reference). A value of 0 disables this filter for a particular amplicon (e.g. 0,90-110 This would disable the quantification window for the first amplicon and specify the quantification window of 90-110 for the second).Note that if there are multiple amplicons provided, and only one quantification window coordinate is provided, the same quantification window will be used for all amplicons and be adjusted to account for insertions/deletions.(default: None)",
+            "default": null,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "annotate_wildtype_allele": {
+            "keys": ["--annotate_wildtype_allele"],
+            "type": "str",
+            "help": "Wildtype alleles in the allele table plots will be marked with this string (e.g. **).",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "keep_intermediate": {
+            "keys": ["--keep_intermediate"],
+            "help": "Keep all the intermediate files",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "dump": {
+            "keys": ["--dump"],
+            "help": "Dump numpy arrays and pandas dataframes to file for debugging purposes",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "write_detailed_allele_table": {
+            "keys": ["--write_detailed_allele_table"],
+            "help": "If set, a detailed allele table will be written including alignment scores for each read sequence.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "fastq_output": {
+            "keys": ["--fastq_output"],
+            "help": "If set, a fastq file with annotations for each read will be produced.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "bam_output": {
+            "keys": ["--bam_output"],
+            "help": "If set, a bam file with alignments for each read will be produced.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "bowtie2_index": {
+            "keys": ["-x", "--bowtie2_index"],
+            "type": "str",
+            "help": "Basename of Bowtie2 index for the reference genome",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "zip_output": {
+            "keys": ["--zip_output"],
+            "help": "If set, the output will be placed in a zip folder.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "max_rows_alleles_around_cut_to_plot": {
+            "keys": ["--max_rows_alleles_around_cut_to_plot"],
+            "type": "int",
+            "help": "Maximum number of rows to report in the alleles table plot.",
+            "default": 50,
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "suppress_report": {
+            "keys": ["--suppress_report"],
+            "help": "Suppress output report",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "place_report_in_output_folder": {
+            "keys": ["--place_report_in_output_folder"],
+            "help": "If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "suppress_plots": {
+            "keys": ["--suppress_plots"],
+            "help": "Suppress output plots",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "write_cleaned_report": {
+            "keys": ["--write_cleaned_report"],
+            "help": "SUPPRESS",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "base_editor_output": {
+            "name": "Base Editor Output",
+            "keys": ["--base_editor_output"],
+            "help": "Outputs plots and tables to aid in analysis of base editor studies.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "conversion_nuc_from": {
+            "keys": ["--conversion_nuc_from"],
+            "type": "str",
+            "help": "For base editor plots, this is the nucleotide targeted by the base editor",
+            "default": "C",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "conversion_nuc_to": {
+            "keys": ["--conversion_nuc_to"],
+            "type": "str",
+            "help": "For base editor plots, this is the nucleotide produced by the base editor",
+            "default": "T",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_pegRNA_spacer_seq": {
+            "name": "Prime Editing Spacer Sequence",
+            "keys": ["--prime_editing_pegRNA_spacer_seq"],
+            "type": "str",
+            "help": "pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the given sequence.",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_pegRNA_extension_seq": {
+            "name": "Prime Editing Extension Sequence",
+            "keys": ["--prime_editing_pegRNA_extension_seq"],
+            "type": "str",
+            "help": "Extension sequence used in prime editing. The sequence should be given in the RNA 5'->3' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS).",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_pegRNA_extension_quantification_window_size": {
+            "name": "Prime Editing pegRNA Extension Quantification Window Size",
+            "keys": ["--prime_editing_pegRNA_extension_quantification_window_size"],
+            "type": "int",
+            "help": "Quantification window size (in bp) at flap site for measuring modifications anchored at the right side of the extension sequence. Similar to the --quantification_window parameter, the total length of the quantification window will be 2x this parameter. Default: 5bp (10bp total window size)",
+            "default": 5,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_pegRNA_scaffold_seq": {
+            "name": "Prime Editing pegRNA Scaffold Sequence",
+            "keys": ["--prime_editing_pegRNA_scaffold_seq"],
+            "type": "str",
+            "help": "If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that the RT template directly follows this sequence. A common value is 'GGCACCGAGUCGGUGC'.",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_pegRNA_scaffold_min_match_length": {
+            "name": "Prime Editing pegRNA Scaffold Min Match Length",
+            "keys": ["--prime_editing_pegRNA_scaffold_min_match_length"],
+            "type": "int",
+            "help": "Minimum number of bases matching scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences.",
+            "default": 1,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_nicking_guide_seq": {
+            "name": "Prime Editing Nicking Guide Sequence",
+            "keys": ["--prime_editing_nicking_guide_seq"],
+            "type": "str",
+            "help": "Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_override_prime_edited_ref_seq": {
+            "name": "Prime Editing Override Prime Edited Reference Sequence",
+            "keys": ["--prime_editing_override_prime_edited_ref_seq"],
+            "type": "str",
+            "help": "If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence.",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_override_sequence_checks": {
+            "keys": ["--prime_editing_override_sequence_checks"],
+            "help": "If set, checks to assert that the prime editing guides and extension sequence are in the proper orientation are not performed. This may be useful if the checks are failing inappropriately, but the user is confident that the sequences are correct.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_gap_open_penalty": {
+            "keys": ["--prime_editing_gap_open_penalty"],
+            "type": "int",
+            "help": "SUPPRESS",
+            "default": -50,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "prime_editing_gap_extend_penalty": {
+            "keys": ["--prime_editing_gap_extend_penalty"],
+            "type": "int",
+            "help": "SUPPRESS",
+            "default": 0,
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "crispresso1_mode": {
+            "name": "CRISPResso 1 Mode",
+            "keys": ["--crispresso1_mode"],
+            "help": "Parameter usage as in CRISPResso 1",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "dsODN": {
+            "name": "dsODN",
+            "keys": ["--dsODN"],
+            "type": "str",
+            "help": "Label reads with the dsODN sequence provided",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "auto": {
+            "keys": ["--auto"],
+            "help": "Infer amplicon sequence from most common reads",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "debug": {
+            "keys": ["--debug"],
+            "help": "Show debug messages",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "no_rerun": {
+            "keys": ["--no_rerun"],
+            "help": "Don't rerun CRISPResso2 if a run using the same parameters has already been finished.",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "n_processes": {
+            "name": "Number of Processes",
+            "keys": ["-p", "--n_processes"],
+            "type": "str",
+            "help": "Specify the number of processes to use for analysis. Please use with caution since increasing this parameter will significantly increase the memory required to run CRISPResso. Can be set to 'max'.",
+            "default": "1",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "bam_input": {
+            "keys": ["--bam_input"],
+            "type": "str",
+            "help": "Aligned reads for processing in bam format",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled"]
+        },
+        "bam_chr_loc": {
+            "name": "BAM Chromosome Location",
+            "keys": ["--bam_chr_loc"],
+            "type": "str",
+            "help": "Chromosome location in bam for reads to process. For example: 'chr1:50-100' or 'chrX'.",
+            "default": "",
+            "tools": ["Core", "Batch", "Pooled"]
+        },
+        "save_also_png": {
+            "keys": ["--save_also_png"],
+            "action": "store_true",
+            "help": "SUPPRESS",
+            "tools": ["Core", "Batch", "Pooled", "WGS"]
+        },
+        "batch_settings": {
+            "keys": ["-bs", "--batch_settings"],
+            "help": "Settings file for batch. Must be tab-separated text file. The header row contains CRISPResso parameters (e.g., fastq_r1, fastq_r2, amplicon_seq, and other optional parameters). Each following row sets parameters for an additional batch.",
+            "type": "str",
+            "required": true,
+            "tools": ["Batch"]
+        },
+        "skip_failed": {
+            "keys": ["--skip_failed"],
+            "help": "Continue with batch analysis even if one sample fails",
+            "action": "store_true",
+            "tools": ["Batch", "Pooled", "WGS"]
+        },
+        "min_reads_for_inclusion": {
+            "keys": ["--min_reads_for_inclusion"],
+            "help": "Minimum number of reads for a batch to be included in the batch summary",
+            "type": "int",
+            "default": 0,
+            "tools": ["Batch"]
+        },
+        "batch_output_folder": {
+            "keys": ["-bo", "--batch_output_folder"],
+            "help": "Directory where batch analysis output will be stored",
+            "type": "str",
+            "default": "",
+            "tools": ["Batch"]
+        },
+        "suppress_batch_summary_plots": {
+            "keys": ["--suppress_batch_summary_plots"],
+            "help": "Suppress batch summary plots - e.g. if many samples are run at once, the summary plots of all sub-runs may be too large. This parameter suppresses the production of these plots.",
+            "action": "store_true",
+            "tools": ["Batch"]
+        },
+        "crispresso_command": {
+            "name": "CRISPResso Command",
+            "keys": ["--crispresso_command"],
+            "help": "CRISPResso command to call",
+            "type": "str",
+            "default": "CRISPResso",
+            "tools": ["Batch", "Pooled", "WGS", "Meta"]
+        },
+        "amplicons_file": {
+            "keys": ["-f", "--amplicons_file"],
+            "help": "R|Amplicons description file. This file is a tab-delimited text file with up to 14 columns (2 required):\n  - amplicon_name:  an identifier for the amplicon (must be unique).\n  - amplicon_seq:  amplicon sequence used in the experiment.\n  - guide_seq (OPTIONAL):  sgRNA sequence used for this amplicon without the PAM sequence. Multiple guides can be given separated by commas and not spaces.\n  - expected_hdr_amplicon_seq (OPTIONAL): expected amplicon sequence in case of HDR.\n  - coding_seq (OPTIONAL): Subsequence(s) of the amplicon corresponding to coding sequences. If more than one separate them by commas and not spaces.\n  - prime_editing_pegRNA_spacer_seq (OPTIONAL): pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the given sequence.\n  - prime_editing_nicking_guide_seq (OPTIONAL): Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence.\n  - prime_editing_pegRNA_extension_seq (OPTIONAL): Extension sequence used in prime editing. The sequence should be given in the RNA 5'->3' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS).\n  - prime_editing_pegRNA_scaffold_seq (OPTIONAL): If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that the RT template directly follows this sequence. A common value ends with 'GGCACCGAGUCGGUGC'.\n  - prime_editing_pegRNA_scaffold_min_match_length (OPTIONAL): Minimum number of bases matching scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences.\n  - prime_editing_override_prime_edited_ref_seq (OPTIONAL): If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence.\n  - quantification_window_coordinates (OPTIONAL): Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the '--quantification_window_center', '-- cleavage_offset', '--window_around_sgrna' or '-- window_around_sgrna' values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separated by the dash sign like 'start-stop', and multiple ranges can be separated by the underscore (_). A value of 0 disables this filter. (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 5th-10th bp in the first reference and the 5th-10th and 20th-30th bp in the second reference) (default: None)\n  - quantification_window_size (OPTIONAL): Defines the size (in bp) of the quantification window extending from the position specified by the '--cleavage_offset' or '--quantification_window_center' parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp.\n  - quantification_window_center (OPTIONAL): Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17.",
+            "type": "str",
+            "default": "",
+            "tools": ["Pooled"]
+        },
+        "gene_annotations": {
+            "keys": ["--gene_annotations"],
+            "help": "Gene Annotation Table from UCSC Genome Browser Tables (http://genome.ucsc.edu/cgi-bin/hgTables?command=start), please select as table 'knownGene', as output format 'all fields from selected table' and as file returned 'gzip compressed'",
+            "type": "str",
+            "default": "",
+            "tools": ["WGS", "Pooled"]
+        },
+        "bowtie2_options_string": {
+            "keys": ["--bowtie2_options_string"],
+            "help": "Override options for the Bowtie2 alignment command. By default, this is ' --end-to-end -N 0 --np 0 -mp 3,2 --score-min L,-5,-3(1-H)' where H is the default homology score.",
+            "type": "str",
+            "default": "",
+            "tools": ["Pooled"]
+        },
+        "use_legacy_bowtie2_options_string": {
+            "keys": ["--use_legacy_bowtie2_options_string"],
+            "help": "Use legacy (more stringent) Bowtie2 alignment parameters: ' -k 1 --end-to-end -N 0 --np 0 '.",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "min_reads_to_use_region_pooled": {
+            "name": "Minimum Reads to Use Region",
+            "keys": ["--min_reads_to_use_region"],
+            "help": "Minimum number of reads that align to a region to perform the CRISPResso analysis",
+            "type": "float",
+            "default": 1000,
+            "tools": ["Pooled"]
+        },
+        "skip_reporting_problematic_regions": {
+            "keys": ["--skip_reporting_problematic_regions"],
+            "help": "Skip reporting of problematic regions. By default, when both amplicons (-f) and genome (-x) are provided, problematic reads that align to the genome but to positions other than where the amplicons align are reported as problematic",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "compile_postrun_references": {
+            "keys": ["--compile_postrun_references"],
+            "help": "If set, a file will be produced which compiles the reference sequences of frequent amplicons.",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "compile_postrun_reference_allele_cutoff": {
+            "keys": ["--compile_postrun_reference_allele_cutoff"],
+            "help": "Only alleles with at least this percentage frequency in the population will be reported in the postrun analysis. This parameter is given as a percent, so 30 is 30%%.",
+            "type": "float",
+            "default": "30",
+            "tools": ["Pooled"]
+        },
+        "alternate_alleles": {
+            "keys": ["--alternate_alleles"],
+            "help": "Path to tab-separated file with alternate allele sequences for pooled experiments. This file has the columns 'region_name','reference_seqs', and 'reference_names' and gives the reference sequences of alternate alleles that will be passed to CRISPResso for each individual region for allelic analysis. Multiple reference alleles and reference names for a given region name are separated by commas (no spaces).",
+            "type": "str",
+            "default": "",
+            "tools": ["Pooled"]
+        },
+        "limit_open_files_for_demux": {
+            "keys": ["--limit_open_files_for_demux"],
+            "help": "If set, only one file will be opened during demultiplexing of read alignment locations. This will be slightly slower as the reads must be sorted, but may be necessary if the number of amplicons is greater than the number of files that can be opened due to OS constraints.",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "aligned_pooled_bam": {
+            "keys": ["--aligned_pooled_bam"],
+            "help": "Path to aligned input for CRISPRessoPooled processing. If this parameter is specified, the alignments in the given bam will be used to demultiplex reads. If this parameter is not set (default), input reads provided by --fastq_r1 (and optionally --fastq_r2) will be aligned to the reference genome using bowtie2. If the input bam is given, the corresponding reference fasta must also be given to extract reference genomic sequences via the parameter --bowtie2_index. Note that if the aligned reads are paired-end sequenced, they should already be merged into 1 read (e.g. via Flash) before alignment.",
+            "type": "str",
+            "tools": ["Pooled"]
+        },
+        "demultiplex_only_at_amplicons": {
+            "keys": ["--demultiplex_only_at_amplicons"],
+            "help": "DEPRECATED in v2.3.2, see `demultiplex_at_amplicons_and_genome`",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "demultiplex_genome_wide": {
+            "keys": ["--demultiplex_genome_wide"],
+            "help": "If set, and an amplicon file (--amplicons_file) and reference sequence (--bowtie2_index) are provided, the entire genome will be demultiplexed and reads with the exact same start and stop coordinates as an amplicon will be assigned to that amplicon. If this flag is not set, reads overlapping alignment positions of amplicons will be demultiplexed and assigned to that amplicon.",
+            "action": "store_true",
+            "tools": ["Pooled"]
+        },
+        "bam_file": {
+            "keys": ["-b", "--bam_file"],
+            "help": "WGS aligned bam file",
+            "type": "str",
+            "required": true,
+            "default": "bam filename",
+            "tools": ["WGS"]
+        },
+        "region_file": {
+            "keys": ["-f", "--region_file"],
+            "help": "R|Regions description file. A BED format file containing the regions to analyze, one per line. The REQUIRED columns are:\n  - chr_id (chromosome name)\n  - bpstart (start position)\n  - bpend (end position)\n\nThe optional columns are:\n  - name (an unique indentifier for the region)\n  - guide_seq\n  - expected_hdr_amplicon_seq\n  - coding_seq\nSee CRISPResso --help for more details on these last 3 parameters",
+            "type": "str",
+            "required": true,
+            "tools": ["WGS"]
+        },
+        "reference_file": {
+            "keys": ["-r", "--reference_file"],
+            "help": "A FASTA format reference file (for example hg19.fa for the human genome)",
+            "type": "str",
+            "required": true,
+            "default": "",
+            "tools": ["WGS"]
+        },
+        "min_reads_to_use_region_wgs": {
+            "name": "Minimum Reads to Use Region",
+            "keys": ["--min_reads_to_use_region"],
+            "help": "Minimum number of reads that align to a region to perform the CRISPResso analysis for WGS",
+            "type": "float",
+            "default": 10,
+            "tools": ["WGS"]
+        },
+        "crispresso_output_folder_1": {
+            "keys": ["crispresso_output_folder_1"],
+            "help": "First output folder with CRISPResso analysis",
+            "type": "str",
+            "tools": ["Compare"]
+        },
+        "crispresso_output_folder_2": {
+            "keys": ["crispresso_output_folder_2"],
+            "help": "Second output folder with CRISPResso analysis",
+            "type": "str",
+            "tools": ["Compare"]
+        },
+        "sample_1_name": {
+            "keys": ["-n1", "--sample_1_name"],
+            "help": "Sample 1 name",
+            "tools": ["Compare"]
+        },
+        "sample_2_name": {
+            "keys": ["-n2", "--sample_2_name"],
+            "help": "Sample 2 name",
+            "tools": ["Compare"]
+        },
+        "reported_qvalue_cutoff": {
+            "keys": ["--reported_qvalue_cutoff"],
+            "help": "Q-value cutoff for significance in tests for differential editing. Each base position is tested (for insertions, deletions, substitutions, and all modifications) using Fisher's exact test, followed by Bonferroni correction. The number of bases with significance below this threshold in the quantification window are counted and reported in the output summary.",
+            "type": "float",
+            "default": 0.05,
+            "tools": ["Compare"]
+        },
+        "disable_guardrails":{
+            "keys": ["--disable_guardrails"],
+            "help": "Disable guardrail warnings",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "use_matplotlib": {
+            "keys": ["--use_matplotlib"],
+            "help": "Use matplotlib for plotting instead of plotly/d3 when CRISPRessoPro is installed",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        },
+        "halt_on_plot_fail": {
+            "keys": ["--halt_on_plot_fail"],
+            "help": "Halt execution if a plot fails to generate",
+            "action": "store_true",
+            "tools": ["Core", "Batch", "Pooled", "WGS", "Compare"]
+        }
+    },
+    "Sections": {
+        "Required": {
+            "Core": []
+        },
+        "Optional": {
+            "Prime Editing": []
+        }
+    }
+}
diff --git a/CRISPResso2/default_style.json b/CRISPResso2/default_style.json
new file mode 100644
index 00000000..9876b670
--- /dev/null
+++ b/CRISPResso2/default_style.json
@@ -0,0 +1,13 @@
+{
+  "colors": {
+    "Substitution": "#0000FF",
+    "Insertion": "#008000",
+    "Deletion": "#FF0000",
+    "A": "#7FC97F",
+    "T": "#BEAED4",
+    "C": "#FDC086",
+    "G": "#FFFF99",
+    "N": "#C8C8C8",
+    "-": "#C1C1C1"
+  }
+}
\ No newline at end of file
diff --git a/CRISPResso2/templates/layout.html b/CRISPResso2/templates/layout.html
deleted file mode 100644
index 5eba4687..00000000
--- a/CRISPResso2/templates/layout.html
+++ /dev/null
@@ -1,104 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>CRISPResso2 Report</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, initial-scale=1">
-		<meta name="description" content="CRISPResso2: Analysis of genome editing outcomes from deep sequencing data">
-		<meta name="author" content="Kendell Clement and Luca Pinello">
-		<link href='https://fonts.googleapis.com/css?family=Montserrat|Ranga:700' rel='stylesheet' type='text/css'>
-        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
-		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.3.5/jquery.fancybox.min.css" />
-		<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
-		<style>
-			body {
-				font-family: 'Montserrat', sans-serif !important;
-			}
-			@media (max-width:1550px)
-			{
-				#crispresso_h
-				{
-					font-size:5rem;
-					font-weight:300;
-					line-height:1.2;
-				}
-			}
-			@media (min-width:1550px)
-			{
-				#crispresso_h
-				{
-					font-size:8rem;
-					font-weight:300;
-					line-height:1.2;
-				}
-			}
-		</style>
-
-
-		<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
-		Remove this if you use the .htaccess -->
-		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-
-		<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD//////////////////////v7+//v7+//7+vr/8/Ly//v6+v/7+/v//v7+/////////////////////////////////////////////v7+///////AwcH/gYSE/3t+fv9+gYD/paWl//39/f/+/v7//v7+/////////////////////////////v7+///////z8/P/hYiI/5adnP+Xn57/lZ6d/42Tk//X19f///////39/f/+/v7///////////////////////39/f//////ury7/5GWlv+psK//nqWk/6Cnpv+osa//kpeW/8rMy//+/v7//////////////////////////////////////5ibm/+orq3/o6mo/5+mpf+gpqT/rLOx/5Wbmf95fnz/oKSj/+7u7v///////v7+///////+/v7///////Hy8v+boKD/s7q5/6yzsf+qsbD/q7Gw/7K5uP+gpqT/mZub/7i7u/+mqan///////7+/v///////v7+///////d3t7/lpyb/5abmv90enn/cXZ2/3N4d/+Ijo7/pKup/4uQj/+Znpz/s7W0///////+/v7///////7+/v//////4+Pj/4aKh/+Ahn//a3Fq/2dtZv9tcmz/i5GK/42Sjv+goaH/5Obl//b29v///////////////////////v7+///////t7u7/1tfW/8nMyf/Ex8T/yczJ/83Qzf/a29r/+fn5///////+/v7/////////////////////////////////+fn5//z8/P/m5ub/9vb2//n5+f/y8vL//f39///////9/f3////////////////////////////4+Pj/5+fn/+np6f//////5OTk/+/v7//k5OT/+vr6/+fn5//l5eX/+vr6//////////////////7+/v//////5ubm//X19f///////f39/+Tk5P//////5eXl//j4+P//////8fHx/+Tk5P///////v7+////////////2dnZ/8nJyf///////////+/v7//IyMj///////Pz8//m5ub///////////+9vb3/09PT////////////+fn5/9DQ0P/39/f////////////c3Nz/sLCw///////7+/v/sLCw//n5+f//////+Pj4/+Hh4f/+/v7/////////////////////////////////8/Pz//X19f///////////8/Pz//o6Oj///////39/f///////////////////////f39///////////////////////+/v7////////////+/v7//v7+/////////////v7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" />
-		<!--<link rel="shortcut icon" href="favicon.ico">-->
-		<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
-
-	{% block head %}{% endblock %}
-
-	</head>
-
-	<body>
-		<div class="container">
-
-			<div class="row pb-2">
-				<div class="col-sm-1"></div>
-				<div class="col-sm-3"> <a href='https://crispresso2.pinellolab.org'><img class='img-fluid' src="https://crispresso.pinellolab.partners.org/static/imgs/CRISPResso_justcup.png" width="80%"></a>
-				</div>
-				<div class="col-sm-7" >
-					<a href='https://crispresso2.pinellolab.org' style="text-decoration:none !important; color:rgb(33, 37, 41)"><h1 id='crispresso_h' style="font-family:'Ranga', sans-serif">CRISPResso2 </h1></a>
-					<h3 style="margin-top: -0.5em;padding-right: 3em;">Analysis of genome editing outcomes from deep sequencing data</h3>
-				</div>
-				<div class="col-sm-1">
-
-				</div>
-			</div>
-
-			<div class="row">
-				{% block content %}{% endblock %}
-
-			</div>
-
-			<div class="row">
-
-
-			<div class="col-sm-1"></div>
-			<footer class="col-sm-10 crispresso_body_div" style='padding-bottom:3em'>
-
-				<p class="text-center">
-      If you like CRISPResso2 please support us by citing it in your work: <br>
-					Clement K, Rees H, Canver MC, Gehrke JM, Farouni R, Hsu JY, Cole MA, Liu DR, Joung JK, Bauer DE, Pinello L.<br>
-					<a href="https://rdcu.be/boxVG" target="_blank">CRISPResso2 provides accurate and rapid genome editing sequence analysis. </a><br>
-					Nat Biotechnol. 2019 Mar; 37(3):224-226. doi: 10.1038/s41587-019-0032-3. PubMed PMID: 30809026.
-				</p>
-				<p class="text-center"><small> &copy; Copyright <a href="https://pinellolab.org">Kendell Clement and Luca Pinello</a></small></p>
-				<p class="text-center">
-					<small>
-				<a href="https://twitter.com/intent/tweet?text=CRISPResso2%20provides%20accurate%20and%20rapid%20genome%20editing%20sequence%20analysis%20%40kendellclement%20%40lucapinello%20%23CRISPR%20%23CRISPResso2"
-					class="twitter-share-button" data-via="kendellclement" data-size="large" data-count="none" data-hashtags="CRISPR" data-url="https://crispresso2.pinellolab.org">Tweet about CRISPresso2!</a>
-				</small>
-				</p>
-
-			</footer>
-			<div class="col-sm-1"></div>
-
-
-			<!-- Optional JavaScript -->
-	    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
-            <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
-			<script src="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.3.5/jquery.fancybox.min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-			{% block foot %}{% endblock %}
-	</body>
-
-</html>
diff --git a/CRISPResso2/templates/report.html b/CRISPResso2/templates/report.html
deleted file mode 100644
index 37cc4bae..00000000
--- a/CRISPResso2/templates/report.html
+++ /dev/null
@@ -1,854 +0,0 @@
-{% extends "layout.html" %}
-{% block head %}
-<style>
-.nav-tabs.amp-header {
-  border-bottom:none !important;
-}
-
-.card-header.amp-header {
-  border-bottom:none !important;
-}
-.nav-tabs.amp-header .nav-link.active {
-  background-color:lightsteelblue;
-  border-bottom:lightsteelblue;
-}
-
-.pre-scrollable {
-    max-height: 340px;
-    overflow-y: auto;
-    background-color: #f8f8f8;
-    text-align: left
-}
-
-.tab-content.amp-body {
-  background-color:lightsteelblue;
-}
-
-@media print {
-   .tab-content > .tab-pane {
-    display: block !important;
-    opacity: 1 !important;
-    visibility: visible !important;
-		margin-bottom: 2em !important;
-		page-break-inside: avoid;
-  }
-  .nav-tabs {
-    display:none !important;
-    visibility:hidden !important;
-  }
-  .tab-content.amp-body {
-    background-color:transparent !important;
-  	border:None !important;
-  }
-}
-@media only screen and (max-width: 600px) {
-	.jumbotron img {
-		width:100%
-	}
-}
-</style>
-
-{% endblock %}
-
-{% block content %}
-<div class="col-sm-1"></div>
-<div class="col-sm-10">
-
-	<div class="jumbotron" style="background:rgba(0,0,0,0.0); padding:0px" >
-		<div id='jumbotron_content' >
-
-				<div class='card text-center mb-2'>
-					<div class='card-header'>
-              {% if report_data['report_display_name'] != '' %}
-      						<h5>{{report_data['report_display_name']}}</h5>
-              {% endif %}
-  						<h5>CRISPResso2 run information</h5>
-						<ul class="nav nav-tabs justify-content-center card-header-tabs" id="log-tab" role="tablist">
-						  <li class="nav-item">
-						    <button class="nav-link active" id="log_aln-tab" data-bs-toggle="tab" data-bs-target="#log_aln" role="tab" aria-controls="log_aln" aria-selected="true">Alignment statistics</button>
-						  </li>
-						  <li class="nav-item">
-						    <button class="nav-link" id="log_params-tab" data-bs-toggle="tab" data-bs-target="#log_params" role="tab" aria-controls="log_params" aria-selected="false">Run Parameters</button>
-						  </li>
-						</ul>
-					</div>
-					<div class='card-body'>
-            <div class='tab-content'>
-  					  <div class="tab-pane fade show active" id="log_aln" role="tabpanel">
-						  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1a']}}.png" width='40%' ></a>
-    						<label class="labelpadding">{{report_data['fig_captions']['plot_1a']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas']['plot_1a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-  						</div>
-  					  <div class="tab-pane fade text-start" id="log_params" role="tabpanel">
-                <p>CRISPResso version: {{report_data['run_data']['running_info']['version']}}</p>
-                <p>Run completed: {{report_data['run_data']['running_info']['end_time_string']}}</p>
-                <p>Amplicon sequence: <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args']['amplicon_seq']}}</pre></p>
-                {% if report_data['run_data']['running_info']['args']['guide_seq'] != '' %}
-                  <p>Guide sequence: <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args']['guide_seq']}}</pre></p>
-                {% endif %}
-                <p>Command used: <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['command_used']}}</pre></p>
-                <p>Parameters: <pre class='pre-scrollable'>{{report_data['run_data']['running_info']['args_string']}}</pre></p>
-		<p class='text-center m-0'><small><a href="{{report_data['crispresso_data_path']}}{{report_data['run_data']['running_info']['log_filename']}}">Running log</a></small></p>
-  						</div>
-            </div>
-					</div>
-				</div>
-
-				<div class='card text-center mb-2'>
-					<div class='card-header'>
-						<h5>Allele assignments</h5>
-						<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
-						  <li class="nav-item">
-						    <button class="nav-link active" id="aln_pie-tab" data-bs-toggle="tab" data-bs-target="#aln_pie" role="tab" aria-controls="aln_pie" aria-selected="true">Piechart</button>
-						  </li>
-						  <li class="nav-item">
-						    <button class="nav-link" id="aln_bar-tab" data-bs-toggle="tab" data-bs-target="#aln_bar" role="tab" aria-controls="aln_bar" aria-selected="false">Barplot</button>
-						  </li>
-    					{% if report_data['fig_locs']['plot_1d'] %}
-						  <li class="nav-item">
-						    <button class="nav-link" id="aln_dsODN-tab" data-bs-toggle="tab" data-bs-target="#aln_dsODN" role="tab" aria-controls="aln_dsODN" aria-selected="false">dsODN</button>
-						  </li>
-              {% endif %}
-						</ul>
-					</div>
-					<div class='card-body'>
-						<div class="tab-content" id="tabContent">
-						  <div class="tab-pane fade show active" id="aln_pie" role="tabpanel" aria-labelledby="aln_pie-tab">
-							  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1b']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1b']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions']['plot_1b']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas']['plot_1b'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						  <div class="tab-pane fade" id="aln_bar" role="tabpanel" aria-labelledby="aln_bar-tab">
-							  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1c']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1c']}}.png" width='40%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions']['plot_1c']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas']['plot_1c'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-    					{% if report_data['fig_locs']['plot_1d'] %}
-						  <div class="tab-pane fade" id="aln_dsODN" role="tabpanel" aria-labelledby="aln_dsODN-tab">
-							  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1d']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_1d']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions']['plot_1d']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas']['plot_1d'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-              {% endif %}
-						</div>
-
-					</div>
-				</div> {# end card #}
-
-        {# start global coding sequence report #}
-					{% if report_data['fig_locs']['plot_5a'] %}
-						<div class='card text-center mb-2'>
-							<div class='card-header'>
-    						<h5>Global frameshift analysis</h5>
-							</div>
-					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_5a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_5a']}}.png" width='50%' ></a>
-							<label class="labelpadding">{{report_data['fig_captions']['plot_5a']}}</label>
-						  {% for (data_label,data_path) in report_data['fig_datas']['plot_5a'] %}
-						  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-              {% endfor %}
-						</div>
-					</div>
-					{% endif %}
-
-					{% if report_data['fig_locs']['plot_6a'] %}
-						<div class='card text-center mb-2'>
-							<div class='card-header'>
-    						<h5>Global frameshift mutagenesis profiles</h5>
-							</div>
-					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_6a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_6a']}}.png" width='95%' ></a>
-							<label class="labelpadding">{{report_data['fig_captions']['plot_6a']}}</label>
-						  {% for (data_label,data_path) in report_data['fig_datas']['plot_6a'] %}
-						  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-              {% endfor %}
-						</div>
-					</div>
-					{% endif %}
-
-					{% if report_data['fig_locs']['plot_8a'] %}
-						<div class='card text-center mb-2'>
-							<div class='card-header'>
-    						<h5>Global splicing analysis</h5>
-							</div>
-					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_8a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_8a']}}.png" width='50%' ></a>
-							<label class="labelpadding">{{report_data['fig_captions']['plot_8a']}}</label>
-						  {% for (data_label,data_path) in report_data['fig_datas']['plot_8a'] %}
-						  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-              {% endfor %}
-						</div>
-					</div>
-					{% endif %}
-          {# end of global coding sequence analysis #}
-
-            {# start hdr summary #}
-						{% if report_data['fig_locs'][report_data.amplicons[0]]['plot_4g'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>HDR summary plot</h5>
-							{% else %}
-  						<h5>HDR summary report (all reads aligned to {{report_data.amplicons[0]}})</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]]['plot_4g']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]]['plot_4g']}}.png" width='80%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][report_data.amplicons[0]]['plot_4g']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][report_data.amplicons[0]]['plot_4g'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-            {# end HDR summary #}
-
-            {# start prime editing report #}
-						{% if report_data['fig_locs'][report_data.amplicons[0]]['plot_11a'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>Prime editing report</h5>
-							{% else %}
-  						<h5>Prime editing report (all reads aligned to {{report_data.amplicons[0]}})</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]]['plot_11a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]]['plot_11a']}}.png" width='80%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][report_data.amplicons[0]]['plot_11a']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][report_data.amplicons[0]]['plot_11a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-				{% if report_data['sgRNA_based_fig_names'][report_data.amplicons[0]] and  report_data['sgRNA_based_fig_names'][report_data.amplicons[0]]['11b']%}
-				<div class='card text-center mb-2'>
-					<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-						<h5>Prime editing summary plots at analysis positions</h5>
-							{% else %}
-  						<h5>Prime editing summary plots at analysis positions (aligned to {{report_data.amplicons[0]}})</h5>
-							{% endif %}
-					</div>
-					<div class='card-body'>
-								{% for fig_name in report_data['sgRNA_based_fig_names'][report_data.amplicons[0]]['11b'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][report_data.amplicons[0]][fig_name]}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][report_data.amplicons[0]][fig_name]}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][report_data.amplicons[0]][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-                </div>
-							{% endfor %}
-						</div>
-					</div>
-					{% endif %} {# end plot 11b for prime editing #}
-
-						{% if report_data['fig_locs']['plot_11c'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-  						<h5>Scaffold insertions</h5>
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_11c']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs']['plot_11c']}}.png" width='40%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions']['plot_11c']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas']['plot_11c'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-            {# end prime editing section #}
-
-
-					{% if report_data.amplicons|length == 1 %}
-          <div> {# if only one amplicon, just a normal div #}
-          {% else %}
-	        {# If there is more than one amplicon, print a navigation to show each amplicon #}
-					<p>Reads are aligned to each amplicon sequence separately. Quantification and visualization of these reads are shown for each amplicon below:</p>
-				<div id='amplicons-card' class='card text-center mb-2'>
-					<div class='card-header amp-header'>
-						<h5>Amplicons</h5>
-						<ul class="nav nav-tabs amp-header card-header-tabs" id="nav-tab" role="tablist">
-							{% for amplicon_name in report_data.amplicons %}
-								{% if loop.index0 == 0 %}
-								  <li class="nav-item">
-								    <button class="nav-link active" id="pills_{{amplicon_name}}_tab" data-bs-toggle="tab" data-bs-target="#div_{{amplicon_name}}" role="tab" aria-controls="div_{{amplicon_name}}" aria-selected="true">{{amplicon_name}}</button>
-								  </li>
-								{% else %}
-								<li class="nav-item">
-									<button class="nav-link" id="pills_{{amplicon_name}}_tab" data-bs-toggle="tab" data-bs-target="#div_{{amplicon_name}}" role="tab" aria-controls="div_{{amplicon_name}}" aria-selected="false">{{amplicon_name}}</button>
-								</li>
-								{% endif%}
-							{% endfor %}
-						</ul>
-				</div>
-					{% endif %} {# end if report contains more than one amplicon #}
-
-
-					{% if report_data.amplicons|length == 1 %} {# if only one amplicon, just a normal div #}
-					<div>
-					{% else %}
-					<div class="tab-content pt-3 pr-3 pl-3 amp-body card-body" id="nav-tabContent">
-					{% endif %}
-
-					{% for amplicon_name in report_data.amplicons %}
-						{% if report_data.amplicons|length == 1 %} {# if only one amplicon, just a normal div #}
-						<div>
-						{% elif loop.index0 == 0 %} {# if more than one amplicon, and the first, this is the active one #}
-						<div class="tab-pane fade show active" id="div_{{amplicon_name}}" role="tabpanel" aria-labelledby="pills_{{amplicon_name}}_tab">
-            <div class="d-none d-print-block"><h3>Reads aligning to {{amplicon_name}}</h3></div> {# this bit appears in print mode #}
-						{% else %} {# otherwise inactive tab #}
-						<div class="tab-pane fade" id="div_{{amplicon_name}}" role="tabpanel" aria-labelledby="pills_{{amplicon_name}}_tab">
-            <div class="d-none d-print-block"><h3>Reads aligning to {{amplicon_name}}</h3> </div>{# this bit appears in print mode #}
-						{% endif %}
-
-      				<div class='card text-center mb-2'>
-      					<div class='card-header'>
-									{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-      						<h5>Nucleotide composition</h5>
-									{% else %}
-      						<h5>Nucleotide composition for {{amplicon_name}}</h5>
-									{% endif %}
-      					</div>
-      					<div class='card-body'>
-						{% if report_data['fig_locs'][amplicon_name]['plot_2a'] %}
-<div class='div_zoom_outer d-none d-md-block' style="height:100px;border:1px solid #DDDDDD;position:relative">
-	<div id="zoomview_nucs_{{amplicon_name}}" style="position:absolute;left:0;height:100%;width:100%;background-image: url({{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_2a']}}.png);background-size:auto 100%;background-repeat:no-repeat;"></div>
-  <div class="d-lg-none" style="overflow-x:scroll;overflow-y:hidden;position:absolute;width:100%;height:100%">
-	  <img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_2a']}}.png" style='height:100%'>
-  </div>
-</div>
-<div style='position:relative;display:inline-block;width:95%'>
-	<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_2a']}}.pdf">
-    <div id='zoomlens_nucs_{{amplicon_name}}' class="d-none d-lg-block" style='float: left;position: absolute;left: 0px;top: 0px;z-index: 1000;border: 1px solid #DDDDDD;height:100%;width:10%'></div>
-    <img id='tozoom_nucs_{{amplicon_name}}' src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_2a']}}.png" width='100%' style='position:relative'>
-  </a>
-</div>
-
-<label class="labelpadding">
-  <span class='d-none d-md-block'>Hover your mouse over the bottom image to zoom in on a specific region.</span><br>
-  {{report_data['fig_captions'][amplicon_name]['plot_2a']}}
-</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_2a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-						{% endif %}
-
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and  report_data['sgRNA_based_fig_names'][amplicon_name]['2b']%}
-  							<div>
-  								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['2b'] %}
-								<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-  								{% endfor %}
-  							</div>
-							{% endif %}
-            </div>
-          </div> {# end card #}
-
-  				<div class='card text-center mb-2'>
-  					<div class='card-header'>
-									{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-      						<h5>Modification lengths</h5>
-									{% else %}
-      						<h5>Modification lengths for {{amplicon_name}}</h5>
-									{% endif %}
-  						<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
-						{% if report_data['fig_locs'][amplicon_name]['plot_3a'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_3a" role="tab" aria-controls="{{amplicon_name}}_3a" aria-selected="true">Summary</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_3b'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_3b" role="tab" aria-controls="{{amplicon_name}}_3b" aria-selected="false">Indels</button>
-  						  </li>
-            {% endif %}
-  						</ul>
-  					</div>
-  					<div class='card-body'>
-  						<div class="tab-content" id="pills-tabContent">
-						{% if report_data['fig_locs'][amplicon_name]['plot_3a'] %}
-  						  <div class="tab-pane fade show active" id="{{amplicon_name}}_3a" role="tabpanel">
-							  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_3a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_3a']}}.png" width='40%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_3a']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_3a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-  							</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_3b'] %}
-  						  <div class="tab-pane fade" id="{{amplicon_name}}_3b" role="tabpanel">
-							  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_3b']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_3b']}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_3b']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_3b'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-  							</div>
-  						</div> {# end card_body #}
-  				</div> {# end card #}
-
-  				<div class='card text-center mb-2'>
-  					<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-  						<h5>Indel characterization</h5>
-							{% else %}
-  						<h5>Indel characterization for {{amplicon_name}}</h5>
-							{% endif %}
-  						<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
-						{% if report_data['fig_locs'][amplicon_name]['plot_4a'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4a" role="tab" aria-controls="{{amplicon_name}}_4a" aria-selected="true">All Modifications Combined</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4b'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4b" role="tab" aria-controls="{{amplicon_name}}_4b" aria-selected="false">All Modifications by Type</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4c'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4c" role="tab" aria-controls="{{amplicon_name}}_4c" aria-selected="false">Modifications in Quantification Window</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4d'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4d" role="tab" aria-controls="{{amplicon_name}}_4d" aria-selected="false">Indel Lengths</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4e'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4e" role="tab" aria-controls="{{amplicon_name}}_4e" aria-selected="false">All reads aligned to {{amplicon_name}}</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4f'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_4f" role="tab" aria-controls="{{amplicon_name}}_4f" aria-selected="false">HDR reads aligned to {{amplicon_name}}</button>
-  						  </li>
-            {% endif %}
-  						</ul>
-  					</div> {# end card head #}
-  					<div class='card-body'>
-  						<div class="tab-content" id="pills-tabContent">
-						{% if report_data['fig_locs'][amplicon_name]['plot_4a'] %}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_4a" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4a']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4a']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4b'] %}
-							  <div class="tab-pane fade show active" id="{{amplicon_name}}_4b" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4b']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4b']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4b']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4b'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-						{% if report_data['fig_locs'][amplicon_name]['plot_4c'] %}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_4c" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4c']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4c']}}.png" width='50%' ></a>
-
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4c']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4c'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_4d'] %}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_4d" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4d']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4d']}}.png" width='100%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4d']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4d'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_4e'] %}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_4e" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4e']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4e']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4e']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4e'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_4f'] %}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_4f" role="tabpanel">
-								  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4f']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_4f']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_4f']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_4f'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						{% endif %}
-  							</div>
-  						</div> {# end card body #}
-  				</div> {# end card #}
-
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_5'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>Frameshift analysis</h5>
-							{% else %}
-  						<h5>Frameshift analysis for {{amplicon_name}}</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_5']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_5']}}.png" width='70%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_5']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_5'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_6'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>Frameshift mutagenesis profiles</h5>
-							{% else %}
-  						<h5>Frameshift mutagenesis profiles for {{amplicon_name}}</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_6']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_6']}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_6']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_6'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_7'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>Non-coding mutations</h5>
-							{% else %}
-  						<h5>Coding mutations for {{amplicon_name}}</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_7']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_7']}}.png" width='40%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_7']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_7'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-						{% if report_data['fig_locs'][amplicon_name]['plot_8'] %}
-							<div class='card text-center mb-2'>
-								<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-									<h5>Splicing</h5>
-							{% else %}
-  						<h5>Splicing for {{amplicon_name}}</h5>
-							{% endif %}
-								</div>
-  					<div class='card-body'>
-						<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_8']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_8']}}.png" width='50%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_8']}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_8'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-							</div>
-						</div>
-						{% endif %}
-
-
-				{% if report_data['sgRNA_based_fig_names'][amplicon_name] and  report_data['sgRNA_based_fig_names'][amplicon_name]['9']%}
-				<div class='card text-center mb-2'>
-					<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-						<h5>Allele plots</h5>
-							{% else %}
-  						<h5>Allele plots for {{amplicon_name}}</h5>
-							{% endif %}
-					</div>
-					<div class='card-body'>
-								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['9'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-                </div>
-							{% endfor %}
-						</div>
-					</div>
-					{% endif %}
-
-					{% if report_data['fig_locs'][amplicon_name]['plot_10a'] %}
-  				<div class='card text-center mb-2'>
-  					<div class='card-header'>
-							{% if report_data.amplicons|length == 1 %} {# if only one amplicon #}
-						<h5>Base editing</h5>
-							{% else %}
-  						<h5>Base editing for {{amplicon_name}}</h5>
-							{% endif %}
-  						<ul class="nav nav-tabs justify-content-center card-header-tabs" id="aln-tab" role="tablist">
-						{% if report_data['fig_locs'][amplicon_name]['plot_10a'] %}
-  						  <li class="nav-item">
-						  <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10a" role="tab" aria-controls="{{amplicon_name}}_10a" aria-selected="true">Substitution Frequencies</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10d']%}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10d" role="tab" aria-controls="{{amplicon_name}}_10d" aria-selected="false">Nucleotide Frequencies</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10e']%}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10e" role="tab" aria-controls="{{amplicon_name}}_10e" aria-selected="false">Base Proportions</button>
-  						  </li>
-            {% endif %}
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10f']%}
-  						  <li class="nav-item">
-						  <button class="nav-link" data-bs-toggle="tab" data-bs-target="#{{amplicon_name}}_10f" role="tab" aria-controls="{{amplicon_name}}_10f" aria-selected="false">Non-reference Bases</button>
-  						  </li>
-            {% endif %}
-  						</ul>
-  					</div>
-  					<div class='card-body'>
-  						<div class="tab-content" id="pills-tabContent">
-						{% if report_data['fig_locs'][amplicon_name]['plot_10c'] %}
-							  <div class="tab-pane fade show active" id="{{amplicon_name}}_10a" role="tabpanel">
-                <div class='mb-3'>
-			<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10a']}}.png" width='70%' ></a>
-  								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_10a']}}</label>
-  							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_10a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
-                </div>
-                <div>
-			<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10b']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10b']}}.png" width='35%' ></a>
-			<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10c']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10c']}}.png" width='35%' ></a>
-								<label class="labelpadding">Figures 10b,c: Substitution frequencies across the entire amplicon (left) and in the quantification window (right).</label>
-  							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_10b'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
-  							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_10c'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
-                </div>
-							</div>
-						{% else %}
-						  <div class="tab-pane fade show active" id="{{amplicon_name}}_10a" role="tabpanel">
-                <div class='mb-3'>
-			<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10a']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10a']}}.png" width='70%' ></a>
-  								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_10a']}}</label>
-  							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_10a'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
-                </div>
-                <div>
-			<a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10b']}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name]['plot_10b']}}.png" width='35%' ></a>
-  								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name]['plot_10b']}}</label>
-  							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name]['plot_10b'] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                  {% endfor %}
-                </div>
-  						</div>
-						{% endif %}
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10d']%}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_10d" role="tabpanel">
-  								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['10d'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-  							</div>
-  								{% endfor %}
-              </div>
-						{% endif %}
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10e']%}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_10e" role="tabpanel">
-  								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['10e'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                {% endfor %}
-  							</div>
-								{% endfor %}
-              </div>
-						{% endif %}
-
-						{% if report_data['sgRNA_based_fig_names'][amplicon_name] and report_data['sgRNA_based_fig_names'][amplicon_name]['10f']%}
-							  <div class="tab-pane fade" id="{{amplicon_name}}_10f" role="tabpanel">
-  								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['10f'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-    								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-    							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                    {% endfor %}
-                  </div>
-  								{% endfor %}
-
-  								{% for fig_name in report_data['sgRNA_based_fig_names'][amplicon_name]['10g'] %}
-                  <div class='mb-4'>
-			  <a href="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.pdf"><img src="{{report_data['crispresso_data_path']}}{{report_data['fig_locs'][amplicon_name][fig_name]}}.png" width='95%' ></a>
-    								<label class="labelpadding">{{report_data['fig_captions'][amplicon_name][fig_name]}}</label>
-    							  {% for (data_label,data_path) in report_data['fig_datas'][amplicon_name][fig_name] %}
-							  <p class="m-0"><small>Data: <a href="{{report_data['crispresso_data_path']}}{{data_path}}">{{data_label}}</a></small></p>
-                    {% endfor %}
-                    </div>
-  								{% endfor %}
-  							</div>
-              </div>
-						{% endif %}
-
-  							</div>
-  				</div> {# end card #}
-					{% endif %} {# end base editing card #}
-
-				</div> {# end this amplicon tab #} <!--end amp tab -->
-
-					{% endfor %}
-				</div> {# tab content #} <!-- end tab content -->
-
-			</div> <!-- end card -->
-
-	</div> {# jumbotron_content #} <!-- end jumbotron_content -->
-</div> {# jumbrotron #} <!-- end jumbotron -->
-
-</div> {# column #} <!-- end column -->
-
-<div class="col-sm-1"></div>
-{% endblock %}
-
-{% block foot %}
-<script>
-function updateZoom(e) {
-  /*prevent any other actions that may occur when moving over the image:*/
-//  e.preventDefault();
-  var img = e.target.imgObj
-  var view = e.target.viewObj
-  var lens = e.target.lensObj
-
-  if (!lens.hasWidthSet)
-  {
-    view_height = $('#'+view.id).height()
-    view_width = $('#'+view.id).width()
-    img_height = $('#'+img.id).height()
-    img_width = $('#'+img.id).width()
-    lens_height = $('#'+lens.id).height()
-    lens_width = $('#'+lens.id).width()
-
-    new_width = img_height * view_width/view_height  //make up for loss of precision
-    $('#'+lens.id).outerWidth(new_width)
-    lens.hasWidthSet = true
-
-    cx = view_width / new_width
-
-    view.view_height = view_height
-    view.view_width = view_width
-    img.img_height = img_height
-    img.img_width = img_width
-    lens.lens_height = lens_height
-    lens.lens_width = new_width
-
-  }
-
-  var pos, x, y;
-  /*get the cursor's x and y positions:*/
-  pos = getCursorPos(e,img);
-  /*calculate the position of the lens:*/
-  x = pos.x - (lens.lens_width / 2);
-  /*prevent the lens from being positioned outside the image:*/
-  if (x > img.img_width - lens.lens_width) {x = img.img_width - lens.lens_width;;
-  }
-  if (x < 0) {x = 0;}
-  lens.style.left = x + "px";
-  view.style.backgroundPosition = "-" + (x * cx) + "px 0px";
-}
-
-function getCursorPos(e,img) {
-  var a, x = 0, y = 0;
-  e = e || window.event;
-  /*get the x and y positions of the image:*/
-  a = img.getBoundingClientRect();
-  /*calculate the cursor's x and y coordinates, relative to the image:*/
-  x = e.pageX - a.left;
-  y = e.pageY - a.top;
-  /*consider any page scrolling:*/
-  x = x - window.pageXOffset;
-  y = y - window.pageYOffset;
-  return {x : x, y : y};
-}
-
-var passiveSupported = false;
-try {
-  var options = {
-    get passive() { // This function will be called when the browser
-                    //   attempts to access the passive property.
-      passiveSupported = true;
-    }
-  };
-
-  window.addEventListener("test", options, options);
-  window.removeEventListener("test", options, options);
-} catch(err) {
-  passiveSupported = false;
-}
-
-	{% for amplicon_name in report_data.amplicons %}
-		{% if report_data['fig_locs'][amplicon_name]['plot_2a'] %}
-		view = document.getElementById('zoomview_nucs_{{amplicon_name}}');
-		img = document.getElementById('tozoom_nucs_{{amplicon_name}}');
-		lens = document.getElementById('zoomlens_nucs_{{amplicon_name}}')
-
-		img.viewObj = view
-		img.lensObj = lens
-		img.imgObj = img
-
-		lens.viewObj = view
-		lens.lensObj = lens
-		lens.imgObj = img
-
-		lens.addEventListener("mousemove", updateZoom, passiveSupported? { passive: true } : false);
-		img.addEventListener("mousemove", updateZoom, passiveSupported? { passive: true } : false);
-		/*and also for touch screens:*/
-		lens.addEventListener("touchmove", updateZoom, passiveSupported? { passive: true } : false);
-		img.addEventListener("touchmove", updateZoom, passiveSupported? { passive: true } : false);
-
-		{% endif %}
-	{% endfor %}
-	</script>
-{% endblock %}
diff --git a/CRISPRessoAggregate.py b/CRISPRessoAggregate.py
deleted file mode 100644
index 4660026d..00000000
--- a/CRISPRessoAggregate.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2020
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2020 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoAggregateCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoBatch.py b/CRISPRessoBatch.py
deleted file mode 100755
index 2937848e..00000000
--- a/CRISPRessoBatch.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoBatchCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoCompare.py b/CRISPRessoCompare.py
deleted file mode 100755
index 842a081b..00000000
--- a/CRISPRessoCompare.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoCompareCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoMeta.py b/CRISPRessoMeta.py
deleted file mode 100644
index a94c21a2..00000000
--- a/CRISPRessoMeta.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoMetaCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoPooled.py b/CRISPRessoPooled.py
deleted file mode 100755
index b5ec0ab0..00000000
--- a/CRISPRessoPooled.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoPooledCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoPooledWGSCompare.py b/CRISPRessoPooledWGSCompare.py
deleted file mode 100755
index 9b048a96..00000000
--- a/CRISPRessoPooledWGSCompare.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoPooledWGSCompareCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/CRISPRessoWGS.py b/CRISPRessoWGS.py
deleted file mode 100755
index bb65626f..00000000
--- a/CRISPRessoWGS.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-'''
-CRISPResso2 - Kendell Clement and Luca Pinello 2018
-Software pipeline for the analysis of genome editing outcomes from deep sequencing data
-(c) 2018 The General Hospital Corporation. All Rights Reserved.
-'''
-
-
-from CRISPResso2.CRISPRessoWGSCORE import main
-
-
-if __name__ == '__main__':
-    main()
diff --git a/Dockerfile b/Dockerfile
index 3af1d19b..5667580b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,10 +10,11 @@ MAINTAINER Kendell Clement
 RUN apt-get update && apt-get install gcc g++ bowtie2 samtools libsys-hostname-long-perl \
   -y --no-install-recommends \
   && apt-get clean \
+  && apt-get autoremove -y \
   && rm -rf /var/lib/apt/lists/* \
   && rm -rf /usr/share/man/* \
   && rm -rf /usr/share/doc/* \
-  && conda install -c defaults -c conda-forge -c bioconda -y -n base --debug -c bioconda trimmomatic flash numpy cython jinja2 tbb=2020.2 pyparsing=2.3.1 scipy matplotlib pandas plotly\
+  && conda install -c defaults -c conda-forge -c bioconda -y -n base --debug fastp numpy cython jinja2 tbb=2020.2 pyparsing=2.3.1 scipy matplotlib-base pandas plotly\
   && conda clean --all --yes
 
 #install ms fonts
@@ -40,4 +41,4 @@ RUN python setup.py install \
   && CRISPRessoCompare -h
 
 
-ENTRYPOINT ["python","/CRISPResso2/CRISPResso2_router.py"]
+ENTRYPOINT ["python","/CRISPResso2/CRISPResso2_router.py"]
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index 84d707d9..298dcdf0 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -18,8 +18,7 @@ The Software Tool is free for your use subject to the terms and conditions set f
 MGH owns all right, title and interest in the Software Tool. MGH grants to you, the “Licensee,” a royalty-free, non-exclusive, non-transferable, revocable license to use the Software Tool for non-commercial research or academic purposes only; it is NOT made available here as a free tool or download for any commercial or clinical use. You may not copy or distribute the Software Tool in any form. This license is limited to the individual that accesses the Software Tool. No right to sublicense or assign this EULA is granted herein.
 
 The Software Tool optionally makes calls to unmodified versions of
-FLASh (https://ccb.jhu.edu/software/FLASH/) and
-Trimmomatic (http://www.usadellab.org/cms/?page=trimmomatic) software, both of which are covered under their own licenses (GPLv3).
+fastp <https://github.com/OpenGene/fastp> software, which is covered under its own license (MIT).
 
 By using this Software Tool, you agree to allow MGH the right to collect data and statistics (i) on system usage patterns and (ii) to improve this Software Tool.
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 59f8ed4c..07fd7902 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
 include CRISPResso2/EDNAFULL
-include CRISPResso2/templates/*
+include CRISPResso2/args.json
+recursive-include CRISPResso2/CRISPRessoReports/templates *
diff --git a/README.md b/README.md
index f123c56f..486177ac 100644
--- a/README.md
+++ b/README.md
@@ -1,1115 +1,48 @@
-[![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/pinellolab/crispresso2.svg)](https://hub.docker.com/r/pinellolab/crispresso2)
-[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/pinellolab/crispresso2.svg)](https://hub.docker.com/r/pinellolab/crispresso2)
+[![Docker Image Version (tag)](https://img.shields.io/docker/v/pinellolab/crispresso2/latest?logo=docker&label=Docker)](https://hub.docker.com/r/pinellolab/crispresso2/tags)
 [![CircleCI branch](https://img.shields.io/circleci/project/github/pinellolab/CRISPResso2/master.svg)](https://circleci.com/gh/pinellolab/CRISPResso2)
 [![install with bioconda](https://img.shields.io/badge/install%20with-bioconda-brightgreen.svg?style=flat)](http://bioconda.github.io/recipes/crispresso2/README.html)
+[![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://docs.crispresso.com)
 
 # CRISPResso2
+
 CRISPResso2 is a software pipeline designed to enable rapid and intuitive interpretation of genome editing experiments. A limited web implementation is available at: https://crispresso2.pinellolab.org/.
 
 Briefly, CRISPResso2:
+
 - aligns sequencing reads to a reference sequence
 - quantifies insertions, mutations and deletions to determine whether a read is modified or unmodified by genome editing
 - summarizes editing results in intuitive plots and datasets
 
-## What can I do with CRISPResso2?
-CRISPResso2 can be used to analyze genome editing outcomes using cleaving nucleases (e.g. Cas9 or Cpf1) or noncleaving nucleases (e.g. base editors). The following operations can be automatically performed:
-- filtering of low-quality reads
-- adapter trimming
-- alignment of reads to one or multiple reference sequences (in the case of multiple alleles)
-- quantification of HDR and NHEJ outcomes (if the HDR sequence is provided)
-- quantification frameshift/inframe mutations and identification affected splice sites (if an exon sequence is provided)
-- visualization of the indel distribution and position (for cleaving nucleases)
-- visualization of distribution and position of substitutions (for base editors)
-- visualization of alleles and their frequencies
+Access the full documentation at <https://docs.crispresso.com>.
 
 In addition, CRISPResso can be run as part of a larger tool suite:
-- [CRISPRessoBatch](#crispressobatch) - for analyzing and comparing multiple experimental conditions at the same site
-- [CRISPRessoPooled](#crispressopooled) - for analyzing multiple amplicons from a pooled amplicon sequencing experiment
-- [CRISPRessoWGS](#crispressowgs) - for analyzing specific sites in whole-genome sequencing samples
-- [CRISPRessoCompare](#crispressocompare) - for comparing editing between two samples (e.g., treated vs control)
-- [CRISPRessoAggregate](#crispressoaggregate) - for aggregating results from previously-run CRISPResso analyses
-
-## CRISPResso2 processing
-![CRISPResso2 Schematic](https://github.com/pinellolab/CRISPResso2/blob/master/crispresso_schematic.png "CRISPResso2 Schematic")
 
-#### Quality filtering
-Input reads are first filtered based on the quality score (phred33) in order to remove potentially false positive indels. The filtering based on the phred33 quality score can be modulated by adjusting the optimal parameters (see additional notes below).
-#### Adapter trimming
-Next, adapters are trimmed from the reads. If no adapter are present, select 'No Trimming' under the 'Trimming adapter' heading in the optional parameters. If reads contain adapter sequences that need to be trimmed, select the adapters used for trimming under the ‘Trimming adapter’ heading in the optional parameters. Possible adapters include Nextera PE, TruSeq3 PE, TruSeq3 SE, TruSeq2 PE, and TruSeq2 SE. The adapters are trimmed from the reads using Trimmomatic.
-#### Read merging
-If paired-end reads are provided, reads are merged using FLASh . This produces a single read for alignment to the amplicon sequence, and reduces sequencing errors that may be present at the end of sequencing reads.
-#### Alignment
-The preprocessed reads are then aligned to the reference sequence with a global sequence alignment algorithm that takes into account our biological knowledge of nuclease function. If multiple alleles are present at the editing site, each allele can be passed to CRISPResso2 and sequenced reads will be assigned to the reference sequence or origin.
-#### Visualization and analysis
-Finally, after analyzing the aligned reads, a set of informative graphs are generated, allowing for the quantification and visualization of the position and type of outcomes within the amplicon sequence.
+- [CRISPRessoBatch](https://docs.crispresso.com/suite/batch/tool.html) - for analyzing and comparing multiple experimental conditions at the same site
+- [CRISPRessoPooled](https://docs.crispresso.com/suite/pooled/tool.html) - for analyzing multiple amplicons from a pooled amplicon sequencing experiment
+- [CRISPRessoWGS](https://docs.crispresso.com/suite/wgs/tool.html) - for analyzing specific sites in whole-genome sequencing samples
+- [CRISPRessoCompare](https://docs.crispresso.com/suite/compare/tool.html) - for comparing editing between two samples (e.g., treated vs control)
+- [CRISPRessoAggregate](https://docs.crispresso.com/suite/aggregate/tool.html) - for aggregating results from previously-run CRISPResso analyses
 
-## How is CRISPResso2 different from CRISPResso?
-CRISPResso2 introduces four key innovations for the analysis of genome editing data:
-1) Comprehensive analysis of sequencing data from base editors. We have added additional analysis and visualization capabilities especially for experiments using base editors.
-2) Allele specific quantification of heterozygous references. If the targeted editing region has more than one allele, reads arising from each allele can be deconvoluted.
-3) A novel biologically-informed alignment algorithm. This algorithm incorporates knowledge about the mutations produced by gene editing tools to create more biologically-likely alignments.
-4) Ultra-fast processing time.
+![CRISPResso2 Schematic](https://raw.githubusercontent.com/pinellolab/CRISPResso2/master/crispresso_schematic.png "CRISPResso2 Schematic")
 
 ## Installation
-CRISPResso2 can be installed using the [conda](http://conda.pydata.org/docs/intro.html) package manager [Bioconda](https://bioconda.github.io/), or it can be run using the [Docker](https://www.docker.com/) containerization system.
-
-### Bioconda
-To install CRISPResso2 using Bioconda, download and install Anaconda Python, following the instructions at: https://www.anaconda.com/distribution/.
-
-Open a terminal and type:
-
-```
-conda config --add channels defaults
-conda config --add channels bioconda
-conda config --add channels conda-forge
-```
-
-To install CRISPResso2 into the current conda environment, type:
-
-```
-conda install CRISPResso2
-```
-
-Alternatively, to create a new environment named `crispresso2_env` with CRISPResso2, type:
-
-```
-conda create -n crispresso2_env -c bioconda crispresso2
-```
-
-Activate your conda environment:
-
-```
-conda activate crispresso2_env
-```
-
-Verify that CRISPResso is installed using the command:
-
-```
-CRISPResso -h
-```
-
-#### Bioconda for Apple Silicon
-
-If you would like to install CRISPResso using bioconda on a Mac with Apple silicon ([aren't sure?](https://support.apple.com/en-us/HT211814)), then there is a slight change you need to make. First, ensure that you have [Rosetta installed](https://support.apple.com/en-us/HT211861). Next, you must tell bioconda to install the Intel versions of the packages. If you would like to do this system wide, which we recommend, run the command:
-
-``` shell
-conda config --add subdirs osx-64
-```
-
-Then you can proceed with the installation instructions above.
-
-If you would like to use the Intel versions in a single environment, then run:
-
-``` shell
-CONDA_SUBDIR=osx-64 conda create -n crispresso2_env -c bioconda crispresso2
-```
-
-If you choose to use the `CONDA_SUBDIR=osx-64` method, note that if you install additional packages into the environment you will need to add the `CONDA_SUBDIR=osx-64` to the beginning of each command. Alternatively, you could set this environment variable in your shell, but we recommend to use the `conda config --add subdirs osx-64` method because it is less error prone.
-
-### Docker
-CRISPResso2 can be used via the Docker containerization system. This system allows CRISPResso2 to run on your system without configuring and installing additional packages. To run CRISPResso2, first download and install docker: https://docs.docker.com/engine/installation/
-
-Next, Docker must be configured to access your hard drive and to run with sufficient memory. These parameters can be found in the Docker settings menu. To allow Docker to access your hard drive, select 'Shared Drives' and make sure your drive name is selected. To adjust the memory allocation, select the 'Advanced' tab and allocate at least 4G of memory.
-
-To run CRISPResso2, make sure Docker is running, then open a command prompt (Mac) or Powershell (Windows). Change directories to the location where your data is, and run the following command:
-
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso -h
-```
-
-The first time you run this command, it will download the Docker image. The `-v` parameter mounts the current directory to be accessible by CRISPResso2, and the `-w` parameter sets the CRISPResso2 working directory. As long as you are running the command from the directory containing your data, you should not change the Docker `-v` or `-w` parameters.
-
-Additional parameters for CRISPResso2 as described below can be added to this command. For example,
-
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso -r1 sample.fastq.gz -a ATTAACCAAG
-```
-
-## CRISPResso2 usage
-CRISPResso2 is designed be run on a single amplicon. For experiments involving multiple amplicons in the same fastq, see the instructions for [CRISPRessoPooled](#crispressopooled) or [CRISPRessoWGS](#crispressoWGS) below.
-
-CRISPResso2 requires only two parameters: input sequences in the form of fastq files (given by the `--fastq_r1` and `--fastq_r2`) parameters, and the amplicon sequence to align to (given by the `--amplicon_seq` parameter). For example:
-
-*Using Bioconda:*
-```
-CRISPResso --fastq_r1 reads.fastq.gz --amplicon_seq AATGTCCCCCAATGGGAAGTTCATCTGGCACTGCCCACAGGTGAGGAGGTCATGATCCCCTTCTGGAGCTCCCAACGGGCCGTGGTCTGGTTCATCATCTGTAAGAATGGCTTCAAGAGGCTCGGCTGTGGTT
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso --fastq_r1 reads.fastq.gz --amplicon_seq AATGTCCCCCAATGGGAAGTTCATCTGGCACTGCCCACAGGTGAGGAGGTCATGATCCCCTTCTGGAGCTCCCAACGGGCCGTGGTCTGGTTCATCATCTGTAAGAATGGCTTCAAGAGGCTCGGCTGTGGTT
-```
-
-### Example run: Non-homologous end joining (NHEJ)
-Download the test datasets [nhej.r1.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/nhej.r1.fastq.gz) and [nhej.r2.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/nhej.r2.fastq.gz) to your current directory. This is the first 25,000 sequences from a paired-end sequencing experiment. To analyze this experiment, run the command:
-
-*Using Bioconda:*
-```
-CRISPResso --fastq_r1 nhej.r1.fastq.gz --fastq_r2 nhej.r2.fastq.gz --amplicon_seq AATGTCCCCCAATGGGAAGTTCATCTGGCACTGCCCACAGGTGAGGAGGTCATGATCCCCTTCTGGAGCTCCCAACGGGCCGTGGTCTGGTTCATCATCTGTAAGAATGGCTTCAAGAGGCTCGGCTGTGGTT -n nhej
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso --fastq_r1 nhej.r1.fastq.gz --fastq_r2 nhej.r2.fastq.gz --amplicon_seq AATGTCCCCCAATGGGAAGTTCATCTGGCACTGCCCACAGGTGAGGAGGTCATGATCCCCTTCTGGAGCTCCCAACGGGCCGTGGTCTGGTTCATCATCTGTAAGAATGGCTTCAAGAGGCTCGGCTGTGGTT -n nhej
-```
-
-This should produce a folder called 'CRISPResso_on_nhej'. Open the file called CRISPResso_on_nhej/CRISPResso2_report.html in a web browser, and you should see an output like this: [CRISPResso2_report.html](https://crispresso.pinellolab.partners.org/static/demo/CRISPResso_on_nhej/CRISPResso2_report.html).
-
-### Example run: Multiple alleles
-Download the test dataset [allele_specific.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/allele_specific.fastq.gz) to your current directory. This is the first 25,000 sequences from a editing experiment targeting one allele. To analyze this experiment, run the following command:
-
-*Using Bioconda:*
-```
-CRISPResso --fastq_r1 allele_specific.fastq.gz --amplicon_seq CGAGAGCCGCAGCCATGAACGGCACAGAGGGCCCCAATTTTTATGTGCCCTTCTCCAACGTCACAGGCGTGGTGCGGAGCCACTTCGAGCAGCCGCAGTACTACCTGGCGGAACCATGGCAGTTCTCCATGCTGGCAGCGTACATGTTCCTGCTCATCGTGCTGGG,CGAGAGCCGCAGCCATGAACGGCACAGAGGGCCCCAATTTTTATGTGCCCTTCTCCAACGTCACAGGCGTGGTGCGGAGCCCCTTCGAGCAGCCGCAGTACTACCTGGCGGAACCATGGCAGTTCTCCATGCTGGCAGCGTACATGTTCCTGCTCATCGTGCTGGG --amplicon_name P23H,WT --guide_seq GTGCGGAGCCACTTCGAGCAGC
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso --fastq_r1 allele_specific.fastq.gz --amplicon_seq CGAGAGCCGCAGCCATGAACGGCACAGAGGGCCCCAATTTTTATGTGCCCTTCTCCAACGTCACAGGCGTGGTGCGGAGCCACTTCGAGCAGCCGCAGTACTACCTGGCGGAACCATGGCAGTTCTCCATGCTGGCAGCGTACATGTTCCTGCTCATCGTGCTGGG,CGAGAGCCGCAGCCATGAACGGCACAGAGGGCCCCAATTTTTATGTGCCCTTCTCCAACGTCACAGGCGTGGTGCGGAGCCCCTTCGAGCAGCCGCAGTACTACCTGGCGGAACCATGGCAGTTCTCCATGCTGGCAGCGTACATGTTCCTGCTCATCGTGCTGGG --amplicon_name P23H,WT --guide_seq GTGCGGAGCCACTTCGAGCAGC
-```
-
-This should produce a folder called 'CRISPResso_on_allele_specific'. Open the file called CRISPResso_on_allele_specific/CRISPResso2_report.html in a web browser, and you should see an output like this: [CRISPResso2_report.html](https://crispresso.pinellolab.partners.org/static/demo/CRISPResso_on_allele_specific/CRISPResso2_report.html).
-
-### Example run: Base editing experiment
-Download the test dataset [base_editor.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/base_editor.fastq.gz) to your current directory. This is the first 25,000 sequences from an editing experiment performed at the EMX1 locus. To analyze this experiment, run the following command:
-
-*Using Bioconda:*
-```
-CRISPResso --fastq_r1 base_editor.fastq.gz --amplicon_seq GGCCCCAGTGGCTGCTCTGGGGGCCTCCTGAGTTTCTCATCTGTGCCCCTCCCTCCCTGGCCCAGGTGAAGGTGTGGTTCCAGAACCGGAGGACAAAGTACAAACGGCAGAAGCTGGAGGAGGAAGGGCCTGAGTCCGAGCAGAAGAAGAAGGGCTCCCATCACATCAACCGGTGGCGCATTGCCACGAAGCAGGCCAATGGGGAGGACATCGATGTCACCTCCAATGACTAGGGTGG --guide_seq GAGTCCGAGCAGAAGAAGAA --quantification_window_size 10 --quantification_window_center -10 --base_editor_output
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPResso --fastq_r1 base_editor.fastq.gz --amplicon_seq GGCCCCAGTGGCTGCTCTGGGGGCCTCCTGAGTTTCTCATCTGTGCCCCTCCCTCCCTGGCCCAGGTGAAGGTGTGGTTCCAGAACCGGAGGACAAAGTACAAACGGCAGAAGCTGGAGGAGGAAGGGCCTGAGTCCGAGCAGAAGAAGAAGGGCTCCCATCACATCAACCGGTGGCGCATTGCCACGAAGCAGGCCAATGGGGAGGACATCGATGTCACCTCCAATGACTAGGGTGG --guide_seq GAGTCCGAGCAGAAGAAGAA --quantification_window_size 10 --quantification_window_center -10 --base_editor_output
-```
-
-This should produce a folder called 'CRISPResso_on_base_editor'. Open the file called CRISPResso_on_base_editor/CRISPResso2_report.html in a web browser, and you should see an output like this: [CRISPResso2_report.html](https://crispresso.pinellolab.partners.org/static/demo/CRISPResso_on_base_editor/CRISPResso2_report.html).
-
-### Parameter List
--h or --help: show a help message and exit.
-
--r1 or --fastq_r1: The first fastq file.
-
--r2 or --fastq_r2 FASTQ_R2: The second fastq file for paired end reads.
-
--a or --amplicon_seq: The amplicon sequence used for the experiment.
-
--an or --amplicon_name: A name for the reference amplicon can be given. If multiple amplicons are given, multiple names can be specified here. Because amplicon names are used as output filename prefixes, amplicon names are truncated to 21bp unless the parameter `--suppress_amplicon_name_truncation` is set. (default: Reference)
-
--g or --guide_seq: sgRNA sequence, if more than one, please separate by commas. Note that the sgRNA needs to be input as the guide RNA sequence (usually 20 nt) immediately adjacent to but not including the PAM sequence (5' of NGG for SpCas9). If the PAM is found on the opposite strand with respect to the Amplicon Sequence, ensure the sgRNA sequence is also found on the opposite strand. The CRISPResso convention is to depict the expected cleavage position using the value of the parameter '--quantification_window_center' nucleotides from the 3' end of the guide. In addition, the use of alternate nucleases besides SpCas9 is supported. For example, if using the Cpf1 system, enter the sequence (usually 20 nt) immediately 3' of the PAM sequence and explicitly set the '--cleavage_offset' parameter to 1, since the default setting of -3 is suitable only for SpCas9. (default: )
-
--e or --expected_hdr_amplicon_seq: Amplicon sequence expected after HDR. The expected HDR amplicon sequence can be provided to quantify the number of reads showing a successful HDR repair. Note that the entire amplicon sequence must be provided, not just the donor template. CRISPResso2 will quantify identified instances of NHEJ, HDR, or mixed editing events. (default: )
-
--c or --coding_seq: Subsequence/s of the amplicon sequence covering one or more coding sequences for frameshift analysis. Sequences of exons within the amplicon sequence can be provided to enable frameshift analysis and splice site analysis by CRISPResso2. If more than one (for example, split by intron/s), please separate by commas. Users should provide the subsequences of the reference amplicon sequence that correspond to coding sequences (not the whole exon sequence(s)!). (default: )
-
-#### sgRNA parameters
-
--gn or --guide_name: sgRNA names, if more than one, please separate by commas. (default: sgRNA)
-
--fg or --flexiguide: sgRNA sequence (flexible). The flexiguide sequence will be aligned to the amplicon sequence(s), as long as the guide sequence has homology as set by --flexiguide_homology. (default: '')
-
--fh or --flexiguide_homology: flexiguides will yield guides in amplicons with at least this homology to the flexiguide sequence (default:80 meaning 80% homology is required)
-
--fgn or --flexiguide_name: Names for the flexiguides, similar to --guide_name. (default: '')
-
---discard_guide_positions_overhanging_amplicon_edge: If set, for guides that align to multiple positions, guide positions will be discarded if plotting around those regions would included bp that extend beyond the end of the amplicon. (default: False)
-
-#### Read filtering, trimming, and merging parameters
-
---split_interleaved_input: Splits a single fastq file containing paired end reads in two files before running CRISPResso (default: False)
-
--q or --min_average_read_quality: Minimum average quality score (phred33) to keep a read (default: 0)
-
--s or --min_single_bp_quality: Minimum single bp score (phred33) to keep a read (default: 0)
-
---min_bp_quality_or_N: Bases with a quality score (phred33) less than this value will be set to "N" (default: 0)
-
---trim_sequences: Enable the trimming of Illumina adapters with [Trimmomatic](http://www.usadellab.org/cms/?page=trimmomatic) (default: False)
-
---trimmomatic_command: Command to run [Trimmomatic](http://www.usadellab.org/cms/?page=trimmomatic). Alternate executables for Trimmomatic should be specified here. The default uses the conda-installed trimmomatic. (default: trimmomatic)
-
---trimmomatic_options_string: Override options for Trimmomatic (default: ). This parameter can be used to specify different adaptor sequences used in the experiment if you need to trim them. For example: ```ILLUMINACLIP:NexteraPE-PE.fa:0:90:10:0:true```, where NexteraPE-PE.fa is a file containing sequences of adapters to be trimmed.
-
---min_paired_end_reads_overlap: Parameter for the FLASH read merging step. Minimum required overlap length between two reads to provide a confident overlap. (default: 10)
-
---max_paired_end_reads_overlap: Parameter for the FLASH merging step. Maximum overlap length expected in approximately 90% of read pairs.  Please see the FLASH manual for more information.  (default: 100)
-
---stringent_flash_merging: Use stringent parameters for flash merging. In the case where flash could merge R1 and R2 reads ambiguously, the expected overlap is calculated as 2\*average_read_length - amplicon_length. The flash parameters for --min-overlap and --max-overlap will be set to prefer merged reads with length within 10bp of the expected overlap. These values override the --min_paired_end_reads_overlap or --max_paired_end_reads_overlap CRISPResso parameters. (default: False)
-
-#### Quantification window parameters
-
--w or --quantification_window_size or --window_around_sgrna: Defines the size (in bp) of the quantification window extending from the position specified by the "--cleavage_offset" or "--quantification_window_center" parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp. (default: 1)
-
--wc or --quantification_window_center or --cleavage_offset: Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17. (default: -3)
-
--qwc or --quantification_window_coordinates: Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the "--quantification_window_center", "-- cleavage_offset", "--window_around_sgrna" or "-- window_around_sgrna" values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separated by the dash sign like "start-stop", and multiple ranges can be separated by the underscore (\_). A value of 0 disables this filter. (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 5th-10th bp in the first reference and the 5th-10th and 20th-30th bp in the second reference) (default: None)
-
---exclude_bp_from_left: Exclude bp from the left side of the amplicon sequence for the quantification of the indels (default: 15)
-
---exclude_bp_from_right: Exclude bp from the right side of the amplicon sequence for the quantification of the indels (default: 15)
-
---ignore_substitutions: Ignore substitutions events for the quantification and visualization (default: False)
-
---ignore_insertions: Ignore insertions events for the quantification and visualization (default: False)
-
---ignore_deletions: Ignore deletions events for the quantification and visualization (default: False)
-
---discard_indel_reads: Discard reads with indels in the quantification window from analysis (default: False)
-
-#### Read alignment parameters
-
--amas or --amplicon_min_alignment_score: Amplicon Minimum Alignment Score; score between 0 and 100; sequences must have at least this homology score with the amplicon to be aligned (can be comma-separated list of multiple scores, corresponding to amplicon sequences given in --amplicon_seq) After reads are aligned to a reference sequence, the homology is calculated as the number of bp they have in common. If the aligned read has a homology less than this parameter, it is discarded. This is useful for filtering erroneous reads that do not align to the target amplicon, for example arising from alternate primer locations. (default: 60)
-
---default_min_aln_score or --min_identity_score: Default minimum homology score for a read to align to a reference amplicon (default: 60)
-
---expand_ambiguous_alignments: If more than one reference amplicon is given, reads that align to multiple reference amplicons will count equally toward each amplicon. Default behavior is to exclude ambiguous alignments. (default: False)
-
---needleman_wunsch_gap_open: Gap open option for Needleman-Wunsch alignment (default: -20)
-
---needleman_wunsch_gap_extend: Gap extend option for Needleman-Wunsch alignment (default: -2)
-
---needleman_wunsch_gap_incentive: Gap incentive value for inserting indels at cut sites (default: 1)
-
---needleman_wunsch_aln_matrix_loc: Location of the matrix specifying substitution scores in the NCBI format (see ftp://ftp.ncbi.nih.gov/blast/matrices/) (default: EDNAFULL)
-
-#### Base editing parameters
---base_editor_output: Outputs plots and tables to aid in analysis of base editor studies. If base editor output is selected, plots showing the frequency of substitutions in the quantification window are generated. The target and result bases can also be set to measure the rate of on-target conversion at bases in the quantification window. (default: False)
-
---conversion_nuc_from: For base editor plots, this is the nucleotide targeted by the base editor (default: C)
-
---conversion_nuc_to: For base editor plots, this is the nucleotide produced by the base editor (default: T)
-
-#### Prime editing parameters
-
---prime_editing_pegRNA_spacer_seq: pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the given sequence. (default: )
-
---prime_editing_pegRNA_extension_seq: Extension sequence used in prime editing. The sequence should be given in the RNA 5'->3' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS). (default: )
 
---prime_editing_pegRNA_extension_quantification_window_size: Quantification window size (in bp) at flap site for measuring modifications anchored at the right side of the extension sequence. Similar to the --quantification_window parameter, the total length of  the quantification window will be 2x this parameter. Default: 5bp (10bp total window size) (default: 5)
+CRISPResso2 can be [installed](https://docs.crispresso.com/installation.html) in the following ways:
 
---prime_editing_pegRNA_scaffold_seq: If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that the RT template directly follows this sequence. A common value ends with 'GGCACCGAGUCGGUGC'. (default: )
+- [Bioconda](https://docs.crispresso.com/installation.html#bioconda)
+  - [Bioconda on Apple Silicon](https://docs.crispresso.com/installation.html#bioconda-for-apple-silicon)
+- [Docker](https://docs.crispresso.com/installation.html#docker)
 
---prime_editing_pegRNA_scaffold_min_match_length: Minimum number of bases matching scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences. (default: 1)
+## Examples
 
---prime_editing_nicking_guide_seq: Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence (default: )
-
---prime_editing_override_prime_edited_ref_seq: If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence. (default='')
-
-#### Plotting parameters
-
---plot_histogram_outliers: If set, all values will be shown on histograms. By default (if unset), histogram ranges are limited to plotting data within the 99 percentile. (default: False)
-
-#### Allele plot parameters
-
---plot_window_size or --offset_around_cut_to_plot: Defines the size of the window extending from the quantification window center to plot. Nucleotides within plot_window_size of the quantification_window_center for each guide are plotted. (default: 20)
-
---min_frequency_alleles_around_cut_to_plot: Minimum % reads required to report an allele in the alleles table plot. This parameter only affects plotting. All alleles will be reported in data files. (default: 0.2 (i.e. 0.2\%))
-
---max_rows_alleles_around_cut_to_plot: Maximum number of rows to report in the alleles table plot. (default: 50)
-
---expand_allele_plots_by_quantification: If set, alleles with different modifications in the quantification window (but not necessarily in the plotting window (e.g. for another sgRNA)) are plotted on separate lines, even though they may have the same apparent sequence. To force the allele plot and the allele table to be the same, set this parameter. If unset, all alleles with the same sequence will be collapsed into one row. (default: False)
-
---allele_plot_pcts_only_for_assigned_reference: If set, in the allele plots, the percentages will show the percentage as a percent of reads aligned to the assigned reference. Default behavior is to show percentage as a percent of all reads. (default: False)
-
---annotate_wildtype_allele: Wildtype alleles in the allele table plots will be marked with this string (e.g. \*\*). (default: )
-
-#### Output parameters
-
---file_prefix: File prefix for output plots and tables (default: )
-
--n or --name: Output name of the report (default: the names is obtained from the filename of the fastq file/s used in input) (default: )
-
--o or --output_folder: Output folder to use for the analysis (default: current folder)
-
---write_detailed_allele_table: If set, a detailed allele table will be written including alignment scores for each read sequence. (default: False)
-
---suppress_amplicon_name_truncation: If set, amplicon names will not be truncated when creating output filename prefixes. If not set, amplicon names longer than 21 characters will be truncated when creating filename prefixes. (default: False)
-
---fastq_output: If set, a fastq file with annotations for each read will be produced. (default: False)
-
---bam_output': If set, a bam file with alignments for each read will be produced. Setting this parameter will produce a file called 'CRISPResso_output.bam' with the alignments in bam format. If the `bowtie2_index` is provided, alignments will be reported in reference to that genome. If the `bowtie2_index` is not provided, alignments will be reported in reference to a custom reference created by the amplicon sequence(s) and written to the file 'CRISPResso_output.fa'. (default: False)
-
--x or --bowtie2_index: Basename of Bowtie2 index for the reference genome. Optionally used in the creation of a bam file. See `bam_output`. (default: '')
-
---keep_intermediate: Keep all the intermediate files (default: False)
-
---dump: Dump numpy arrays and pandas dataframes to file for debugging purposes (default: False)
-
---crispresso1_mode: Output as in CRISPResso1. In particular, if this flag is set, the old output files 'Mapping_statistics.txt', and 'Quantification_of_editing_frequency.txt' are created, and the new files 'nucleotide_frequency_table.txt' and 'substitution_frequency_table.txt' and figure 2a and 2b are suppressed, and the files 'selected_nucleotide_percentage_table.txt' are not produced when the flag `--base_editor_output` is set (default: False)
-
---suppress_report: Suppress output report, plots output as .pdf only (not .png) (default: False)
-
---suppress_plots: Suppress output plots (default: False)
-
---place_report_in_output_folder: If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output. (default: False)
-
---zip_output: If true, the output folder will be zipped upon completion. If --zip_output is true --place_report_in_output_folder should be true otherwise --place_report_in_output_folder is automatically set to true as well. (default: False)
-
-#### Miscellaneous parameters
-
---auto: Infer amplicon sequence from most common reads (default: False)
-
---dsODN: dsODN sequence -- Reads containing the dsODN are labeled and quantified. (default: '')
-
---debug: Show debug messages (default: False)
-
--v or --verbosity: Verbosity level of output to the console (1-4), 4 is the most verbose. If parameter `--debug` is set `--verbosity` is overridden and set to 4. (default=3)
-
---no_rerun: Don't rerun CRISPResso2 if a run using the same parameters has already been finished. (default: False)
-
---bam_input BAM_INPUT: Aligned reads for processing in bam format. This parameter can be given instead of fastq_r1 to specify that reads are to be taken from this bam file. An output bam is produced that contains an additional field with CRISPResso2 information. (default: )
-
---bam_chr_loc BAM_CHR_LOC: Chromosome location in bam for reads to process. For example: "chr1:50-100" or "chrX". (default: )
-
-## CRISPResso2 output
-The output of CRISPResso2 consists of a set of informative graphs that allow for the quantification and visualization of the position and type of outcomes within an amplicon sequence.
-
-### Data file descriptions
-*CRISPResso2_report.html* is a summary report that can be viewed in a web browser containing all of the output plots and summary statistics.
-
-*Alleles_frequency_table.zip* can be unzipped to a tab-separated text file that shows all reads and alignments to references. The first column shows the aligned sequence of the sequenced read. The second column shows the aligned sequence of the reference sequence. Gaps in each of these columns represent insertions and deletions. The next column 'Reference_Name' shows the name of the reference that the read aligned to. The fourth column, 'Read_Status' shows whether the read was modified or unmodified. The fifth through seventh columns ('n_deleted', 'n_inserted', 'n_substituted') show the number of bases deleted, inserted, and substituted as compared to the reference sequence. The eighth column shows the number of reads having that sequence, and the ninth column shows the percentage of all reads having that sequence.
-
-*CRISPResso_mapping_statistics.txt* is a tab-delimited text file showing the number of reads in the input ('READS IN INPUTS') the number of reads after filtering, trimming and merging (READS AFTER PREPROCESSING), the number of reads aligned (READS ALIGNED) and the number of reads for which the alignment had to be computed vs read from cache.
-
-*CRISPResso_quantification_of_editing_frequency.txt* is a tab-delimited text file showing the number of reads aligning to each reference amplicon, as well as the status (modified/unmodified, number of insertions, deletions, and/or substitutions) of those reads.
-
-*CRISPResso_RUNNING_LOG.txt* is a text file and shows a log of the CRISPResso run.
-
-*CRISPResso2_info.json* can be read by other CRISPResso tools and contains information about the run and results.
-
-The remainder of the files are produced for each amplicon, and each file is prefixed by the name of the amplicon if more than one amplicon is given.
-
-*Alleles_frequency_table_around_sgRNA_NNNNN.txt* is a tab-separated text file that shows alleles and alignments to the specified reference for a subsequence around the sgRNA (here, shown by 'NNNNN'). This data report is produced for each amplicon when a guide is found in the amplicon sequence. A report is generated for each guide. The number of nucleotides shown in this report can be modified by changing the `--plot_window_size` parameter.
-
-*Substitution_frequency_table_around_sgRNA_NNNNN.txt* is a tab-separated text file that shows the frequency of substitutions in the amplicon sequence around the sgRNA (here, shown by 'NNNNN'). The first row shows the reference sequence. The following rows show the number of substitutions to each base. For example, the first numeric value in the second row (marked ‘A’) shows the number of bases that have a substitution resulting in an A at the first basepair of the amplicon sequence. The number of unmodified bases at each position is now shown in this table (because they aren’t substitutions). Thus, if the first basepair of the amplicon sequence is an A, the first value in the first row will show 0. A report is generated for each guide. The number of nucleotides shown in this report can be modified by changing the `--plot_window_size` parameter.
-
-*Substitution_frequency_table.txt* is a tab-separated text file that shows the frequency of substitutions in the amplicon sequence across the entire amplicon. The first row shows the reference sequence. The following rows show the number of substitutions to each base. For example, the first numeric value in the second row (marked ‘A’) shows the number of bases that have a substitution resulting in an A at the first basepair of the amplicon sequence. The number of unmodified bases at each position is now shown in this table (because they aren’t substitutions). Thus, if the first basepair of the AMPLICON sequence is an A, the first value in the first row will show 0.
-
-*Insertion_histogram.txt* is a tab-separated text file that shows a histogram of the insertion sizes in the amplicon sequence in the quantification window. Insertions outside of the quantification window are not included. The ins_size column shows the insertion length, and the fq column shows the number of reads having that insertion size.
-
-*Deletion_histogram.txt* is a tab-separated text file that shows a histogram of the deletion sizes in the amplicon sequence in the quantification window. Deletions outside of the quantification window are not included. The del_size column shows length of the deletion, and the fq column shows the number of reads having that number of substitutions.
-
-*Substitution_histogram.txt* is a tab-separated text file that shows a histogram of the number of substitutions in the amplicon sequence in the quantification window. Substitutions outside of the quantification window are not included. The sub_count column shows the number of substitutions, and the fq column shows the number of reads having that number of substitutions.
-
-*Effect_vector_insertion.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with an insertion at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a insertion at that location.
-
-*Effect_vector_deletion.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with a deletion at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a deletion at that location.
-
-*Effect_vector_substitution.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with a substitution at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a substitution at that location.
-
-*Effect_vector_combined.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with any modification (insertion, deletion, or substitution) at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a modification at that location.
-
-*Modification_count_vectors.txt* is a tab-separated file showing the number of modifications for each position in the amplicon. The first row shows the amplicon sequence, and successive rows show the number of reads with insertions (row 2), insertions_left (row 3), deletions (row 4), substitutions (row 5) and the sum of all modifications (row 6). Additionally, the last row shows the number of reads aligned.
-
-If an insertion occurs between bases 5 and 6, the insertions vector will be incremented at bases 5 and 6. However, the insertions_left vector will only be incremented at base 5 so the sum of the insertions_left row represents an accurate count of the number of insertions, whereas the sum of the insertions row will yield twice the number of insertions.
-
-*Quantification_window_modification_count_vectors.txt* is a tab-separated file showing the number of modifications for positions in the quantification window of the amplicon. The first row shows the amplicon sequence in the quantification window, and successive rows show the number of reads with insertions (row 2), insertions_left (row 3), deletions (row 4), substitutions (row 5) and the sum of all modifications (row 6). Additionally, the last row shows the number of reads aligned.
-
-*Nucleotide_frequency_table.txt* is a tab-separated file showing the number of each residue at each position in the amplicon. The first row shows the amplicon sequence, and successive rows show the number of reads with an A (row 2), C (row 3), G (row 4), T (row 5), N (row 6), or a deletion (-) (row 7) at each position.
-
-*Quantification_window_nucleotide_frequency_table.txt* is a tab-separated file showing the number of each residue at positions in the quantification window of the amplicon. The first row shows the amplicon sequence in the quantification window, and successive rows show the number of reads with an A (row 2), C (row 3), G (row 4), T (row 5), N (row 6), or a deletion (-) (row 7) at each position.
-
-*Nucleotide_percentage_table.txt* is a tab-separated file showing the percentage of each residue at each position in the amplicon. The first row shows the amplicon sequence, and successive rows show the percentage of reads with an A (row 2), C (row 3), G (row 4), T (row 5), N (row 6), or a deletion (-) (row 7) at each position.
-
-*Quantification_window_nucleotide_percentage_table.txt* is a tab-separated file showing the percentage of each residue at positions in the quantification window of the amplicon. The first row shows the amplicon sequence in the quantification window, and successive rows show the percentage of reads with an A (row 2), C (row 3), G (row 4), T (row 5), N (row 6), or a deletion (-) (row 7) at each position.
-
-The following report files are produced when the base editor mode is enabled:
-
-*Selected_nucleotide_percentage_table_around_sgRNA_NNNNN.txt* is a tab-separated text file that shows the percentage of each base at selected nucleotides in the amplicon sequence around the sgRNA (here, shown by 'NNNNN'). If the base editing experiment targets cytosines (as set by the --base_editor_from parameter), each C in the quantification window will be numbered (e.g. C5 represents the cytosine at the 5th position in the selected nucleotides). The percentage of each base at these selected target cytosines is reported, with the first row showing the numbered cytosines, and the remainder of the rows showing the percentage of each nucleotide present at these locations. This file shows nucleotides within '--plot_window_size' bp of the position specified by the parameter '--quantification_window_center' relative to the 3' end of each guide.
-
-*Selected_nucleotide_frequency_table_around_sgRNA_NNNNN.txt* is a tab-separated text file that shows the frequency of each base at selected nucleotides in the amplicon sequence around the sgRNA (here, shown by 'NNNNN'). If the base editing experiment targets cytosines (as set by the --base_editor_from parameter), each C in the quantification window will be numbered (e.g. C5 represents the cytosine at the 5th position in the selected nucleotides). The frequency of each base at these selected target cytosines is reported, with the first row showing the numbered cytosines, and the remainder of the rows showing the frequency of each nucleotide present at these locations. This file shows nucleotides within '--plot_window_size' bp of the position specified by the parameter '--quantification_window_center' relative to the 3' end of each guide.
-
-The following report files are produced when the amplicon contains a coding sequence:
-
-*Frameshift_analysis.txt* is a text file describing the number of noncoding, in-frame, and frameshift mutations. This report file is produced when the amplicon contains a coding sequence.
-
-*Splice_sites_analysis.txt* is a text file describing the number of splicing sites that are unmodified and modified. This file report is produced when the amplicon contains a coding sequence.
-
-*Effect_vector_insertion_noncoding.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with a noncoding insertion at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a noncoding insertion at that location. This report file is produced when amplicon contains a coding sequence.
-
-*Effect_vector_deletion_noncoding.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with a noncoding deletion at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a noncoding deletion at that location. This report file is produced when amplicon contains a coding sequence.
-
-*Effect_vector_substitution_noncoding.txt* is a tab-separated text file with a one-row header that shows the percentage of reads with a noncoding substitution at each base in the reference sequence. The first column shows the 1-based position of the amplicon, and the second column shows the percentage of reads with a noncoding substitution at that location. This report file is produced when amplicon contains a coding sequence.
+- [CRISPResso example runs](https://docs.crispresso.com/suite/core/examples.html)
+- [CRISPRessoBatch example runs](https://docs.crispresso.com/suite/batch/examples.html)
+- [CRISPRessoPooled example runs](https://docs.crispresso.com/suite/pooled/examples.html)
+- [CRISPRessoWGS example runs](https://docs.crispresso.com/suite/wgs/examples.html)
+- [CRISPRessoCompare example runs](https://docs.crispresso.com/suite/compare/examples.html)
+- [CRISPRessoPooledWGCompare example runs](https://docs.crispresso.com/suite/pooledwgscompare/examples.html)
+- [CRISPRessoAggregate example runs](https://docs.crispresso.com/suite/aggregate/examples.html)
 
 ## Troubleshooting
-Please check that your input file(s) are in FASTQ format (compressed fastq.gz also accepted).
-
-If you get an empty report, please double check that your amplicon sequence is correct and in the correct orientation. It can be helpful to inspect the first few lines of your FASTQ file - the start of the amplicon sequence should match the start of your sequences. If not, check to see if the files are trimmed (see point below).
-
-It is important to determine whether your reads are trimmed or not. CRISPResso2 assumes that the reads ARE ALREADY TRIMMED! If reads are not already trimmed, select the adapters used for trimming under the ‘Trimming Adapter’ heading under the ‘Optional Parameters’. This is FUNDAMENTAL to CRISPResso analysis. Failure to trim adaptors may result in false positives. This will result in a report where you will observe an unrealistic 100% modified alleles and a sharp peak at the edges of the reference amplicon in figure 4.
-
-The quality filter assumes that your reads uses the Phred33 scale, and it should be adjusted for each user’s specific application. A reasonable value for this parameter is 30.
-
-If your amplicon sequence is longer than your sequenced read length, the R1 and R2 reads should overlap by at least 10bp. For example, if you sequence using 150bp reads, the maximum amplicon length should be 290 bp.
-
-Especially in repetitive regions, multiple alignments may have the best score. If you want to investigate alternate best-scoring alignments, you can view all alignments using this tool: http://rna.informatik.uni-freiburg.de/Teaching/index.jsp?toolName=Gotoh. As input, sequences from the 'Alleles_frequency_table.txt' can be used. Specifically, for a given row, the value in the 'Aligned_Sequence' should be entered into the 'Sequence a' box after removing any dashes, and the value in the 'Reference_Sequence' should be entered into the 'Sequence b' box after removing any dashes. The alternate alignments can be selected in the 'Results' panel in the Output section.
-
-## Alternate running modes
-CRISPResso2 can be run for many fastqs ([CRISPRessoBatch](#crispressobatch)), for many amplicons in the same fastq ([CRISPRessoPooled](#crispressopooled)), or for whole-genome sequencing ([CRISPRessoWGS](#crispressowgs)).
-
-### CRISPRessoBatch
-CRISPRessoBatch allows users to specify input files and other command line arguments in a single file, and then to run CRISPResso2 analysis on each file in parallel. Samples for which the amplicon and guide sequences are the same will be compared between batches, producing useful summary tables and coomparison plots.
-
-This flexible utility adds four additional parameters:
-
---batch_settings: This parameter specifies the tab-separated batch file. The batch file consists of a header line listing the parameters specified, and then one line for each sample describing the parameters for that sample. Each of the parameters for CRISPResso2 given above can be specified for each sample. When CRISPRessoBatch is run, additional parameters can be specified that will be applied to all of the samples listed in the batch file. An example batch file looks like:
-```
-name	fastq_r1
-sample1	sample1.fq
-sample2	sample2.fq
-sample3	sample3.fq
-```
-
---skip_failed: If any sample fails, CRISPRessoBatch will exit without completion. However, if this parameter is specified, CRISPressoBatch will continue and only summarize the statistics of the successfully-completed runs.
-
--p or --n_processes: This specifies the number of processes to use for quantification. (default: 1)
-
--bo or --batch_output_folder: Directory where batch analysis output will be stored.
-
-CRISPRessoBatch outputs several summary files and plots:
-
-*CRISPRessoBatch_quantification_of_editing_frequency* shows the number of reads that were modified for each amplicon in each sample.
-
-*CRISPRessoBatch_mapping_statistics.txt* aggregates the read mapping data from each sample.
-
-For each amplicon, the following files are produced with the name of the amplicon as the filename prefix:
-
-*NUCLEOTIDE_FREQUENCY_SUMMARY.txt* and *NUCLEOTIDE_PERCENTAGE_SUMMARY.txt* aggregate the nucleotide counts and percentages at each position in the amplicon for each sample.
-
-
-*MODIFICATION_FREQUENCY_SUMMARY.txt* and *MODIFICATION_PERCENTAGE_SUMMARY.txt* aggregate the modification frequency and percentage at each position in the amplicon for each sample.
-
-#### Example run: Batch mode
-Download the test dataset files [SRR3305543.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/SRR3305543.fastq.gz), [SRR3305544.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/SRR3305544.fastq.gz), [SRR3305545.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/SRR3305545.fastq.gz), and [SRR3305546.fastq.gz](https://crispresso.pinellolab.partners.org/static/demo/SRR3305546.fastq.gz) to your current directory. These are files are the first 25,000 sequences from an editing experiment performed on several base editors. Also include a batch file that lists these files and the sample names: [batch.batch](https://crispresso.pinellolab.partners.org/static/demo/batch.batch) To analyze this experiment, run the following command:
-
-*Using Bioconda:*
-```
-CRISPRessoBatch --batch_settings batch.batch --amplicon_seq CATTGCAGAGAGGCGTATCATTTCGCGGATGTTCCAATCAGTACGCAGAGAGTCGCCGTCTCCAAGGTGAAAGCGGAAGTAGGGCCTTCGCGCACCTCATGGAATCCCTTCTGCAGCACCTGGATCGCTTTTCCGAGCTTCTGGCGGTCTCAAGCACTACCTACGTCAGCACCTGGGACCCC -p 4 --base_editor_output -g GGAATCCCTTCTGCAGCACC -wc -10 -w 20
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoBatch --batch_settings batch.batch --amplicon_seq CATTGCAGAGAGGCGTATCATTTCGCGGATGTTCCAATCAGTACGCAGAGAGTCGCCGTCTCCAAGGTGAAAGCGGAAGTAGGGCCTTCGCGCACCTCATGGAATCCCTTCTGCAGCACCTGGATCGCTTTTCCGAGCTTCTGGCGGTCTCAAGCACTACCTACGTCAGCACCTGGGACCCC -p 4 --base_editor_output -g GGAATCCCTTCTGCAGCACC -wc -10 -w 20
-```
-
-This should produce a folder called 'CRISPRessoBatch_on_batch'. Open the file called CRISPRessoBatch_on_batch/CRISPResso2Batch_report.html in a web browser, and you should see an output like this: [CRISPResso2Batch_report.html](https://crispresso.pinellolab.partners.org/static/demo/CRISPRessoBatch_on_batch/CRISPResso2Batch_report.html).
-
-### CRISPRessoPooled
-CRISPRessoPooled is a utility to analyze and quantify targeted sequencing CRISPR/Cas9 experiments involving pooled amplicon sequencing libraries. One common experimental strategy is to pool multiple amplicons (e.g. a single on-target site plus a set of potential off-target sites) into a single deep sequencing reaction (briefly, genomic DNA samples for pooled applications can be prepared by first amplifying the target regions for each gene/target of interest with
-regions of 150-400bp depending on the desired coverage. In a second round of PCR, with minimized cycle numbers, barcode and adaptors are added. With optimization, these two rounds of PCR can be merged into a single reaction. These reactions are then quantified, normalized, pooled, and undergo quality control before being sequenced).
-CRISPRessoPooled demultiplexes reads from multiple amplicons and runs the CRISPResso utility with appropriate reads for each amplicon separately.
-
-#### Usage
-
-This tool can run in 3 different modes:
-
-**Amplicons mode:** Given a set of amplicon sequences, in this mode the
-tool demultiplexes the reads, aligning each read to the amplicon with
-best alignment, and creates separate compressed FASTQ files, one for
-each amplicon. Reads that do not align to any amplicon are discarded.
-After this preprocessing, CRISPResso is run for each FASTQ file, and
-separated reports are generated, one for each amplicon.
-
-To run the tool in this mode the user must provide:
-
-1.  Paired-end reads (two files) or single-end reads (single file)
-    in [FASTQ
-    format ](http://en.wikipedia.org/wiki/FASTQ_format)(fastq.gz files
-    are also accepted)
-
-2.  A description file containing the amplicon sequences used to enrich
-    regions in the genome and some additional information. In
-    particular, this file, is a tab delimited text file with up to 12
-    columns (first 2 columns required):
-
-- *AMPLICON\_NAME*: an identifier for the amplicon (*must be unique*).
-
-- *AMPLICON\_SEQUENCE*: amplicon sequence used in the design of
-    the experiment.
-
-- *sgRNA\_SEQUENCE (OPTIONAL)*: sgRNA sequence used for this amplicon
-    *without the PAM sequence.* If not available, enter *NA.*
-
-- *EXPECTED\_AMPLICON\_AFTER\_HDR (OPTIONAL)*: expected amplicon
-    sequence in case of HDR. If more than one, separate by commas *and
-    not spaces*. If not available, enter *NA.*
-
-- *CODING\_SEQUENCE (OPTIONAL)*: Subsequence(s) of the amplicon
-    corresponding to coding sequences. If more than one, separate by
-    commas *and not spaces*. If not available, enter *NA.*
-
-
-- *PRIME\_EDITING\_PEGRNA\_SPACER\_SEQ (OPTIONAL)*: pegRNA spacer sgRNA sequence
-    used in prime editing. The spacer should not include the PAM sequence.
-    The sequence should be given in the RNA 5'->3' order, so for Cas9, the
-    PAM would be on the right side of the given sequence.
-    If not available, enter *NA.*
-
-- *PRIME\_EDITING\_NICKING\_GUIDE\_SEQ (OPTIONAL)*: Nicking sgRNA sequence used in prime
-    editing. The sgRNA should not include the PAM sequence. The sequence should be given
-    in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence.
-    If not available, enter *NA.*
-
-- *PRIME\_EDITING\_PEGRNA\_EXTENSION\_SEQ (OPTIONAL)*: Extension sequence used in prime
-    editing. The sequence should be given in the RNA 5'->3' order, such that the sequence
-    starts with the RT template including the edit, followed by the Primer-binding site (PBS).
-    If not available, enter *NA.*
-
-- *PRIME\_EDITING\_PEGRNA\_SCAFFOLD\_SEQ (OPTIONAL)*: If given, reads containing any of this scaffold sequence
-    before extension sequence (provided by --prime_editing_extension_seq) will be classified
-    as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that
-    the RT template directly follows this sequence. A common value ends with 'GGCACCGAGUCGGUGC'.
-    If not available, enter *NA.*
-
-- *PRIME\_EDITING\_PEGRNA\_SCAFFOLD\_MIN\_MATCH\_LENGTH (OPTIONAL)*: Minimum number of bases matching
-    scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold
-    sequence matches the reference sequence at the incorporation site, the minimum number of
-    bases to match will be minimally increased (beyond this parameter) to disambiguate between
-    prime-edited and scaffold-incorporated sequences. If not available, enter *NA.*
-
-- *PRIME\_EDITING\_OVERRIDE\_PRIME\_EDITED\_REF\_SEQ (OPTIONAL)*:If given, this sequence will be used
-    as the prime-edited reference sequence. This may be useful if the prime-edited reference
-    sequence has large indels or the algorithm cannot otherwise infer the correct reference
-    sequence. If not available, enter *NA.*
-
-- *QWC or QUANTIFICATION\_WINDOW\_COORDINATES (OPTIONAL)*: Bp positions in the amplicon sequence specifying the quantification window.
-    Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that
-    the first nucleotide is position 0. Ranges are separated by the dash sign like "start-stop",
-    and multiple ranges can be separated by the underscore (_). A value of 0 disables this filter.
-    If not available, enter *NA.*
-- *W or QUANTIFICATION\_WINDOW\_SIZE (OPTIONAL)*: Defines the size (in bp) of the quantification window extending from the position specified by the "--cleavage_offset" or "--quantification_window_center" parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp. (default: 1) If not available, enter *NA.*
-
-- *WC or QUANTIFICATION\_WINDOW\_CENTER (OPTIONAL)*: Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17. (default: -3) If not available, enter *NA.*
-
-A file in the correct format should look like this:
-
-Site1 CACACTGTGGCCCCTGTGCCCAGCCCTGGGCTCTCTGTACATGAAGCAAC CCCTGTGCCCAGCCC NA NA
-
-Site2 GTCCTGGTTTTTGGTTTGGGAAATATAGTCATC NA GTCCTGGTTTTTGGTTTAAAAAAATATAGTCATC NA
-
-Site 3 TTTCTGGTTTTTGGTTTGGGAAATATAGTCATC NA NA GGAAATATA
-
-The user can easily create this file with *any text editor* or with
-spreadsheet software like Excel (Microsoft), Numbers (Apple) or Sheets
-(Google Docs) and then save it as tab delimited file.
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -f AMPLICONS_FILE.txt --name ONLY_AMPLICONS_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -f AMPLICONS_FILE.txt --name ONLY_AMPLICONS_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-The output of CRISPRessoPooled Amplicons mode consists of:
-
-1.  REPORT\_READS\_ALIGNED\_TO\_AMPLICONS.txt: this file contains the
-    same information provided in the input description file, plus some
-    additional columns:
-
-    a.  *Demultiplexed\_fastq.gz\_filename*: name of the files
-        containing the raw reads for each amplicon.
-
-    b.  *n\_reads*: number of reads recovered for each amplicon.
-
-2.  A set of fastq.gz files, one for each amplicon.
-
-3.  A set of folders, one for each amplicon, containing a full
-    CRISPResso report.
-
-4.  SAMPLES_QUANTIFICATION_SUMMARY.txt: this file contains a summary of the quantification and the alignment statistics for each          region analyzed (read counts and percentages for the various classes: Unmodified, NHEJ, point mutations, and HDR).
-
-5.  *CRISPRessoPooled\_RUNNING\_LOG.txt*:  execution log and messages
-    for the external utilities called.
-
-**Genome mode:** In this mode the tool aligns each read to the best
-location in the genome. Then potential amplicons are discovered looking
-for regions with enough reads (the default setting is to have at least
-1000 reads, but the parameter can be adjusted with the option
-*--min\_reads\_to\_use\_region*). If a gene annotation file from UCSC is
-provided, the tool also reports the overlapping gene/s to the region. In
-this way it is possible to check if the amplified regions map to
-expected genomic locations and/or also to pseudogenes or other
-problematic regions. Finally CRISPResso is run in each region
-discovered.
-
-To run the tool in this mode the user must provide:
-
-1.  Paired-end reads (two files) or single-end reads (single file)
-    in [FASTQ
-    format ](http://en.wikipedia.org/wiki/FASTQ_format)(fastq.gz files
-    are also accepted)
-
-2.  The full path of the reference genome in bowtie2 format (e.g.
-    /genomes/human\_hg19/hg19). Instructions on how to build
-    a custom index or precomputed index for human and mouse genome
-    assembly can be downloaded from the bowtie2
-    website: http://bowtie-bio.sourceforge.net/bowtie2/index.shtml.
-
-3.  Optionally the full path of a gene annotations file from UCSC. The
-    user can download this file from the UCSC Genome Browser (
-    http://genome.ucsc.edu/cgi-bin/hgTables?command=start ) selecting as
-    table "knownGene", as output format "all fields from selected table"
-    and as file returned "gzip compressed". (e.g. /genomes/human\_hg19/gencode\_v19.gz)
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -x /GENOMES/hg19/hg19 --name ONLY_GENOME_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -x /GENOMES/hg19/hg19 --name ONLY_GENOME_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-The output of CRISPRessoPooled Genome mode consists of:
-
-1.  REPORT\_READS\_ALIGNED\_TO\_GENOME\_ONLY.txt: this file contains the
-    list of all the regions discovered, one per line with the following
-    information:
-
--   chr\_id: chromosome of the region in the reference genome.
-
--   bpstart: start coordinate of the region in the reference genome.
-
--   bpend: end coordinate of the region in the reference genome.
-
--   fastq\_file: location of the fastq.gz file containing the reads
-    mapped to the region.
-
--   n\_reads: number of reads mapped to the region.
-
--   sequence: the sequence, on the reference genome for the region.
-
-1.  MAPPED\_REGIONS (folder): this folder contains all the fastq.gz
-    files for the discovered regions.
-
-2.  A set of folders with the CRISPResso report on the regions with
-    enough reads.
-
-3.  SAMPLES_QUANTIFICATION_SUMMARY.txt: this file contains a summary of the quantification and the alignment statistics for each          region analyzed (read counts and percentages for the various classes: Unmodified, NHEJ, point mutations, and HDR).
-
-4.  *CRISPRessoPooled\_RUNNING\_LOG.txt*:  execution log and messages
-    for the external utilities called.
-
-    This running mode is particularly useful to check for mapping
-    artifacts or contamination in the library. In an optimal
-    experiment, the list of the regions discovered should contain only
-    the regions for which amplicons were designed.
-
-**Mixed mode (Amplicons + Genome)**: in this mode, the tool first aligns
-reads to the genome and, as in the **Genome mode**, discovers aligning
-regions with reads exceeding a tunable threshold. Next it will align the
-amplicon sequences to the reference genome and will use only the reads
-that match both the amplicon locations and the discovered genomic
-locations, excluding spurious reads coming from other regions, or reads
-not properly trimmed. Finally CRISPResso is run using each of the
-surviving regions.
-
-To run the tool in this mode the user must provide:
-
--   Paired-end reads (two files) or single-end reads (single file)
-    in [FASTQ
-    format ](http://en.wikipedia.org/wiki/FASTQ_format)(fastq.gz files
-    are also accepted)
-
--   A description file containing the amplicon sequences used to enrich
-    regions in the genome and some additional information (as described
-    in the Amplicons mode section).
-
--   The reference genome in bowtie2 format (as described in Genome
-    mode section).
-
--   Optionally the gene annotations from UCSC (as described in Genome
-    mode section).
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -f AMPLICONS_FILE.txt -x /GENOMES/hg19/hg19 --name AMPLICONS_AND_GENOME_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoPooled -r1 SRR1046762_1.fastq.gz -r2 SRR1046762_2.fastq.gz -f AMPLICONS_FILE.txt -x /GENOMES/hg19/hg19 --name AMPLICONS_AND_GENOME_SRR1046762 --gene_annotations gencode_v19.gz
-```
-
-The output of CRISPRessoPooled Mixed Amplicons + Genome mode consists of
-these files:
-
-1.  REPORT\_READS\_ALIGNED\_TO\_GENOME\_AND\_AMPLICONS.txt: this file
-    contains the same information provided in the input description
-    file, plus some additional columns:
-
-    a.  Amplicon\_Specific\_fastq.gz\_filename: name of the file
-        containing the raw reads recovered for the amplicon.
-
-    b.  *n\_reads*: number of reads recovered for the amplicon.
-
-    c.  *Gene\_overlapping:* gene/s overlapping the amplicon region.
-
-    d.  chr\_id: chromosome of the amplicon in the reference genome.
-
-    e.  bpstart: start coordinate of the amplicon in the
-        reference genome.
-
-    f.  bpend: end coordinate of the amplicon in the reference genome.
-
-    g.  Reference\_Sequence: sequence in the reference genome for the
-        region mapped for the amplicon.
-
-2.  MAPPED\_REGIONS (folder): this folder contains all the fastq.gz
-    files for the discovered regions.
-
-3.  A set of folders with the CRISPResso report on the amplicons with
-    enough reads.
-
-4.  SAMPLES_QUANTIFICATION_SUMMARY.txt: this file contains a summary of the quantification and the alignment statistics for each          region analyzed (read counts and percentages for the various classes: Unmodified, NHEJ, point mutations, and HDR).
-
-5.  *CRISPRessoPooled\_RUNNING\_LOG.txt*:   execution log and messages
-    for the external utilities called.
-
-The Mixed mode combines the benefits of the two previous running modes.
-In this mode it is possible to recover in an unbiased way all the
-genomic regions contained in the library, and hence discover
-contaminations or mapping artifacts. In addition, by knowing the
-location of the amplicon with respect to the reference genome, reads not
-properly trimmed or mapped to pseudogenes or other problematic regions
-will be automatically discarded, providing the cleanest set of reads to
-quantify the mutations in the target regions with CRISPResso.
-
-If the focus of the analysis is to obtain the best quantification of
-editing efficiency for a set of amplicons, we suggest running the tool
-in the Mixed mode. The Genome mode is instead suggested to check
-problematic libraries, since a report is generated for each region
-discovered, even if the region is not mappable to any amplicon (however,
-his may be time consuming). Finally the Amplicon mode is the fastest,
-although the least reliable in terms of quantification accuracy.
-
-#### Parameter List
--f or --amplicons_file: Amplicons description file (default: ''). This file is a tab-delimited text file with up to 14 columns (2 required):
-
---amplicon_name:  an identifier for the amplicon (must be unique)
-
---amplicon_seq:  amplicon sequence used in the experiment
-
---guide_seq (OPTIONAL):  sgRNA sequence used for this amplicon without the PAM sequence. Multiple guides can be given separated by commas and not spaces.
-
---expected_hdr_amplicon_seq (OPTIONAL): expected amplicon sequence in case of HDR.
-
---coding_seq (OPTIONAL): Subsequence(s) of the amplicon corresponding to coding sequences. If more than one separate them by commas and not spaces.
-
---prime_editing_pegRNA_spacer_seq (OPTIONAL): pegRNA spacer sgRNA sequence used in prime editing. The spacer should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the given sequence.
-
---prime_editing_nicking_guide_seq (OPTIONAL): Nicking sgRNA sequence used in prime editing. The sgRNA should not include the PAM sequence. The sequence should be given in the RNA 5'->3' order, so for Cas9, the PAM would be on the right side of the sequence.
-
---prime_editing_pegRNA_extension_seq (OPTIONAL): Extension sequence used in prime editing. The sequence should be given in the RNA 5'->3' order, such that the sequence starts with the RT template including the edit, followed by the Primer-binding site (PBS).
-
---prime_editing_pegRNA_scaffold_seq (OPTIONAL): If given, reads containing any of this scaffold sequence before extension sequence (provided by --prime_editing_extension_seq) will be classified as 'Scaffold-incorporated'. The sequence should be given in the 5'->3' order such that the RT template directly follows this sequence. A common value ends with 'GGCACCGAGUCGGUGC'.
-
---prime_editing_pegRNA_scaffold_min_match_length (OPTIONAL): Minimum number of bases matching scaffold sequence for the read to be counted as 'Scaffold-incorporated'. If the scaffold sequence matches the reference sequence at the incorporation site, the minimum number of bases to match will be minimally increased (beyond this parameter) to disambiguate between prime-edited and scaffold-incorporated sequences.
-
---prime_editing_override_prime_edited_ref_seq (OPTIONAL): If given, this sequence will be used as the prime-edited reference sequence. This may be useful if the prime-edited reference sequence has large indels or the algorithm cannot otherwise infer the correct reference sequence.
-
---quantification_window_coordinates (OPTIONAL): Bp positions in the amplicon sequence specifying the quantification window. This parameter overrides values of the "--quantification_window_center", "-- cleavage_offset", "--window_around_sgrna" or "-- window_around_sgrna" values. Any indels/substitutions outside this window are excluded. Indexes are 0-based, meaning that the first nucleotide is position 0. Ranges are separated by the dash sign like "start-stop", and multiple ranges can be separated by the underscore (\_). A value of 0 disables this filter. (can be comma-separated list of values, corresponding to amplicon sequences given in --amplicon_seq e.g. 5-10,5-10_20-30 would specify the 5th-10th bp in the first reference and the 5th-10th and 20th-30th bp in the second reference) (default: None)
-
---quantification_window_size (OPTIONAL): Defines the size (in bp) of the quantification window extending from the position specified by the "--cleavage_offset" or "--quantification_window_center" parameter in relation to the provided guide RNA sequence(s) (--sgRNA). Mutations within this number of bp from the quantification window center are used in classifying reads as modified or unmodified. A value of 0 disables this window and indels in the entire amplicon are considered. Default is 1, 1bp on each side of the cleavage position for a total length of 2bp.
-
---quantification_window_center (OPTIONAL): Center of quantification window to use within respect to the 3' end of the provided sgRNA sequence. Remember that the sgRNA sequence must be entered without the PAM. For cleaving nucleases, this is the predicted cleavage position. The default is -3 and is suitable for the Cas9 system. For alternate nucleases, other cleavage offsets may be appropriate, for example, if using Cpf1 this parameter would be set to 1. For base editors, this could be set to -17.
-
---gene_annotations: Gene Annotation Table from UCSC Genome Browser Tables <http://genome.ucsc.edu/cgi-bin/hgTables?command=start>, please select as table "knownGene", as output format "all fields from selected table" and as file returned "gzip compressed". (default: '')
-
--x or --bowtie2_index: Basename of Bowtie2 index for the reference genome. (default: '')
-
---bowtie2_options_string: Override options for the Bowtie2 alignment command. By default, this is " --end-to-end -N 0 --np 0 -mp 3,2 --score-min L,-5,-3(1-H)" where H is the default homology score. (default: ' --end-to-end -N 0 --np 0 -mp 3,2 --score-min L,-5,-3(1-H)')
-
---use_legacy_bowtie2_options_string: Use legacy (more stringent) Bowtie2 alignment parameters: " -k 1 --end-to-end -N 0 --np 0 ". (default: False)
-
---min_reads_to_use_region: Minimum number of reads that align to a region to perform the CRISPResso analysis. (default: 1000)
-
---skip_failed: Continue with pooled analysis even if one sample fails. (default: False)
-
---skip_reporting_problematic_regions: Skip reporting of problematic regions. By default, when both amplicons (-f) and genome (-x) are provided, problematic reads that align to the genome but to positions other than where the amplicons align are reported as problematic. (default: False)
-
---compile_postrun_references: If set, a file will be produced which compiles the reference sequences of frequent amplicons. (default: False)
-
---compile_postrun_reference_allele_cutoff: Only alleles with at least this percentage frequency in the population will be reported in the postrun analysis. This parameter is given as a percent, so 30 is 30%. (default: 30)
-
---alternate_alleles: Path to tab-separated file with alternate allele sequences for pooled experiments. This file has the columns "region_name","reference_seqs", and "reference_names" and gives the reference sequences of alternate alleles that will be passed to CRISPResso for each individual region for allelic analysis. Multiple reference alleles and reference names for a given region name are separated by commas (no spaces). (default: '')
-
---limit_open_files_for_demux: If set, only one file will be opened during demultiplexing of read alignment locations. This will be slightly slower as the reads must be sorted, but may be necessary if the number of amplicons is greater than the number of files that can be opened due to OS constraints. (default: False)
-
-### CRISPRessoWGS
-
-CRISPRessoWGS is a utility for the analysis of genome editing experiment
-from whole genome sequencing (WGS) data. CRISPRessoWGS allows exploring
-any region of the genome to quantify targeted editing or potentially
-off-target effects. The intended use case for CRISPRessoWGS is the analysis
-of targeted regions, and WGS reads from those regions will be realigned using
-CRISPResso's alignment aligorithm for more accurate genome editing 
-quantification. To scan the entire genome for mutations 
-[VarScan](http://dkoboldt.github.io/varscan/) or [MuTect](https://github.com/broadinstitute/mutect) 
-are more suitable, and identified regions can be analyzed and visualized using
-CRISPRessoWGS.
-
-#### Usage
-To run CRISPRessoWGS you must provide:
-
-1.  A genome aligned *BAM* file. To align reads from a WGS experiment to
-    the genome there are many options available, we suggest using either
-    **Bowtie2 (**<http://bowtie-bio.sourceforge.net/bowtie2/>) or **BWA
-    (**<http://bio-bwa.sourceforge.net/>**).**
-
-2.  A *FASTA* file containing the reference sequence used to align the
-    reads and create the BAM file (the reference files for the most
-    common organism can be download from
-    UCSC: http://hgdownload.soe.ucsc.edu/downloads.html. *Download and
-    uncompress only the file ending with .fa.gz*, for example for the
-    last version of the human genome download and *uncompress* the
-    file hg38.fa.gz)
-
-3.  Descriptions file containing the coordinates of the regions to
-    analyze and some additional information. In particular, this file is
-    a tab delimited text file with up to 7 columns (4 required):
-
-    -   chr\_id: chromosome of the region in the reference genome.
-
-    -   bpstart: start coordinate of the region in the reference genome.
-
-    -   bpend: end coordinate of the region in the reference genome.
-
-    -   *REGION\_NAME*: an identifier for the region (*must be unique*).
-
-    -   *sgRNA\_SEQUENCE (OPTIONAL)*: sgRNA sequence used for this genomic segment *without the PAM sequence.* If not available, enter *NA.*
-
-    -   *EXPECTED\_SEGMENT\_AFTER\_HDR (OPTIONAL)*: expected genomic segment sequence in case of HDR. If more than one, separate by commas *and not spaces*. If not available, enter *NA.*
-
-    -   *CODING\_SEQUENCE (OPTIONAL)*: Subsequence(s) of the genomic segment corresponding to coding sequences. If more than one, separate by commas *and not spaces*. If not available, enter *NA.*
-
-A file in the correct format should look like this (column entries must be separated by tabs):
-
-```
-chr1 65118211 65118261 R1 CTACAGAGCCCCAGTCCTGG NA NA
-
-chr6 51002798 51002820 R2 NA NA NA
-```
-
-Note: *no column titles should be entered.* As you may have noticed this
-file is just a *BED* file with extra columns. For this reason a normal
-BED file with 4 columns, is also **accepted** by this utility.
-
-4.  Optionally the full path of a gene annotations file from UCSC. You
-    can download the this file from the UCSC Genome
-    Browser (http://genome.ucsc.edu/cgi-bin/hgTables?command=start)
-    selecting as table "knownGene", as output format "all fields from
-    selected table" and as file returned "gzip compressed". (something
-    like: /genomes/human\_hg19/gencode\_v19.gz)
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoWGS -b WGS/50/50_sorted_rmdup_fixed_groups.bam -f WGS_TEST.txt -r /GENOMES/mm9/mm9.fa --gene_annotations ensemble_mm9.txt.gz --name CRISPR_WGS_SRR1542350
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoWGS -b WGS/50/50_sorted_rmdup_fixed_groups.bam -f WGS_TEST.txt -r /GENOMES/mm9/mm9.fa --gene_annotations ensemble_mm9.txt.gz --name CRISPR_WGS_SRR1542350
-```
-
-The output from these files will consist of:
-
-1.  REPORT\_READS\_ALIGNED\_TO\_SELECTED\_REGIONS\_WGS.txt: this file
-    contains the same information provided in the input description
-    file, plus some additional columns:
-
-    a.  sequence: sequence in the reference genome for the
-        region specified.
-
-    b.  *gene\_overlapping:* gene/s overlapping the region specified.
-
-    c.  *n\_reads*: number of reads recovered for the region.
-
-    d.  bam\_file\_with\_reads\_in\_region: file containing only the
-        subset of the reads that overlap, also partially, with
-        the region. This file is indexed and can be easily loaded for
-        example on IGV for visualization of single reads or for the
-        comparison of two conditions. For example, in the figure below
-        (fig X) we show reads mapped to a region inside the coding
-        sequence of the gene Crygc subjected to
-        NHEJ (CRISPR\_WGS\_SRR1542350) vs reads from a control
-        experiment (CONTROL\_WGS\_SRR1542349).
-
-    e.  fastq.gz\_file\_trimmed\_reads\_in\_region: file containing only
-        the subset of reads fully covering the specified regions, and
-        trimmed to match the sequence in that region. These reads are
-        used for the subsequent analysis with CRISPResso.
-
-2.  ANALYZED\_REGIONS (folder): this folder contains all the BAM and
-    FASTQ files, one for each region analyzed.
-
-3.  A set of folders with the CRISPResso report on the regions provided
-    in input with enough reads (the default setting is to have at least
-    10 reads, but the parameter can be adjusted with the option
-
-    *--min\_reads\_to\_use\_region*).
-
-4.  *CRISPRessoPooled\_RUNNING\_LOG.txt*:   execution log and messages
-    for the external utilities called.
-
-This utility is particular useful to investigate and quantify mutation
-frequency in a list of potential target or off-target sites, coming for
-example from prediction tools, or from other orthogonal assays.
-
-#### Parameter List
-
--b or --bam_file: WGS aligned bam file. (default: 'bam filename')
-
--f or --region_file: Regions description file. A BED format file containing the regions to analyze, one per line. The REQUIRED columns are: chr_id(chromosome name), bpstart(start position), bpend(end position), the optional columns are: name (an unique indentifier for the region), guide_seq, expected_hdr_amplicon_seq,coding_seq, see CRISPResso help for more details on these last 3 parameters).
-
--r or --reference_file: A FASTA format reference file (for example hg19.fa for the human genome). (default: '')
-
---min_reads_to_use_region: Minimum number of reads that align to a region to perform the CRISPResso analysis. (default: 10)
-
---gene_annotations: Gene Annotation Table from UCSC Genome Browser Tables <http://genome.ucsc.edu/cgi-bin/hgTables?command=start>, please select as table "knownGene", as output format "all fields from selected table" and as file returned "gzip compressed". (default: '')
-
---crispresso_command: CRISPResso command to call. (default: 'CRISPResso')
-
-### CRISPRessoCompare
-
-CRISPRessoCompare is a utility for the comparison of a pair of CRISPResso analyses. CRISPRessoCompare produces a summary of differences between two conditions, for example a CRISPR treated and an untreated control sample (see figure below). Informative plots are generated showing the differences in editing rates and localization within the reference amplicon,
-
-#### Usage
-
-To run CRISPRessoCompare you must provide:
-
-1.	Two output folders generated with CRISPResso using the same reference amplicon and settings but on different datasets.
-2.	Optionally a name for each condition to use for the plots, and the name of the output folder
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoCompare -n1 "VEGFA CRISPR" -n2 "VEGFA CONTROL"  -n VEGFA_Site_1_SRR10467_VS_SRR1046787 CRISPResso_on_VEGFA_Site_1_SRR1046762/ CRISPResso_on_VEGFA_Site_1_SRR1046787/
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoCompare -n1 "VEGFA CRISPR" -n2 "VEGFA CONTROL"  -n VEGFA_Site_1_SRR10467_VS_SRR1046787 CRISPResso_on_VEGFA_Site_1_SRR1046762/ CRISPResso_on_VEGFA_Site_1_SRR1046787/
-```
-
-The output will consist of:
-
-1.	Comparison_Efficiency.pdf: a figure containing a comparison of the edit frequencies for each category (NHEJ, MIXED NHEJ-HDR and HDR) and as well the net effect subtracting the second sample (second folder in the command line) provided in the analysis from the first sample (first folder in the command line).
-2.	Comparison_Combined_Insertion_Deletion_Substitution_Locations.pdf: a figure showing the average profile for the mutations for the two samples in the same scale and their difference with the same convention used in the previous figure (first sample – second sample).
-3.	CRISPRessoCompare_significant_base_counts.txt: a text file reporting the number of bases for each amplicon and in the quantification window for each amplicon that were significantly enriched for Insertions, Deletions, and Substitutions, as well as All Modifications (Fisher's exact test, Bonferonni corrected p-values).
-4.	CRISPRessoCompare_RUNNING_LOG.txt: detailed execution log.
-
-#### Parameter List
-crispresso_output_folder_1: First output folder with CRISPResso analysis (Required)
-crispresso_output_folder_2: Second output folder with CRISPResso analysis (Required)
-
--n or --name: Output name. (default:'')
--n1 or --sample_1_name: Sample 1 name
--n2 or --sample_2_name: Sample 2 name
--o or --output_folder: Output folder name
---reported_qvalue_cutoff: Q-value cutoff for signifance in tests for differential editing. Each base position is tested (for insertions, deletions, substitutions, and all modifications) using Fisher's exact test, followed by Bonferonni correction. The number of bases with a significance below this threshold in the quantification window are counted and reported in the output summary. (default:0.05)
---min_frequency_alleles_around_cut_to_plot: Minimum %% reads required to report an allele in the alleles table plot. (default:0.2)
---max_rows_alleles_around_cut_to_plot: Maximum number of rows to report in the alleles table plot. (default:50)
---suppress_report: Suppress output report. (default:False)
---place_report_in_output_folder: If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output. (default:False)
-
-### CRISPRessoPooledWGSCompare
-
-CRISPRessoPooledWGSCompare is an extension of the CRIPRessoCompare utility allowing the user to run and summarize multiple CRISPRessoCompare analyses where several regions are analyzed in two different conditions, as in the case of the CRISPRessoPooled or CRISPRessoWGS utilities.
-
-
-#### Usage
-
-To run CRISPRessoPooledWGSCompare you must provide:
-1.	Two output folders generated with CRISPRessoPooled or CRISPRessoWGS using the same reference amplicon and settings but on different datasets.
-2.	Optionally a name for each condition to use for the plots, and the name of the output folder
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoPooledWGSCompare CRISPRessoPooled_on_AMPLICONS_AND_GENOME_SRR1046762/ CRISPRessoPooled_on_AMPLICONS_AND_GENOME_SRR1046787/ -n1 SRR1046762 -n2 SRR1046787 -n AMPLICONS_AND_GENOME_SRR1046762_VS_SRR1046787
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoPooledWGSCompare CRISPRessoPooled_on_AMPLICONS_AND_GENOME_SRR1046762/ CRISPRessoPooled_on_AMPLICONS_AND_GENOME_SRR1046787/ -n1 SRR1046762 -n2 SRR1046787 -n AMPLICONS_AND_GENOME_SRR1046762_VS_SRR1046787
-```
-
-The output from these files will consist of:
-1.	COMPARISON_SAMPLES_QUANTIFICATION_SUMMARIES.txt: this file contains a summary of the quantification for each of the two conditions for each region and their difference (read counts and percentages for the various classes: Unmodified, NHEJ, MIXED NHEJ-HDR  and HDR).
-2.	A set of folders with CRISPRessoCompare reports on the common regions with enough reads in both conditions.
-3.	CRISPRessoPooledWGSCompare_significant_base_count_summary.txt: a text file summarizing for each sample and amplicon in both conditions the number of bases for each amplicon and in the quantification window for each amplicon that were significantly enriched for Insertions, Deletions, and Substitutions, as well as All Modifications (Fisher's exact test, Bonferonni corrected p-values).
-4.	CRISPRessoPooledWGSCompare_RUNNING_LOG.txt: detailed execution log.
-
-#### Parameter List
-crispresso_pooled_wgs_output_folder_1: First output folder with CRISPRessoPooled or CRISPRessoWGS analysis (Required)
-crispresso_pooled_wgs_output_folder_2: Second output folder with CRISPRessoPooled or CRISPRessoWGS analysis (Required)
-
--p or --n_processes: Number of processes to use for this analysis. Can be set to 'max'.
--n or --name: Output name. (default:'')
--n1 or --sample_1_name: Sample 1 name
--n2 or --sample_2_name: Sample 2 name
--o or --output_folder: Output folder name
---reported_qvalue_cutoff: Q-value cutoff for signifance in tests for differential editing. Each base position is tested (for insertions, deletions, substitutions, and all modifications) using Fisher's exact test, followed by Bonferonni correction. The number of bases with a significance below this threshold in the quantification window are counted and reported in the output summary. (default:0.05)
---min_frequency_alleles_around_cut_to_plot: Minimum %% reads required to report an allele in the alleles table plot. (default:0.2)
---max_rows_alleles_around_cut_to_plot: Maximum number of rows to report in the alleles table plot. (default:50)
---suppress_report: Suppress output report. (default:False)
---place_report_in_output_folder: If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output. (default:False)
-
-### CRISPRessoAggregate
-
-CRISPRessoAggregate is a utility to combine the analysis of several CRISPResso runs. The analyses are summarized and editing rates are optionally visualized in a summary report.
-
-#### Usage
-
-CRISPRessoAggregate has the following parameters:
-
---name: Output name of the report (required)
-
---prefix: Prefix for CRISPResso folders to aggregate (may be specified multiple times)
-
---suffix: Suffix for CRISPResso folders to aggregate
-
---min_reads_for_inclusion: Minimum number of reads for a run to be included in the run summary (default: 0)
-
---place_report_in_output_folder: If true, report will be written inside the CRISPResso output folder. By default, the report will be written one directory up from the report output (default: False)
-
---suppress_report: Suppress output report (default: False)
-
---suppress_plots: Suppress output plots (default: False)
-
-To run CRISPRessoCompare you must provide the --name parameter, and CRISPResso folders in the current directory will be summarized. To summarize folders in other locations, provide these locations using the '--prefix' parameter.
-
-Example:
-
-*Using Bioconda:*
-```
-CRISPRessoAggregate --name "VEGFA" --prefix CRISPRessoRuns/VEGFA/
-```
-
-*Using Docker:*
-```
-docker run -v ${PWD}:/DATA -w /DATA -i pinellolab/crispresso2 CRISPRessoAggregate --name "VEGFA" --prefix CRISPRessoRuns/VEGFA/
-```
-
-The output will consist of:
 
-1.  CRISPResso2Aggregate_report.html: a html file containing links to all aggregated runs.
-2.  CRISPRessoAggregate_amplicon_information.txt: A tab-separated file with a line for each amplicon that was found in any run. The 'Amplicon Name' column shows the unique name for this amplicon sequence. 'Number of sources' shows how many runs the amplicon was found in, and 'Amplicon sources' show which run folders the amplicon was found in, as well as the name of the amplicon in that run.
-3.  CRISPRessoAggregate_mapping_statistics.txt: A tab-separated file showing the number of reads sequenced and mapped for each run.
-4.  CRISPRessoAggregate_quantification_of_editing_frequency.txt: A tab-separated with the number of reads and edits for each run folder. Data from run folders with multiple amplicons show the sum totals for all amplicons.
-5.  CRISPRessoAggregate_quantification_of_editing_frequency_by_amplicon.txt: A tab-separated file showing the number of reads and edits for each amplicon for each run folder. Data from run folders with multiple amplicons will appear on multiple lines, with one line per amplicon.
+If you run into any issues, check out the [Troubleshooting page](https://docs.crispresso.com/troubleshooting.html) or submit a [new discussion](https://github.com/pinellolab/CRISPResso2/discussions/new?category=troubleshooting).
diff --git a/crispresso_schematic.png b/crispresso_schematic.png
index 602a2304..8f7f9431 100644
Binary files a/crispresso_schematic.png and b/crispresso_schematic.png differ
diff --git a/scripts/countHighQualityBases.py b/scripts/countHighQualityBases.py
new file mode 100644
index 00000000..2dd4dd2b
--- /dev/null
+++ b/scripts/countHighQualityBases.py
@@ -0,0 +1,166 @@
+import gzip
+import argparse
+
+def get_dict_from_plus_line(plus_line):
+    """ Create a dictionary from the 3rd line (plus line) of a CRISPResso-annotated fastq file
+
+    Args:
+        plus_line (str): The 3rd line of a CRISPResso-annotated fastq file
+
+    Returns:
+        dict: dictionary of CRISPResso annotations > values
+    """
+    equals_line_els = plus_line.split(" ")
+    equals_dict = {}
+    for el in equals_line_els:
+        el_els = el.split("=")
+        if len(el_els) > 1:
+            equals_dict[el_els[0]] = el_els[1]
+        else:
+            equals_dict[el_els[0]] = None
+    return equals_dict
+
+def get_ref_details_from_aln_details(aln_details):
+    """ Get the reference details from the 'ALN_DETAILS' element of the 3rd line (plus line) of a CRISPResso-annotated fastq file
+
+    Args:
+        aln_details (str): The ALN_DETAILS element consisting of the reference name, read sequence alignment, reference sequence alignment, and alignment score for each reference
+
+    Returns:
+        dict: dictionary of ref_name > read_seq_alignment, ref_seq_alignment, aln_score
+    """
+    ref_details = {}
+    ref_els = aln_details.split("&")
+    for el in ref_els:
+        el_els = el.split(",")
+        if len(el_els) != 4:
+            raise Exception("got unexpected number of elements in ALN_DETAILS: " + str(el_els))
+        (ref_name, read_seq, ref_seq, aln_score) = el.split(",")
+        ref_details[ref_name] = (read_seq, ref_seq, aln_score)
+    return ref_details
+
+def get_ref_coordinates_from_aln(read_aln, ref_aln):
+    """ Get the reference coordinates from the read and reference alignments
+    So if we want to check the base at the 5th base in the reference, we would go to the ref_pos_in_aln[5]th position in the alignment
+    If we want to get the base in the read corresponding to the 5th base in the reference, we would go to the ref_pos_in_read[5]th position in the read
+
+    Args:
+        read_aln (str): read alignment
+        ref_aln (str): reference alignment
+
+    Returns:
+        arr(int): array of reference positions in the alignment
+        arr(int): array of reference positions in the read
+    """
+    read_pos = -1
+    ref_pos_in_aln = [] #list of reference positions in the alignment
+    ref_pos_in_read = [] #list of reference positions in the read
+    for i in range(len(read_aln)):
+        if read_aln[i] != "-":
+            read_pos += 1
+        if ref_aln[i] != "-":
+            ref_pos_in_aln.append(i)
+            ref_pos_in_read.append(read_pos)
+
+    return (ref_pos_in_aln, ref_pos_in_read)
+
+
+def count_high_quality_bases(file, ref_name, base_pos_in_ref, original_base, target_base, quality_cutoff):
+    """Counts high-quality bases at a certain position in the reference
+
+    Args:
+        file (str): CRISPResso_output.fastq.gz file to analyze
+        ref (str): Reference name
+        base_pos_in_ref (int): 0-based position in the reference to check
+        original_base (char): original base to check
+        target_base (char): target base to check
+        quality_cutoff (int): quality cutoff for high-quality bases
+
+    """
+    # file = "CRISPResso_output.fastq.gz"
+    # ref_name = "Reference"
+    # base_pos_in_ref = 100
+    # original_base = "A"
+    # target_base = "T"
+    # quality_cutoff = 30
+
+    count_total_reads = 0
+    count_unaligned = 0 # also includes reads not aligned to specified ref_name
+
+    count_original_base_highQual = 0
+    count_original_base_lowQual = 0
+
+    count_target_base_highQual = 0
+    count_target_base_lowQual = 0
+
+    count_base_deletion = 0
+
+    count_other_base_highQual = 0
+    count_other_base_lowQual = 0
+
+    with gzip.open(file, 'rt') as f:
+        while True:
+            id_line = f.readline()
+            seq_line = f.readline()
+            plus_line = f.readline()
+            qual_line = f.readline()
+
+            if not id_line:
+                break
+            
+            count_total_reads += 1
+
+            plus_line_dict = get_dict_from_plus_line(plus_line)
+            if 'ALN' not in plus_line_dict:
+                raise Exception("ALN not in plus line: " + plus_line)
+            if plus_line_dict['ALN'] != ref_name:
+                count_unaligned += 1
+            else:
+                ref_details = get_ref_details_from_aln_details(plus_line_dict['ALN_DETAILS'])
+                if ref_name in ref_details:
+                    read_aln = ref_details[ref_name][0] # sequence of read aligned to ref (includes gaps)
+                    ref_aln = ref_details[ref_name][1] # sequence of ref aligned to read (includes gaps)
+                    ref_coordinates_in_aln, ref_coordinates_in_read = get_ref_coordinates_from_aln(read_aln,ref_aln)
+                    base_in_read = read_aln[ref_coordinates_in_aln[base_pos_in_ref]]
+                    quality_in_read = qual_line[ref_coordinates_in_read[base_pos_in_ref]]
+
+                    if base_in_read == original_base:
+                        if ord(quality_in_read) - 33 >= quality_cutoff:
+                            count_original_base_highQual += 1
+                        else:
+                            count_original_base_lowQual += 1
+                    elif base_in_read == target_base:
+                        if ord(quality_in_read) - 33 >= quality_cutoff:
+                            count_target_base_highQual += 1
+                        else:
+                            count_target_base_lowQual += 1
+                    elif base_in_read == "-":
+                        count_base_deletion += 1
+                    else:
+                        if ord(quality_in_read) - 33 >= quality_cutoff:
+                            count_other_base_highQual += 1
+                        else:
+                            count_other_base_lowQual += 1
+
+    print("Total reads read: " + str(count_total_reads))
+    print("Unaligned reads: " + str(count_unaligned))
+    print("Original base high quality: " + str(count_original_base_highQual))
+    print("Original base low quality: " + str(count_original_base_lowQual))
+    print("Target base high quality: " + str(count_target_base_highQual))
+    print("Target base low quality: " + str(count_target_base_lowQual))
+    print("Base deletion: " + str(count_base_deletion))
+    print("Other base high quality: " + str(count_other_base_highQual))
+    print("Other base low quality: " + str(count_other_base_lowQual))
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-f", "--file", help="CRISPResso_output.fastq.gz file to analyze", required=True)
+    parser.add_argument("-r", "--ref_name", help="Reference name", default="Reference")
+    parser.add_argument("-p", "--pos", type=int, help="0-based position in the reference to check", required=True)
+    parser.add_argument("-o", "--original", help="original base to check", required=True)
+    parser.add_argument("-t", "--target", help="target base to check", required=True)
+    parser.add_argument("-q", "--quality", type=int, help="quality cutoff for high-quality bases", default=30)
+    args = parser.parse_args()
+
+    count_high_quality_bases(args.file, args.ref_name, args.pos, args.original, args.target, args.quality)
+    
\ No newline at end of file
diff --git a/scripts/count_sgRNA_specific_edits.py b/scripts/count_sgRNA_specific_edits.py
new file mode 100644
index 00000000..702a6402
--- /dev/null
+++ b/scripts/count_sgRNA_specific_edits.py
@@ -0,0 +1,91 @@
+import argparse
+import os
+import pandas as pd
+import zipfile
+
+from collections import defaultdict
+from CRISPResso2 import CRISPRessoShared
+
+def count_sgRNA_specific_edits(crispresso_output_folder):
+    crispresso2_info = CRISPRessoShared.load_crispresso_info(crispresso_output_folder)
+
+    run_version = crispresso2_info['running_info']['version']
+    version_parts = run_version.split('.')
+    if int(version_parts[0]) != 2 or int(version_parts[1]) < 2 or int(version_parts[2]) < 15:
+        raise Exception('CRISPResso run must be run with CRISPResso2 v2.2.15 or later (this run was run with version v' + str(run_version) + ')')
+
+    if not crispresso2_info['running_info']['args'].write_detailed_allele_table:
+        raise Exception('CRISPResso run must be run with the parameter --write_detailed_allele_table')
+
+    include_idxs = {} # ref_name > guide_name > idxs
+    all_reference_guides = {} # order of guides for each reference
+    modified_counts_by_amplicon = {} # ref_name > category > count
+    for reference_name in crispresso2_info['results']['ref_names']:
+        include_idxs[reference_name] = {}
+        all_reference_guides[reference_name] = []
+        modified_counts_by_amplicon[reference_name] = defaultdict(int)
+        for idx, guide_name in enumerate(crispresso2_info['results']['refs'][reference_name]['sgRNA_names']):
+            this_guide_name = guide_name
+            if this_guide_name == '':
+                this_guide_name = crispresso2_info['results']['refs'][reference_name]['sgRNA_orig_sequences'][idx]
+            if this_guide_name in include_idxs[reference_name]:
+                this_guide_name = this_guide_name + '_' + str(crispresso2_info['results']['refs'][reference_name]['sgRNA_intervals'][idx][0])
+            include_idxs[reference_name][this_guide_name] = crispresso2_info['results']['refs'][reference_name]['sgRNA_include_idxs'][idx]
+            all_reference_guides[reference_name].append(this_guide_name)
+
+
+    z = zipfile.ZipFile(os.path.join(crispresso_output_folder, crispresso2_info['running_info']['allele_frequency_table_zip_filename']))
+    zf = z.open(crispresso2_info['running_info']['allele_frequency_table_filename'])
+    df_alleles = pd.read_csv(zf,sep="\t")
+
+    read_alleles_count = 0 # all alleles read
+    reference_count = defaultdict(int) # counts of modification at reference level
+    reference_modified_count = defaultdict(int)
+    total_counts = defaultdict(int)
+    modified_counts = defaultdict(int)
+    for idx, row in df_alleles.iterrows():
+        read_alleles_count += row['#Reads']
+        this_reference_name = row['Reference_Name']
+        if this_reference_name in include_idxs: # sometimes (e.g. AMBIGUOUS) reads aren't assigned to a reference, so exclude them here..
+            reference_count[this_reference_name] += row['#Reads']
+
+            this_allele_modified_guide_names = []
+            ref_is_modified = False
+            row_insertion_positions = [int(x) for x in row['insertion_positions'][1:-1].split(",")] if row['insertion_positions'] != '[]' else []
+            row_deletion_positions = [int(x) for x in row['deletion_positions'][1:-1].split(",")] if row['deletion_positions'] != '[]' else []
+            row_substitution_positions = [int(x) for x in row['substitution_positions'][1:-1].split(",")] if row['substitution_positions'] != '[]' else []
+            for guide_name in all_reference_guides[this_reference_name]:
+                is_modified = False
+                for ind in include_idxs[this_reference_name][guide_name]:
+                    if ind in row_insertion_positions:
+                        is_modified = True
+                    if ind in row_deletion_positions:
+                        is_modified = True
+                    if ind in row_substitution_positions:
+                        is_modified = True
+                if is_modified:
+                    this_allele_modified_guide_names.append(guide_name)
+                    ref_is_modified = True
+            
+            this_category = 'UNMODIFIED'
+            if ref_is_modified:
+                reference_modified_count[this_reference_name] += row['#Reads']
+                this_category = 'MODIFIED ' + ' + '.join(this_allele_modified_guide_names)
+            modified_counts_by_amplicon[this_reference_name][this_category] += row['#Reads']
+
+
+    print('Processed ' + str(read_alleles_count) + ' alleles')
+    for reference_name in crispresso2_info['results']['ref_names']:
+        print ('Reference: ' + reference_name + ' (' + str(reference_modified_count[reference_name]) + '/' + str(reference_count[reference_name]) + ' modified reads)')
+        for category in modified_counts_by_amplicon[reference_name]:
+            print("\t" + category + ": " + str(modified_counts_by_amplicon[reference_name][category]))
+
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description='Count sgRNA-specific edits')
+    parser.add_argument("-f", "--folder", help="CRISPResso output folder", required=True)
+    args = parser.parse_args()
+
+    count_sgRNA_specific_edits(args.folder)
+    
\ No newline at end of file
diff --git a/scripts/plotAmbiguous.py b/scripts/plotAmbiguous.py
index ee7c24f1..3b01dc3b 100644
--- a/scripts/plotAmbiguous.py
+++ b/scripts/plotAmbiguous.py
@@ -9,7 +9,7 @@
 import numpy as np
 import pandas as pd
 import zipfile
-from CRISPResso2 import CRISPRessoPlot
+
 from CRISPResso2 import CRISPRessoShared
 
 def main():
@@ -21,8 +21,17 @@ def main():
     parser.add_argument("--plot_cut_point",help="If set, a line at the cut point will be plotted.",action="store_true")
     parser.add_argument("--save_png",help="If set, pngs will also be produced (as well as pdfs).",action="store_true")
 
+    #CRISPRessoPro params
+    parser.add_argument('--use_matplotlib', action='store_true',
+                        help='Use matplotlib for plotting instead of plotly/d3 when CRISPRessoPro is installed')
+
     args = parser.parse_args()
 
+    if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+        from CRISPResso2 import CRISPRessoPlot
+    else:
+        from CRISPRessoPro import plot as CRISPRessoPlot
+
     plot_ambiguous_alleles_tables_from_folder(args.CRISPResso2_folder,args.output_root,MIN_FREQUENCY=args.min_freq,MAX_N_ROWS=args.max_rows,SAVE_ALSO_PNG=args.save_png,plot_cut_point=args.plot_cut_point)
 
 def arrStr_to_arr(val):
@@ -109,7 +118,7 @@ def plot_ambiguous_alleles_tables_from_folder(crispresso_output_folder,fig_filen
             for (int_start, int_end) in refs[ref_name]['sgRNA_intervals']:
                 new_sgRNA_intervals += [(int_start - new_sel_cols_start - 1,int_end - new_sel_cols_start - 1)]
             fig_filename_root = fig_filename_root+"_"+ref_name+"_"+sgRNA_label
-            CRISPRessoPlot.plot_alleles_table(ref_seq_around_cut,df_alleles=df_alleles_around_cut,fig_filename_root=fig_filename_root, MIN_FREQUENCY=MIN_FREQUENCY,MAX_N_ROWS=MAX_N_ROWS,SAVE_ALSO_PNG=SAVE_ALSO_PNG,plot_cut_point=plot_cut_point,sgRNA_intervals=new_sgRNA_intervals,sgRNA_names=sgRNA_names,sgRNA_mismatches=sgRNA_mismatches,annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
+            CRISPRessoPlot.plot_alleles_table(ref_seq_around_cut,df_alleles=df_alleles_around_cut,fig_filename_root=fig_filename_root,custom_colors=custom_colors,MIN_FREQUENCY=MIN_FREQUENCY,MAX_N_ROWS=MAX_N_ROWS,SAVE_ALSO_PNG=SAVE_ALSO_PNG,plot_cut_point=plot_cut_point,sgRNA_intervals=new_sgRNA_intervals,sgRNA_names=sgRNA_names,sgRNA_mismatches=sgRNA_mismatches,annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
 
             plot_count += 1
     print('Plotted ' + str(plot_count) + ' plots')
diff --git a/scripts/plotCustomAllelePlot.py b/scripts/plotCustomAllelePlot.py
index 7eb32207..0025706e 100644
--- a/scripts/plotCustomAllelePlot.py
+++ b/scripts/plotCustomAllelePlot.py
@@ -9,7 +9,6 @@
 import numpy as np
 import pandas as pd
 import zipfile
-from CRISPResso2 import CRISPRessoPlot
 from CRISPResso2 import CRISPRessoShared
 import seaborn as sns
 import matplotlib
@@ -29,27 +28,35 @@ def main():
     parser.add_argument("--plot_left",help="Number of bases to plot to the left of the cut site",type=int,default=20)
     parser.add_argument("--plot_right",help="Number of bases to plot to the right of the cut site",type=int,default=20)
     parser.add_argument("--plot_center",help="Center of plot. If set, plots for guide RNAs will not be generated -- only a plot centered at this position will be plotted.",type=int,default=None)
-
+    
+    # CRISPRessoPro params
+    parser.add_argument('--use_matplotlib', action='store_true',
+                        help='Use matplotlib for plotting instead of plotly/d3 when CRISPRessoPro is installed')
 
     args = parser.parse_args()
+    if args.use_matplotlib or not CRISPRessoShared.is_C2Pro_installed():
+        from CRISPResso2 import CRISPRessoPlot
+    else:
+        from CRISPRessoPro import plot as CRISPRessoPlot
+
     plot_alleles_tables_from_folder(args.CRISPResso2_folder,args.output_root,MIN_FREQUENCY=args.min_freq,MAX_N_ROWS=args.max_rows,SAVE_ALSO_PNG=args.save_png,plot_cut_point=args.plot_cut_point,plot_left=args.plot_left,plot_right=args.plot_right,plot_center=args.plot_center)
 
 def arrStr_to_arr(val):
     return [int(x) for x in val[1:-1].split(",")]
 
-def get_row_around_cut_assymetrical(row,cut_point,plot_left,plot_right):
+def get_row_around_cut_asymmetrical(row,cut_point,plot_left,plot_right):
     cut_idx=row['ref_positions'].index(cut_point)
     return row['Aligned_Sequence'][cut_idx-plot_left+1:cut_idx+plot_right+1],row['Reference_Sequence'][cut_idx-plot_left+1:cut_idx+plot_right+1],row['Read_Status']=='UNMODIFIED',row['n_deleted'],row['n_inserted'],row['n_mutated'],row['#Reads'], row['%Reads']
 
-def get_dataframe_around_cut_assymetrical(df_alleles, cut_point,plot_left,plot_right,collapse_by_sequence=True):
+def get_dataframe_around_cut_asymmetrical(df_alleles, cut_point,plot_left,plot_right,collapse_by_sequence=True):
     if df_alleles.shape[0] == 0:
         return df_alleles
     ref1 = df_alleles['Reference_Sequence'].iloc[0]
     ref1 = ref1.replace('-','')
     if (cut_point + plot_right + 1 > len(ref1)):
-        raise(BadParameterException('The plotting window cannot extend past the end of the amplicon. Amplicon length is ' + str(len(ref1)) + ' but plot extends to ' + str(cut_point+plot_right+1)))
+        raise(CRISPRessoShared.BadParameterException('The plotting window cannot extend past the end of the amplicon. Amplicon length is ' + str(len(ref1)) + ' but plot extends to ' + str(cut_point+plot_right+1)))
 
-    df_alleles_around_cut=pd.DataFrame(list(df_alleles.apply(lambda row: get_row_around_cut_assymetrical(row,cut_point,plot_left,plot_right),axis=1).values),
+    df_alleles_around_cut=pd.DataFrame(list(df_alleles.apply(lambda row: get_row_around_cut_asymmetrical(row,cut_point,plot_left,plot_right),axis=1).values),
                     columns=['Aligned_Sequence','Reference_Sequence','Unedited','n_deleted','n_inserted','n_mutated','#Reads','%Reads'])
 
     df_alleles_around_cut=df_alleles_around_cut.groupby(['Aligned_Sequence','Reference_Sequence','Unedited','n_deleted','n_inserted','n_mutated']).sum().reset_index().set_index('Aligned_Sequence')
@@ -111,7 +118,7 @@ def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,p
             plot_cut_point = plot_center
             ref_seq_around_cut=refs[ref_name]['sequence'][cut_point-plot_left+1:cut_point+plot_right+1]
 
-            df_alleles_around_cut=get_dataframe_around_cut_assymetrical(df_alleles, cut_point, plot_left, plot_right)
+            df_alleles_around_cut=get_dataframe_around_cut_asymmetrical(df_alleles, cut_point, plot_left, plot_right)
             this_allele_count = len(df_alleles_around_cut.index)
             if this_allele_count < 1:
                 print('No reads found for ' + ref_name)
@@ -126,7 +133,19 @@ def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,p
                 new_sgRNA_intervals += [(int_start - new_sel_cols_start - 1,int_end - new_sel_cols_start - 1)]
 
             fig_filename_root = fig_filename_root+"_"+ref_name+"_"+sgRNA_label
-            plot_alleles_table(ref_seq_around_cut,df_alleles=df_alleles_around_cut,fig_filename_root=fig_filename_root,cut_point_ind=cut_point-new_sel_cols_start, MIN_FREQUENCY=MIN_FREQUENCY,MAX_N_ROWS=MAX_N_ROWS,SAVE_ALSO_PNG=SAVE_ALSO_PNG,plot_cut_point=plot_cut_point,sgRNA_intervals=new_sgRNA_intervals,sgRNA_names=sgRNA_names,sgRNA_mismatches=sgRNA_mismatches,annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
+            CRISPRessoPlot.plot_alleles_table(ref_seq_around_cut,
+                                              df_alleles=df_alleles_around_cut,
+                                              fig_filename_root=fig_filename_root,
+                                              cut_point_ind=cut_point-new_sel_cols_start,
+                                              custom_colors=custom_colors,
+                                              MIN_FREQUENCY=MIN_FREQUENCY,
+                                              MAX_N_ROWS=MAX_N_ROWS,
+                                              SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                                              plot_cut_point=plot_cut_point,
+                                              sgRNA_intervals=new_sgRNA_intervals,
+                                              sgRNA_names=sgRNA_names,
+                                              sgRNA_mismatches=sgRNA_mismatches,
+                                              annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
 
             plot_count += 1
         else:
@@ -140,7 +159,7 @@ def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,p
                 plot_idxs = sgRNA_plot_idxs[ind]
                 ref_seq_around_cut=refs[ref_name]['sequence'][cut_point-plot_left+1:cut_point+plot_right+1]
 
-                df_alleles_around_cut=get_dataframe_around_cut_assymetrical(df_alleles, cut_point, plot_left, plot_right)
+                df_alleles_around_cut=get_dataframe_around_cut_asymmetrical(df_alleles, cut_point, plot_left, plot_right)
                 this_allele_count = len(df_alleles_around_cut.index)
                 if this_allele_count < 1:
                     print('No reads found for ' + ref_name)
@@ -155,209 +174,23 @@ def plot_alleles_tables_from_folder(crispresso_output_folder,fig_filename_root,p
                     new_sgRNA_intervals += [(int_start - new_sel_cols_start - 1,int_end - new_sel_cols_start - 1)]
 
                 fig_filename_root = fig_filename_root+"_"+ref_name+"_"+sgRNA_label
-                plot_alleles_table(ref_seq_around_cut,df_alleles=df_alleles_around_cut,fig_filename_root=fig_filename_root,cut_point_ind=cut_point-new_sel_cols_start, MIN_FREQUENCY=MIN_FREQUENCY,MAX_N_ROWS=MAX_N_ROWS,SAVE_ALSO_PNG=SAVE_ALSO_PNG,plot_cut_point=plot_cut_point,sgRNA_intervals=new_sgRNA_intervals,sgRNA_names=sgRNA_names,sgRNA_mismatches=sgRNA_mismatches,annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
+                CRISPRessoPlot.plot_alleles_table(ref_seq_around_cut,
+                                                  df_alleles=df_alleles_around_cut,
+                                                  fig_filename_root=fig_filename_root,
+                                                  cut_point_ind=cut_point-new_sel_cols_start,
+                                                  custom_colors=custom_colors,
+                                                  MIN_FREQUENCY=MIN_FREQUENCY,
+                                                  MAX_N_ROWS=MAX_N_ROWS,
+                                                  SAVE_ALSO_PNG=SAVE_ALSO_PNG,
+                                                  plot_cut_point=plot_cut_point,
+                                                  sgRNA_intervals=new_sgRNA_intervals,
+                                                  sgRNA_names=sgRNA_names,
+                                                  sgRNA_mismatches=sgRNA_mismatches,
+                                                  annotate_wildtype_allele=crispresso2_info['running_info']['args'].annotate_wildtype_allele)
 
                 plot_count += 1
     print('Plotted ' + str(plot_count) + ' plots')
 
-def plot_alleles_table(reference_seq,df_alleles,fig_filename_root,MIN_FREQUENCY=0.5,MAX_N_ROWS=100,SAVE_ALSO_PNG=False,plot_cut_point=True,cut_point_ind=None,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None,annotate_wildtype_allele='****',):
-    """
-    plots an allele table for a dataframe with allele frequencies
-    input:
-    reference_seq: the reference amplicon sequence to plot
-    df_alleles: merged dataframe (should include columns "#Reads','%Reads')
-    fig_filename: figure filename to plot (not including '.pdf' or '.png')
-    MIN_FREQUENCY: sum of alleles % must add to this to be plotted
-    MAX_N_ROWS: max rows to plot
-    SAVE_ALSO_PNG: whether to write png file as well
-    plot_cut_point: if false, won't draw 'predicted cleavage' line
-    cut_point_ind: index to plot cut point at
-    sgRNA_intervals: locations where sgRNA is located
-    sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
-    sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
-    custom_colors: dict of colors to plot (e.g. colors['A'] = (1,0,0,0.4) # red,blue,green,alpha )
-    annotate_wildtype_allele: string to add to the end of the wildtype allele (e.g. ** or '')
-    """
-    X,annot,y_labels,insertion_dict,per_element_annot_kws,is_reference = CRISPRessoPlot.prep_alleles_table(df_alleles,reference_seq,MAX_N_ROWS,MIN_FREQUENCY)
-    if annotate_wildtype_allele != '':
-        for ix, is_ref in enumerate(is_reference):
-            if is_ref:
-                y_labels[ix] += annotate_wildtype_allele
-    plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insertion_dict,per_element_annot_kws,SAVE_ALSO_PNG,plot_cut_point,cut_point_ind,sgRNA_intervals,sgRNA_names,sgRNA_mismatches,custom_colors)
-
-def plot_alleles_heatmap(reference_seq,fig_filename_root,X,annot,y_labels,insertion_dict,per_element_annot_kws,SAVE_ALSO_PNG=False,plot_cut_point=True,cut_point_ind=None,sgRNA_intervals=None,sgRNA_names=None,sgRNA_mismatches=None,custom_colors=None):
-    """
-    Plots alleles in a heatmap (nucleotides color-coded for easy visualization)
-    input:
-    -reference_seq: sequence of reference allele to plot
-    -fig_filename: figure filename to plot (not including '.pdf' or '.png')
-    -X: list of numbers representing nucleotides of the allele
-    -annot: list of nucleotides (letters) of the allele
-    -y_labels: list of labels for each row/allele
-    -insertion_dict: locations of insertions -- red squares will be drawn around these
-    -per_element_annot_kws: annotations for each cell (e.g. bold for substitutions, etc.)
-    -SAVE_ALSO_PNG: whether to write png file as well
-    -plot_cut_point: if false, won't draw 'predicted cleavage' line
-    -cut_point_ind: index to plot cut point at
-    -sgRNA_intervals: locations where sgRNA is located
-    -sgRNA_mismatches: array (for each sgRNA_interval) of locations in sgRNA where there are mismatches
-    -sgRNA_names: array (for each sgRNA_interval) of names of sgRNAs (otherwise empty)
-    -custom_colors: dict of colors to plot (e.g. colors['A'] = (1,0,0,0.4) # red,blue,green,alpha )
-    """
-    plot_nuc_len=len(reference_seq)
-
-    # make a color map of fixed colors
-    alpha=0.4
-    A_color=CRISPRessoPlot.get_nuc_color('A',alpha)
-    T_color=CRISPRessoPlot.get_nuc_color('T',alpha)
-    C_color=CRISPRessoPlot.get_nuc_color('C',alpha)
-    G_color=CRISPRessoPlot.get_nuc_color('G',alpha)
-    INDEL_color = CRISPRessoPlot.get_nuc_color('N',alpha)
-
-    if custom_colors is not None:
-        if 'A' in custom_colors:
-            A_color = custom_colors['A']
-        if 'T' in custom_colors:
-            T_color = custom_colors['T']
-        if 'C' in custom_colors:
-            C_color = custom_colors['C']
-        if 'G' in custom_colors:
-            G_color = custom_colors['G']
-        if 'N' in custom_colors:
-            INDEL_color = custom_colors['N']
-
-    dna_to_numbers={'-':0,'A':1,'T':2,'C':3,'G':4,'N':5}
-    seq_to_numbers= lambda seq: [dna_to_numbers[x] for x in seq]
-
-    cmap = colors_mpl.ListedColormap([INDEL_color, A_color,T_color,C_color,G_color,INDEL_color])
-
-    #ref_seq_around_cut=reference_seq[max(0,cut_point-plot_nuc_len/2+1):min(len(reference_seq),cut_point+plot_nuc_len/2+1)]
-
-#    print('per element anoot kws: ' + per_element_annot_kws)
-    if len(per_element_annot_kws) > 1:
-        per_element_annot_kws=np.vstack(per_element_annot_kws[::-1])
-    else:
-        per_element_annot_kws=np.array(per_element_annot_kws)
-    ref_seq_hm=np.expand_dims(seq_to_numbers(reference_seq),1).T
-    ref_seq_annot_hm=np.expand_dims(list(reference_seq),1).T
-
-    annot=annot[::-1]
-    X=X[::-1]
-
-    N_ROWS=len(X)
-    N_COLUMNS=plot_nuc_len
-
-    if N_ROWS < 1:
-        fig=plt.figure()
-        ax = fig.add_subplot(111)
-        plt.text(0.5, 0.5,'No Alleles',horizontalalignment='center',verticalalignment='center',transform = ax.transAxes)
-        ax.set_clip_on(False)
-
-        plt.savefig(fig_filename_root+'.pdf',bbox_inches='tight')
-        if SAVE_ALSO_PNG:
-            plt.savefig(fig_filename_root+'.png',bbox_inches='tight')
-        plt.close()
-        return
-
-    sgRNA_rows = []
-    num_sgRNA_rows = 0
-
-    if sgRNA_intervals and len(sgRNA_intervals) > 0:
-        sgRNA_rows = CRISPRessoPlot.get_rows_for_sgRNA_annotation(sgRNA_intervals,plot_nuc_len)
-        num_sgRNA_rows = max(sgRNA_rows) + 1
-        fig=plt.figure(figsize=(plot_nuc_len*0.3,(N_ROWS+1 + num_sgRNA_rows)*0.6))
-        gs1 = gridspec.GridSpec(N_ROWS+2,N_COLUMNS)
-        gs2 = gridspec.GridSpec(N_ROWS+2,N_COLUMNS)
-        #ax_hm_ref heatmap for the reference
-        ax_hm_ref=plt.subplot(gs1[0:1, :])
-        ax_hm=plt.subplot(gs2[2:, :])
-    else:
-        fig=plt.figure(figsize=(plot_nuc_len*0.3,(N_ROWS+1)*0.6))
-        gs1 = gridspec.GridSpec(N_ROWS+1,N_COLUMNS)
-        gs2 = gridspec.GridSpec(N_ROWS+1,N_COLUMNS)
-        #ax_hm_ref heatmap for the reference
-        ax_hm_ref=plt.subplot(gs1[0, :])
-        ax_hm=plt.subplot(gs2[1:, :])
-
-
-    CRISPRessoPlot.custom_heatmap(ref_seq_hm,annot=ref_seq_annot_hm,annot_kws={'size':16},cmap=cmap,fmt='s',ax=ax_hm_ref,vmin=0,vmax=5,square=True)
-    CRISPRessoPlot.custom_heatmap(X,annot=np.array(annot),annot_kws={'size':16},cmap=cmap,fmt='s',ax=ax_hm,vmin=0,vmax=5,square=True, per_element_annot_kws=per_element_annot_kws)
-
-    ax_hm.yaxis.tick_right()
-    ax_hm.yaxis.set_ticklabels(y_labels[::-1],rotation=True,va='center')
-    ax_hm.xaxis.set_ticks([])
-
-    if sgRNA_intervals and len(sgRNA_intervals) > 0:
-        this_sgRNA_y_start = -1*num_sgRNA_rows
-        this_sgRNA_y_height = num_sgRNA_rows - 0.3
-        CRISPRessoPlot.add_sgRNA_to_ax(ax_hm_ref,sgRNA_intervals,sgRNA_y_start=this_sgRNA_y_start,sgRNA_y_height=this_sgRNA_y_height,amp_len=plot_nuc_len,font_size='small',clip_on=False,sgRNA_names=sgRNA_names,sgRNA_mismatches=sgRNA_mismatches,x_offset=0,label_at_zero=True,sgRNA_rows=sgRNA_rows)
-
-# todo -- add sgRNAs below reference plot
-#    if sgRNA_intervals:
-#        ax_hm_anno=plt.subplot(gs3[2, :])
-#        sgRNA_y_start = 0.3
-##        sgRNA_y_height = 0.1
-#        sgRNA_y_height = 10
-#        min_sgRNA_x = None
-#        for idx,sgRNA_int in enumerate(sgRNA_intervals):
-#            ax_hm_anno.add_patch(
-#                patches.Rectangle((2+sgRNA_int[0], sgRNA_y_start), 1+sgRNA_int[1]-sgRNA_int[0], sgRNA_y_height,facecolor=(0,0,0,0.15))
-#                )
-#            #set left-most sgrna start
-#            if not min_sgRNA_x:
-#                min_sgRNA_x = sgRNA_int[0]
-#            if sgRNA_int[0] < min_sgRNA_x:
-#                min_sgRNA_x = sgRNA_int[0]
-#        ax_hm_anno.text(2+min_sgRNA_x,sgRNA_y_start + sgRNA_y_height/2,'sgRNA ',horizontalalignment='right',verticalalignment='center')
-
-    #print lines
-
-
-    #create boxes for ins
-    for idx,lss in insertion_dict.items():
-        for ls in lss:
-            ax_hm.add_patch(patches.Rectangle((ls[0],N_ROWS-idx-1),ls[1]-ls[0],1,linewidth=3,edgecolor='r',fill=False))
-
-    #cut point vertical line
-    if plot_cut_point:
-        if cut_point_ind is None:
-            ax_hm.vlines([plot_nuc_len/2],*ax_hm.get_ylim(),linestyles='dashed')
-        else:
-            ax_hm.vlines(cut_point_ind,*ax_hm.get_ylim(),linestyles='dashed')
-
-    ax_hm_ref.yaxis.tick_right()
-    ax_hm_ref.xaxis.set_ticks([])
-    ax_hm_ref.yaxis.set_ticklabels(['Reference'],rotation=True,va='center')
-
-
-
-    gs2.update(left=0,right=1, hspace=0.05,wspace=0,top=1*(((N_ROWS)*1.13))/(N_ROWS))
-    gs1.update(left=0,right=1, hspace=0.05,wspace=0,)
-
-    sns.set_context(rc={'axes.facecolor':'white','lines.markeredgewidth': 1,'mathtext.fontset' : 'stix','text.usetex':True,'text.latex.unicode':True} )
-
-    proxies = [matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='black',
-                    mec='none', marker=r'$\mathbf{{{}}}$'.format('bold'),ms=18),
-               matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='none',
-                    mec='r', marker='s',ms=8,markeredgewidth=2.5),
-              matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='none',
-                    mec='black', marker='_',ms=2,)]
-    descriptions=['Substitutions','Insertions','Deletions']
-
-    if plot_cut_point:
-        proxies.append(
-              matplotlib.lines.Line2D([0], [1], linestyle='--',c='black',ms=6))
-        descriptions.append('Predicted cleavage position')
-
-    #ax_hm_ref.legend(proxies, descriptions, numpoints=1, markerscale=2, loc='center', bbox_to_anchor=(0.5, 4),ncol=1)
-    lgd = ax_hm.legend(proxies, descriptions, numpoints=1, markerscale=2, loc='upper center', bbox_to_anchor=(0.5, 0),ncol=1,fancybox=True,shadow=False)
-
-    plt.savefig(fig_filename_root+'.pdf',bbox_inches='tight',bbox_extra_artists=(lgd,))
-    if SAVE_ALSO_PNG:
-        plt.savefig(fig_filename_root+'.png',bbox_inches='tight',bbox_extra_artists=(lgd,))
-    plt.close()
-
-
 if __name__ == "__main__":
     # execute only if run as a script
     main()
diff --git a/setup.py b/setup.py
index cba9ac76..6dda62ab 100644
--- a/setup.py
+++ b/setup.py
@@ -70,7 +70,7 @@ def main():
           url='http://github.com/pinellolab/CRISPResso2',
           package_dir={'CRISPResso2' : 'CRISPResso2'},
           include_package_data = True,
-          packages=['CRISPResso2'],
+          packages=['CRISPResso2', 'CRISPResso2.CRISPRessoReports'],
           entry_points=entry_points,
           description="Software pipeline for the analysis of genome editing outcomes from deep sequencing data",
           classifiers=[
@@ -91,7 +91,6 @@ def main():
               'jinja2',
               'scipy',
               'numpy',
-              'plotly',
               ],
           cmdclass = command_classes,
           ext_modules = ext_modules
diff --git a/tests/unit_tests/test_CRISPRessoBatchCORE.py b/tests/unit_tests/test_CRISPRessoBatchCORE.py
new file mode 100644
index 00000000..ab57b4e0
--- /dev/null
+++ b/tests/unit_tests/test_CRISPRessoBatchCORE.py
@@ -0,0 +1,44 @@
+from CRISPResso2 import CRISPRessoBatchCORE
+
+
+
+def test_should_plot_large_plots():
+    num_rows = 60
+    c2pro_installed = False
+    use_matplotlib = False
+    large_plot_cutoff = 300
+    assert CRISPRessoBatchCORE.should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff)
+
+
+def test_should_plot_large_plots_c2pro_installed_use_matplotlib_small():
+    num_rows = 60
+    c2pro_installed = True
+    use_matplotlib = True
+    large_plot_cutoff = 300
+    assert CRISPRessoBatchCORE.should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff)
+
+
+def test_should_plot_large_plots_c2pro_installed():
+    num_rows = 6000
+    c2pro_installed = True
+    use_matplotlib = False
+    large_plot_cutoff = 300
+    assert CRISPRessoBatchCORE.should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff)
+
+
+def test_should_plot_large_plots_c2pro_installed_use_matplotlib_large():
+    num_rows = 6000
+    c2pro_installed = True
+    use_matplotlib = True
+    large_plot_cutoff = 300
+    assert not CRISPRessoBatchCORE.should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff)
+
+
+def test_should_plot_large_plots_c2pro_not_installed_use_matplotlib():
+    num_rows = 6000
+    c2pro_installed = False
+    use_matplotlib = True
+    large_plot_cutoff = 300
+    assert not CRISPRessoBatchCORE.should_plot_large_plots(num_rows, c2pro_installed, use_matplotlib, large_plot_cutoff)
+
+
diff --git a/tests/unit_tests/test_CRISPRessoCORE.py b/tests/unit_tests/test_CRISPRessoCORE.py
index 268b6727..493f5d79 100644
--- a/tests/unit_tests/test_CRISPRessoCORE.py
+++ b/tests/unit_tests/test_CRISPRessoCORE.py
@@ -1,7 +1,7 @@
 """Unit tests for CRISPResso2CORE."""
 import pytest
 
-from CRISPResso2 import CRISPRessoCORE
+from CRISPResso2 import CRISPRessoCORE, CRISPRessoShared
 
 def test_get_consensus_alignment_from_pairs():
     """Tests for generating consensus alignments from paired reads."""
@@ -93,6 +93,180 @@ def test_get_consensus_alignment_from_pairs():
     assert ref_seq ==      "ATCGATCGAT"
     assert score == 50 #double check this score... should be 5/10
 
+
+def test_split_quant_window_coordinates_single():
+    assert [(5, 10)] == CRISPRessoCORE.split_quant_window_coordinates('5-10')
+
+
+def test_split_quant_window_coordinates_multiple():
+    assert CRISPRessoCORE.split_quant_window_coordinates('2-5_10-12') == [(2, 5), (10, 12)]
+
+
+def test_split_quant_window_coordinates_error():
+    with pytest.raises(CRISPRessoShared.BadParameterException):
+        CRISPRessoCORE.split_quant_window_coordinates('a-5')
+
+
+def test_split_quant_window_coordinates_empty():
+    with pytest.raises(CRISPRessoShared.BadParameterException):
+        CRISPRessoCORE.split_quant_window_coordinates('_')
+
+
+def test_split_quant_window_coordinates_partially_empty():
+    with pytest.raises(CRISPRessoShared.BadParameterException):
+        CRISPRessoCORE.split_quant_window_coordinates('1-3_')
+
+
+def test_split_quant_window_coordinates_blank():
+    with pytest.raises(CRISPRessoShared.BadParameterException):
+        CRISPRessoCORE.split_quant_window_coordinates('')
+
+
+def test_get_include_idxs_from_quant_window_coordinates():
+    quant_window_coordinates = '1-10_12-20'
+    assert CRISPRessoCORE.get_include_idxs_from_quant_window_coordinates(quant_window_coordinates) == [*list(range(1, 11)), *list(range(12, 21))]
+
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates():
+    quant_window_coordinates = '1-10_12-20'
+    s1inds = list(range(22))
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(1, 11)), *list(range(12, 21))]
+
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_insertion_beginning():
+    quant_window_coordinates = '1-10_12-20'
+    # represents a 5bp insertion at the beginning (left)
+    s1inds = list(range(5, 27))
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(6, 16)), *list(range(17, 26))]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_deletion_beginning():
+    quant_window_coordinates = '1-10_12-20'
+    # represents a 5bp deletion at the beginning (left)
+    s1inds = [-1, -1, -1, -1, -1 ] + list(range(26))
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(0, 6)), *list(range(7, 16))]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_deletion():
+    quant_window_coordinates = '10-20_35-40'
+    # represents a 7bp deletion in the middle
+    s1inds = list(range(23)) + [22, 22, 22, 22, 22, 22, 22] + list(range(23, 34))
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(10, 21)), *list(range(35-7, 41-7))]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_deletion_modified():
+    quant_window_coordinates = '10-25_35-40'
+    # represents a 7bp deletion in the middle, where part of the QW is deleted
+    # [0, 1, 3, 4, ... , 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 24, ... , 33]
+    s1inds = list(range(23)) + [22, 22, 22, 22, 22, 22, 22] + list(range(23, 34))
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(10, 23)), *list(range(35-7, 41-7))]
+
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_deletion_end_modified(): 
+    # 5 bp deletion at end of 20 bp sequence
+    quant_window_coordinates = '1-5_10-20'
+    s1inds = [*list(range(16)), *[15, 15, 15, 15, 15]]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(1, 6)), *list(range(10, 16))]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_insertion_and_deletion():
+    # 5 bp deletion and 5 bp insertion
+    quant_window_coordinates = '1-5_10-20'
+    s1inds = [0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 15, 16, 17, 18, 19, 20]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*list(range(1, 6)), *[6, 7, 8, 9, 15, 16, 17, 18, 19, 20]]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_insertion_and_deletion_modified():
+    quant_window_coordinates = '1-5_10-20'
+    s1inds = [0, 1, 2, 2, 4, 5, 6, 7, 7, 7, 7, 7, 7, 8, 9, 10, 15, 16, 17, 18, 19]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [*[1,2,4,5], *[8, 9, 10, 15, 16, 17, 18, 19]]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_insertion_across_qw():
+    # 6 bp insertion in middle of 4 bp sequence
+    quant_window_coordinates = '1-4'
+    s1inds = [0,1,2,9,10]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [1,2,9,10]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_deletion_entire_qw():
+    # 5 bp deletion of entire qw
+    quant_window_coordinates = '1-4_7-10'
+    s1inds = [0, 1, 2, 3, 4, 5, 6, 6, 6, 6, 6]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [1, 2, 3, 4]
+
+def test_get_cloned_include_idxs_from_quant_window_coordinates_include_zero():
+    quant_window_coordinates = '0-5'
+    s1inds = [0, 1, 2, 3, 4, 5]
+    assert CRISPRessoCORE.get_cloned_include_idxs_from_quant_window_coordinates(quant_window_coordinates, s1inds) == [0, 1, 2, 3, 4, 5]
+
+
+# Testing parallelization functions
+def test_regular_input():
+    # Test with typical input
+    assert CRISPRessoCORE.get_variant_cache_equal_boundaries(100, 4) == [0, 25, 50, 75, 100]
+
+def test_remainder_input():
+#     # Test with typical input
+    assert CRISPRessoCORE.get_variant_cache_equal_boundaries(101, 4) == [0, 25, 50, 75, 101]
+
+def test_similar_num_reads_input():
+#     # Test with typical input
+    assert CRISPRessoCORE.get_variant_cache_equal_boundaries(11, 10) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
+
+def test_large_similar_num_reads_input():
+#     # Test with typical input
+    assert CRISPRessoCORE.get_variant_cache_equal_boundaries(101, 100) == list(range(0, 100)) + [101]
+
+def test_more_processes_than_reads():
+#     # Test with typical input
+    # assert CRISPRessoCORE.get_variant_cache_equal_boundaries(3, 5) 
+    # assert that an exception is raised
+    with pytest.raises(Exception):
+        CRISPRessoCORE.get_variant_cache_equal_boundaries(3, 5)
+
+def test_single_process():
+    # Test with a single process
+    assert CRISPRessoCORE.get_variant_cache_equal_boundaries(50, 1) == [0, 50]
+
+def test_zero_sequences():
+    # Test with zero unique sequences
+    with pytest.raises(Exception):
+        CRISPRessoCORE.get_variant_cache_equal_boundaries(0, 3)
+
+def test_large_numbers():
+    # Test with large number of processes and sequences
+    boundaries = CRISPRessoCORE.get_variant_cache_equal_boundaries(10000, 10)
+    assert len(boundaries) == 11  # Check that there are 11 boundaries
+
+def test_sublist_generation():
+    n_processes = 4
+    unique_reads = 100
+    mock_variant_cache = [i for i in range(unique_reads)]
+    assert len(mock_variant_cache) == 100
+    boundaries = CRISPRessoCORE.get_variant_cache_equal_boundaries(unique_reads, n_processes) 
+    assert boundaries == [0, 25, 50, 75, 100]
+    sublists = []
+    for i in range(n_processes):
+        left_sublist_index = boundaries[i]
+        right_sublist_index = boundaries[i+1]
+        sublist = mock_variant_cache[left_sublist_index:right_sublist_index]
+        sublists.append(sublist)
+    assert [len(sublist) for sublist in sublists] == [25, 25, 25, 25]
+    assert [s for sublist in sublists for s in sublist] == mock_variant_cache
+
+def test_irregular_sublist_generation():
+    n_processes = 4
+    unique_reads = 113
+    mock_variant_cache = [i for i in range(unique_reads)]
+    assert len(mock_variant_cache) == 113
+    boundaries = CRISPRessoCORE.get_variant_cache_equal_boundaries(unique_reads, n_processes) 
+    # assert boundaries == [0, 25, 50, 75, 100]
+    sublists = []
+    for i in range(n_processes):
+        left_sublist_index = boundaries[i]
+        right_sublist_index = boundaries[i+1]
+        sublist = mock_variant_cache[left_sublist_index:right_sublist_index]
+        sublists.append(sublist)
+    assert [len(sublist) for sublist in sublists] == [28,28,28,29]
+    assert [s for sublist in sublists for s in sublist] == mock_variant_cache
+
+
+
+    
 if __name__ == "__main__":
 # execute only if run as a script
     test_get_consensus_alignment_from_pairs()
diff --git a/tests/unit_tests/test_CRISPRessoCOREResources.py b/tests/unit_tests/test_CRISPRessoCOREResources.py
index 17b6f801..e479a0a5 100644
--- a/tests/unit_tests/test_CRISPRessoCOREResources.py
+++ b/tests/unit_tests/test_CRISPRessoCOREResources.py
@@ -31,6 +31,7 @@ def test_find_indels_substitutions():
     assert payload['substitution_n'] == 0
     assert payload['deletion_n'] == 3
     assert payload['all_deletion_positions'] == [18, 19, 20]
+    assert payload['all_deletion_coordinates'] == [(18, 21)]
     assert payload['deletion_positions'] == [18, 19, 20]
     assert payload['deletion_coordinates'] == [(18, 21)]
 
@@ -90,6 +91,7 @@ def test_find_indels_substitutions():
         'insertion_sizes': [12],
         'insertion_n': 12,
         'all_deletion_positions': [101, 116, 117, 132, 133, 134, 135, 136],
+        'all_deletion_coordinates': [(101, 102), (116, 118), (132, 137)],
         'deletion_positions': [],
         'deletion_coordinates': [],
         'deletion_sizes': [],
@@ -106,6 +108,7 @@ def test_find_indels_substitutions():
     assert payload['insertion_sizes'] == correct_payload['insertion_sizes']
     assert payload['insertion_n'] == correct_payload['insertion_n']
     assert payload['all_deletion_positions'] == correct_payload['all_deletion_positions']
+    assert payload['all_deletion_coordinates'] == correct_payload['all_deletion_coordinates']
     assert payload['deletion_positions'] == correct_payload['deletion_positions']
     assert payload['deletion_sizes'] == correct_payload['deletion_sizes']
     assert payload['deletion_n'] == correct_payload['deletion_n']
@@ -129,6 +132,7 @@ def test_find_indels_substitutions():
         'insertion_sizes': [54],
         'insertion_n': 54,
         'all_deletion_positions': [191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221],
+        'all_deletion_coordinates': [(191, 222)],
         'deletion_positions': [],
         'deletion_coordinates': [],
         'deletion_sizes': [],
@@ -145,6 +149,48 @@ def test_find_indels_substitutions():
     assert payload['insertion_sizes'] == correct_payload['insertion_sizes']
     assert payload['insertion_n'] == correct_payload['insertion_n']
     assert payload['all_deletion_positions'] == correct_payload['all_deletion_positions']
+    assert payload['all_deletion_coordinates'] == correct_payload['all_deletion_coordinates']
+    assert payload['deletion_positions'] == correct_payload['deletion_positions']
+    assert payload['deletion_sizes'] == correct_payload['deletion_sizes']
+    assert payload['deletion_n'] == correct_payload['deletion_n']
+    assert payload['all_substitution_positions'] == correct_payload['all_substitution_positions']
+    assert payload['substitution_positions'] == correct_payload['substitution_positions']
+    assert payload['substitution_n'] == correct_payload['substitution_n']
+    assert payload['ref_positions'] == correct_payload['ref_positions']
+
+
+def test_find_indels_substitutions_legacy():
+    payload = CRISPRessoCOREResources.find_indels_substitutions_legacy(
+        'TAATCGGATGTTCCAATCAGTACGCAGAGAGTCGCCGTCTCCAAGGTGAAAGCGGAAGTAGGGCCTTCGCGCACCTCATGGAATCCCTTCTGCAAGAGGGCGGCTTTGGGCGGGGTC-CAGTTCCGGGATTA--GCGAACTTAGAGCAC-----ACGTCTGAACTCCAGTCACCGATGTATATCTCGTATGCCGTCTTCTGCTTGAAAAAAAAAAACTTACTCTCACTTAACTCTTGCTTCCCTCCTGACGCCGATG',
+        '----CGGATGTTCCAATCAGTACGCAGAGAGTCGCCGTCTCCAAGGTGAAAGCGGAAGTAGGGCCTTCGCGCACCTCATGGAATCCCTTCTGCAGC------------ACCTGGATCGCTTTTCCGAGCTTCTGGCGGTCTCA-AGCACTACCTACGTCAGCACCTGGGACCCCGCCAC------CGTGCGCCGGGC----CTTGCAGTGGGCGCGCTACCTGCGCCACATCCATCGG--CGCTTTGGTCGG-----',
+        [91, 92],
+    )
+    correct_payload = {
+        'all_insertion_positions': [91, 92, 126, 127, 161, 162, 173, 174, 210, 211],
+        'all_insertion_left_positions': [91, 126, 161, 173, 210],
+        'insertion_positions': [91, 92],
+        'insertion_coordinates': [(91, 92)],
+        'insertion_sizes': [12],
+        'insertion_n': 12,
+        'all_deletion_positions': [101, 116, 117, 132, 133, 134, 135, 136],
+        'all_deletion_coordinates': [(101, 102), (116, 118), (132, 137)],
+        'deletion_positions': [],
+        'deletion_coordinates': [],
+        'deletion_sizes': [],
+        'deletion_n': 0.0,
+        'all_substitution_positions': [90, 91, 92, 93, 95, 98, 103, 104, 110, 112, 115, 121, 122, 125, 142, 144, 147, 148, 149, 150, 152, 154, 158, 159, 160, 161, 165, 166, 171, 172, 173, 178, 180, 181, 182, 183, 184, 185, 186, 187, 188, 191, 192, 195, 196, 197, 198, 200, 201, 203, 205, 206, 208, 210, 212, 215, 216, 217, 219, 222],
+        'substitution_positions': [91, 92],
+        'substitution_n': 2,
+        'ref_positions': [-1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, -92, -92, -92, -92, -92, -92, -92, -92, -92, -92, -92, -92, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, -127, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, -162, -162, -162, -162, -162, -162, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, -174, -174, -174, -174, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, -211, -211, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, -223, -223, -223, -223, -223],
+    }
+    assert payload['all_insertion_positions'] == correct_payload['all_insertion_positions']
+    assert payload['all_insertion_left_positions'] == correct_payload['all_insertion_left_positions']
+    assert payload['insertion_positions'] == correct_payload['insertion_positions']
+    assert payload['insertion_coordinates'] == correct_payload['insertion_coordinates']
+    assert payload['insertion_sizes'] == correct_payload['insertion_sizes']
+    assert payload['insertion_n'] == correct_payload['insertion_n']
+    assert payload['all_deletion_positions'] == correct_payload['all_deletion_positions']
+    assert payload['all_deletion_coordinates'] == correct_payload['all_deletion_coordinates']
     assert payload['deletion_positions'] == correct_payload['deletion_positions']
     assert payload['deletion_sizes'] == correct_payload['deletion_sizes']
     assert payload['deletion_n'] == correct_payload['deletion_n']
diff --git a/tests/unit_tests/test_CRISPRessoCompareCORE.py b/tests/unit_tests/test_CRISPRessoCompareCORE.py
new file mode 100644
index 00000000..6d84019f
--- /dev/null
+++ b/tests/unit_tests/test_CRISPRessoCompareCORE.py
@@ -0,0 +1,85 @@
+"""Unit tests for CRISPRessoCompareCORE."""
+
+from CRISPResso2 import CRISPRessoCompareCORE
+
+from copy import deepcopy
+import pytest
+
+
+@pytest.fixture(scope='function')
+def run_info():
+    return {
+        'results': {
+            'refs': {
+                'Reference': {
+                    'sequence':'CGGATGTTCCAATCAGTACGCAGAGAGTCGCCGTCTCCAAGGTGAAAGCGGAAGTAGGGCCTTCGCGCACCTCATGGAATCCCTTCTGCAGCACCTGGATCGCTTTTCCGAGCTTCTGGCGGTCTCAAGCACTACCTACGTCAGCACCTGGGACCCCGCCACCGTGCGCCGGGCCTTGCAGTGGGCGCGCTACCTGCGCCACATCCATCGGCGCTTTGGTCGG',
+                    'sgRNA_orig_sequences': ['GGCCCTTAAAA'],
+                    'sgRNA_cut_points': [50],
+                    'allele_frequency_files': ['Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt'],
+                },
+            },
+        },
+    }
+
+
+@pytest.fixture(scope='function')
+def run_info_1(run_info):
+    return deepcopy(run_info)
+
+
+@pytest.fixture(scope='function')
+def run_info_2(run_info):
+    return deepcopy(run_info)
+
+
+def test_get_matching_allele_files(run_info):
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info, run_info)
+    assert matching_allele_files == [('Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt', 'Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt')]
+
+
+def test_get_matching_allele_files_different_cut_points(run_info_1, run_info_2):
+    run_info_2['results']['refs']['Reference']['sgRNA_cut_points'] = [50, 51]
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == []
+
+
+def test_get_matching_allele_files_different_guides(run_info_1, run_info_2):
+    run_info_2['results']['refs']['Reference']['sgRNA_orig_sequences'] = ['GGCCCTTAAAC']
+    run_info_2['results']['refs']['Reference']['allele_frequency_files'] = ['Alleles_frequency_table_around_sgRNA_GGCCCTTAAAC.txt']
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == []
+
+
+def test_get_matching_allele_files_multiple_alleles(run_info_1, run_info_2):
+    run_info_1['results']['refs']['Other_Amplicon'] = deepcopy(run_info_1['results']['refs']['Reference'])
+    run_info_1['results']['refs']['Other_Amplicon']['sequence'] = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+    run_info_1['results']['refs']['Other_Amplicon']['allele_frequency_files'] = ['Other_Amplicon.Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt']
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == [('Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt', 'Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt')]
+
+
+def test_get_matching_allele_files_different_amplicon_names_same_sequence(run_info_1, run_info_2):
+    run_info_2['results']['refs']['Other_Amplicon'] = deepcopy(run_info_1['results']['refs']['Reference'])
+    del run_info_2['results']['refs']['Reference']
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == [('Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt', 'Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt')]
+
+
+def test_get_matching_allele_files_some_different_guides(run_info_1, run_info_2):
+    run_info_1['results']['refs']['Reference']['sgRNA_orig_sequences'] += ['AAAAAAAAAAAAAAAAAAA']
+    run_info_1['results']['refs']['Reference']['allele_frequency_files'] += ['Alleles_frequency_table_around_sgRNA_AAAAAAAAAAAAAAAAAAA.txt']
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == [('Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt', 'Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt')]
+
+
+def test_get_matching_allele_files_multiple_guides(run_info_1, run_info_2):
+    run_info_1['results']['refs']['Reference']['sgRNA_orig_sequences'] += ['AAAAAAAAAAAAAAAAAAA']
+    run_info_1['results']['refs']['Reference']['allele_frequency_files'] += ['Alleles_frequency_table_around_sgRNA_AAAAAAAAAAAAAAAAAAA.txt']
+    run_info_2['results']['refs']['Reference']['sgRNA_orig_sequences'] += ['AAAAAAAAAAAAAAAAAAA']
+    run_info_2['results']['refs']['Reference']['allele_frequency_files'] += ['Alleles_frequency_table_around_sgRNA_AAAAAAAAAAAAAAAAAAA.txt']
+    matching_allele_files = CRISPRessoCompareCORE.get_matching_allele_files(run_info_1, run_info_2)
+    assert matching_allele_files == [
+        ('Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt', 'Alleles_frequency_table_around_sgRNA_GGCCCTTAAAA.txt'),
+        ('Alleles_frequency_table_around_sgRNA_AAAAAAAAAAAAAAAAAAA.txt', 'Alleles_frequency_table_around_sgRNA_AAAAAAAAAAAAAAAAAAA.txt'),
+    ]
+
diff --git a/tests/unit_tests/test_CRISPRessoShared.py b/tests/unit_tests/test_CRISPRessoShared.py
index da1bc479..9916f8a2 100644
--- a/tests/unit_tests/test_CRISPRessoShared.py
+++ b/tests/unit_tests/test_CRISPRessoShared.py
@@ -21,3 +21,80 @@ def test_get_mismatches():
         -3,
     )
     assert len(mismatch_cords) == 6
+
+def test_get_relative_coordinates():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('ATCGT', 'TTCGT')
+    assert s1inds_gap_left == [0, 1, 2, 3, 4]
+    assert s1inds_gap_right == [0, 1, 2, 3, 4]
+
+
+def test_get_relative_coordinates_to_gap():
+    # unaligned sequences
+    seq_1 = 'TTCGT'
+    seq_2 = 'TTCT'
+
+    # aligned_sequences
+    to_sequence = 'TTC-T'
+    from_sequence = 'TTCGT'
+
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates(to_sequence, from_sequence)
+    assert s1inds_gap_left == [0, 1, 2, 2, 3]
+    assert s1inds_gap_right == [0, 1, 2, 3, 3]
+
+
+    assert seq_1[0] == seq_2[s1inds_gap_left[0]]
+    assert seq_1[1] == seq_2[s1inds_gap_left[1]]
+    assert seq_1[2] == seq_2[s1inds_gap_left[2]]
+    assert seq_1[4] == seq_2[s1inds_gap_left[4]]
+
+
+def test_get_relative_coordinates_start_gap():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('--CGT', 'TTCGT')
+    assert s1inds_gap_left == [-1, -1, 0, 1, 2]
+    assert s1inds_gap_right == [0, 0, 0, 1, 2]
+
+
+def test_get_relative_coordinates_from_gap():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('ATCGT', 'ATC-T')
+    assert s1inds_gap_left == [0, 1, 2, 4]
+    assert s1inds_gap_right == [0, 1, 2, 4]
+
+def test_get_relative_coordinates_end_gap():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('ATC--', 'ATCGT')
+    assert s1inds_gap_left == [0, 1, 2, 2, 2]
+    assert s1inds_gap_right == [0, 1, 2, 3, 3]
+
+def test_get_relative_coordinates_multiple_gaps():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('AT--TC--G--CC', 'ATCGTCGCGTTCC')
+    assert s1inds_gap_left == [0, 1, 1, 1, 2, 3, 3, 3, 4, 4, 4, 5, 6]
+    assert s1inds_gap_right == [0, 1, 2, 2, 2, 3, 4, 4, 4, 5, 5, 5, 6]
+
+def test_get_relative_coordinates_ind_and_dels():
+    s1inds_gap_left, s1inds_gap_right = CRISPRessoShared.get_relative_coordinates('ATG--C', 'A-GCTC')
+    assert s1inds_gap_left == [0, 2, 2, 2, 3]
+    assert s1inds_gap_right == [0, 2, 3, 3, 3]
+
+
+def test_get_quant_window_ranges_from_include_idxs():
+    include_idxs = [0, 1, 2, 10, 11, 12]
+    assert CRISPRessoShared.get_quant_window_ranges_from_include_idxs(include_idxs) == [(0, 2), (10, 12)]
+
+
+def test_get_quant_window_ranges_from_include_idxs_empty():
+    include_idxs = []
+    assert CRISPRessoShared.get_quant_window_ranges_from_include_idxs(include_idxs) == []
+
+
+def test_get_quant_window_ranges_from_include_idxs_single():
+    include_idxs = [50, 51, 52, 53]
+    assert CRISPRessoShared.get_quant_window_ranges_from_include_idxs(include_idxs) == [(50, 53)]
+
+
+def test_get_quant_window_ranges_from_include_idxs_single_gap():
+    include_idxs = [50, 51, 52, 53, 55]
+    assert CRISPRessoShared.get_quant_window_ranges_from_include_idxs(include_idxs) == [(50, 53), (55, 55)]
+
+
+def test_get_quant_window_ranges_from_include_idxs_multiple_gaps():
+    include_idxs = [50, 51, 52, 53, 55, 56, 57, 58, 60]
+    assert CRISPRessoShared.get_quant_window_ranges_from_include_idxs(include_idxs) == [(50, 53), (55, 58), (60, 60)]