Repository: sdss/mangadap (language: python)

Issue:
Merges develop into master (4.0.0 tag)
As titled.


Discussion / hints:
# [Codecov](https://codecov.io/gh/sdss/mangadap/pull/103?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss) Report
> :exclamation: No coverage uploaded for pull request base (`master@d00e9f7`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss#section-missing-base-commit).
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff            @@
##             master     #103   +/-   ##
=========================================
  Coverage          ?   34.63%           
=========================================
  Files             ?       83           
  Lines             ?    18399           
  Branches          ?        0           
=========================================
  Hits              ?     6372           
  Misses            ?    12027           
  Partials          ?        0           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/sdss/mangadap/pull/103?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sdss/mangadap/pull/103?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss). Last update [d00e9f7...ab47717](https://codecov.io/gh/sdss/mangadap/pull/103?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sdss).


Fix patch:
diff --git a/CHANGES.md b/CHANGES.md
index 4774fd62..3125e40b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,7 +1,28 @@
 
-3.1.3dev
+4.0.3dev
 --------
 
+ - Minor change to `manga_dap_inspector` to change initial spectral plotting
+   limits to the minimum and maximum of the whole cube instead of just the
+   center spaxel.
+ - Deprecated most of the configuration files and directories to the `outofdate/`
+   directory.
+ - Fixed survey-level execution script `rundap` and the post-processing scripts
+   used to create the DAPall file and QA plots.
+
+4.0.2 (1 Apr 2022)
+------------------
+
+ - tomli dependency hotfix for pip distribution
+
+4.0.1 (1 Apr 2022)
+------------------
+
+ - Package name hot fix for pip distribution
+
+4.0.0 (1 Apr 2022)
+------------------
+
  - Update to custom datacube class implementation.  Previous version
    required a file within the mangadap/ directory structure; new code
    allows for imports from a local user file.
diff --git a/README.md b/README.md
index 2854c309..af8ba383 100644
--- a/README.md
+++ b/README.md
@@ -1,101 +1,57 @@
 # The MaNGA Data Analysis Pipeline
 
 [![Build Status](https://github.com/sdss/mangadap/actions/workflows/ci_tests.yml/badge.svg)](https://github.com/sdss/mangadap/actions)
-[![Coverage Status](https://coveralls.io/repos/github/sdss/mangadap/badge.svg?branch=master)](https://coveralls.io/github/sdss/mangadap?branch=master)
+[![Coverage Status](https://codecov.io/gh/sdss/mangadap/branch/master/graph/badge.svg?token=S4veEPJwS1)](https://codecov.io/gh/sdss/mangadap)
 [![Doc Status](https://readthedocs.org/projects/sdss-mangadap/badge/?version=latest)](https://sdss-mangadap.readthedocs.io/en/latest/)
-[![astropy](http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)](http://www.astropy.org/)
 [![License](https://img.shields.io/github/license/sdss/mangadap)](https://github.com/sdss/mangadap/blob/master/LICENSE.md)
+[![astropy](http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)](http://www.astropy.org/)
 
-The MaNGA data-analysis pipeline (MaNGA DAP) is the survey-led software
-package that analyzes the data produced by the MaNGA data-reduction
-pipeline (MaNGA DRP) to produced physical properties derived from the
-MaNGA spectroscopy.
-
-For full documentation, see: https://sdss-mangadap.readthedocs.io/en/latest/
-
-**Note that the version of the DAP used for DR15 is [2.2.1](https://github.com/sdss/mangadap/releases/tag/2.2.1).**
-
-## Citation
-
-If you use the DAP software and/or its output products, please cite the following two papers:
-
- - *Overview*: [Westfall et al. (2019, AJ, 158, 231)](https://ui.adsabs.harvard.edu/abs/2019AJ....158..231W/abstract)
- - *Emission-line Modeling*: [Belfiore et al. (2019, AJ, 158, 160)](https://ui.adsabs.harvard.edu/abs/2019AJ....158..160B/abstract)
-
-## Installation
-
-To install, first clone the repository:
-
-`git clone https://github.com/sdss/mangadap.git`
-
-We recommend using the most recent tag:
+The MaNGA data-analysis pipeline (MaNGA DAP) is the survey-led software package
+that has analyzed all galaxy data produced by the MaNGA data-reduction pipeline
+(MaNGA DRP).  Its goal is to produce high-level, science-ready data products
+derived from MaNGA spectra.  The products currently provided are:
 
-```
-cd mangadap
-./checkout_current_tag
-```
+ - Spatially stacked spectra
+ - Stellar kinematics
+ - Nebular emission-line properties: fluxes, equivalent widths, and
+   kinematics
+ - Spectral indices: absorption-line (e.g., H-delta) and bandhead/color
+   (e.g., TiO, D4000) measurements
 
-----
+That is, the DAP currently focuses on "model-independent" properties.
+Higher-level, model-dependent properties, such as stellar-population parameters,
+are outside of the scope of the current pipeline.
 
-To install, run:
+**See our full documentation at [sdss-mangadap.readthedocs.io](https://sdss-mangadap.readthedocs.io/en/latest/).**
 
-`python3 setup.py install`
 
-On MacOSX, you may need to add `CC=clang`, i.e.:
-   
-`CC=clang python3 setup.py install`
+## SDSS Data Release Versions
 
-----
+ - [SDSS-IV/MaNGA DR15](https://www.sdss.org/dr15/manga/) is based on version 2.2.1
 
-The DAP uses environmental variable to define the paths to specific data
-and other repositories.  If these are not defined, warnings will be
-issued everytime the DAP is installed or imported.  The relevant
-environmental variables, their default, and their usage are provided
-below.
+ - [SDSS-IV/MaNGA DR17](https://www.sdss.org/dr17/manga/) is based on version
+   3.1.2.  Note that the version of the code that produced the DR17 was version
+   3.1.0; version 3.1.1 and 3.1.2 only include documentation updates.
 
-|                 Variable |                           Default |                                       Comments |
-|:------------------------ |:--------------------------------- |:---------------------------------------------- |
-| `MANGADRP_VER`           | `v2_4_3`                          | Version of the DRP, used for path construction |
-| `MANGA_SPECTRO_REDUX`    | `$HOME/MaNGA/redux`               | Root path for the reduced data                 |
-| `MANGADAP_VER`           | `mangadap.__version__`            | Version of the DAP, used for path construction |
-| `MANGA_SPECTRO_ANALYSIS` | `$HOME/MaNGA/analysis`            | Root path for the analysis data                |
-| `MANGACORE_VER`          | `v1_6_2`                          | Version of MaNGA core (survey-level meta data) |
-| `MANGACORE_DIR`          | `$HOME/MaNGA/core/$MANGACORE_VER` | Root path with the MaNGA core repository       |
 
-**Notes:**
- - `$MANGACORE_VER` and `$MANGACORE_DIR` are only needed to perform the
-   survey-level execution of the pipeline.
- - The DAP expects to find the DRP `LOGCUBE` files in
-   `$MANGA_SPECTRO_REDUX/$MANGADRP_VER/[plate]/stack`, where `[plate]`
-   is the desired plate number.
- - The DAP expects to find/write data to
-   `$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER`.
- - `$MANGADAP_VER` is only used to set the path names, not to select the
-   specific version of the pipeline to use
+## Citation
 
-These environmental variables can be added to your `.bash_profile` file
-in your home directory or be included in a script that is sourced when
-you want to run the DAP.  The added lines to your `.bash_profile` file
-could look something like this:
+If you use the DAP software and/or its output products, please cite the
+following two papers:
 
-```
-export MANGA_SPECTRO_REDUX=/Volumes/MaNGA/redux
-export MANGA_SPECTRO_ANALYSIS=/Volumes/MaNGA/analysis
+ - *Overview*: [Westfall et al. (2019, AJ, 158, 231)](https://ui.adsabs.harvard.edu/abs/2019AJ....158..231W/abstract)
+ - *Emission-line Modeling*: [Belfiore et al. (2019, AJ, 158, 160)](https://ui.adsabs.harvard.edu/abs/2019AJ....158..160B/abstract)
 
-export MANGADRP_VER=v2_4_3
+Additionally, if you use SDSS-IV/MaNGA data, please see:
 
-export MANGADAP_VER=2.2.1
+ - [How to Cite SDSS](https://www.sdss.org/collaboration/citing-sdss/)
+ - [SDSS Technical Publications](https://www.sdss.org/science/technical_publications/)
 
-export MANGACORE_VER=v1_6_2
-export MANGACORE_DIR=$HOME/MaNGA/core/$MANGACORE_VER
-```
 
-**Note**: Some of these variables are also defined by Marvin; see
-[here](https://sdss-marvin.readthedocs.io/en/stable/installation.html).
-It's possible to have both Marvin and the DAP point to the same
-directory, but beware that this may mean that some of the files get
-overwritten.
+## Installation
 
+`pip install sdss-mangadap`
 
+See https://sdss-mangadap.readthedocs.io/en/latest/installation.html
 
 
diff --git a/bin/rundap b/bin/rundap
deleted file mode 100755
index c806a44d..00000000
--- a/bin/rundap
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python
-
-# TODO: refactor rundap.py so that it looks more like the other
-# scripts...
-
-import sys
-if sys.version < '3':
-    raise ValueError('Must use python3 to run MaNGA DAP.')
-
-validation=True
-try: 
-    from mangadap.scripts import rundap
-except ImportError as e: 
-    validation = False
-    print('ImportError: {0}'.format(str(e)))
-if validation:
-    rundap = rundap.rundap(console=True)
-
diff --git a/docs/api/mangadap.survey.mangampl.rst b/docs/api/mangadap.survey.mangampl.rst
deleted file mode 100644
index 3dbd8e28..00000000
--- a/docs/api/mangadap.survey.mangampl.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-mangadap.survey.mangampl module
-===============================
-
-.. automodule:: mangadap.survey.mangampl
-   :members:
-   :private-members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/docs/api/mangadap.survey.rst b/docs/api/mangadap.survey.rst
index 91128492..60cb2e34 100644
--- a/docs/api/mangadap.survey.rst
+++ b/docs/api/mangadap.survey.rst
@@ -10,8 +10,7 @@ Submodules
    mangadap.survey.dapall
    mangadap.survey.drpcomplete
    mangadap.survey.manga_dap
-   mangadap.survey.mangampl
-   mangadap.survey.util
+   mangadap.survey.rundap
 
 Module contents
 ---------------
diff --git a/docs/api/mangadap.survey.rundap.rst b/docs/api/mangadap.survey.rundap.rst
new file mode 100644
index 00000000..3c180bfd
--- /dev/null
+++ b/docs/api/mangadap.survey.rundap.rst
@@ -0,0 +1,8 @@
+mangadap.survey.rundap module
+=============================
+
+.. automodule:: mangadap.survey.rundap
+   :members:
+   :private-members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/api/mangadap.survey.util.rst b/docs/api/mangadap.survey.util.rst
deleted file mode 100644
index f9c44db2..00000000
--- a/docs/api/mangadap.survey.util.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-mangadap.survey.util module
-===========================
-
-.. automodule:: mangadap.survey.util
-   :members:
-   :private-members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/docs/execution.rst b/docs/execution.rst
index b231a4c4..fd33f6bd 100644
--- a/docs/execution.rst
+++ b/docs/execution.rst
@@ -8,18 +8,12 @@ Execution
 
 The MaNGA DAP was originally developed to analyze data from the SDSS-IV/MaNGA
 Survey, provided reduction products from the MaNGA data-reduction pipeline
-(DRP).  *However, it is now easy to apply the MaNGA DAP to any IFU datacube.*
+(DRP).  **However, it is now easy to apply the MaNGA DAP to any IFU datacube.**
 
 Below, we describe how to use the MaNGA DAP to analyze MaNGA
 datacubes, as well as how to execute the survey-level batch mode that analyzes
 all the MaNGA datacubes within a given directory structure.
 
-.. warning::
-
-    Much of the survey-level execution of the code needs to be updated for the
-    front end changes in version>4.x.  If not removed outright, out of date
-    documentation is flagged in what follows.
-
 To analyze non-MaNGA datacubes, see both :ref:`fitdatacube` and the description
 of the MaNGA DAP :ref:`plan`.  Also, for an example of how to fit single spectra
 using MaNGA DAP core algorithms, see :ref:`fitonespec`.
@@ -94,22 +88,19 @@ for the spectral resolution and spatial correlation matrix (e.g.,
 
 .. note::
 
-    If the isophotal ellipticity, ``ell``, is such that
-    :math:`\epsilon < 0` or :math:`\epsilon > 1`, the DAP will adopt
-    a default value of 0. The DAP accepts any value for the position
-    angle, ``pa``, but imposes periodic limits (e.g., 380 deg is
-    converted to 20 deg). If the effective radius, ``reff``, is
-    :math:`R_{\rm eff} < 0` or undefined, the DAP uses :math:`R_{\rm
-    eff} = 1`.
+    The metadata included in the configuration file is used by the DAP during
+    the analysis; however, not all of these metadata parameters are explicitly
+    *required*.  See how the data is checked/used in
+    :func:`mangadap.datacube.datacube.DataCube.populate_metadata`.
 
 .. _execution-mangadap:
 
 DAP command-line script
 -----------------------
 
-The main DAP script is ``$MANGADAP_DIR/bin/manga_dap``, which is a
-simple wrapper of :func:`~mangadap.survey.manga_dap.manga_dap`.  With the
-DAP installed, you can call the script directly from the command line:
+The main DAP script is ``manga_dap``, which is a simple wrapper of
+:func:`~mangadap.survey.manga_dap.manga_dap`.  With the DAP installed, you can
+call the script directly from the command line:
 
 .. include:: help/manga_dap.rst
 
@@ -117,16 +108,23 @@ The DAP allows you to define your own datacube class, as long as it is derived
 from :class:`~mangadap.datacube.datacube.DataCube`. You can then specify that
 your data should be instantiated with that derived class using the
 ``cube_module`` argument; this defaults to
-``mangadap.datacube.datacube.MaNGADataCube``, expected a MaNGA datacube.
+:class:`mangadap.datacube.datacube.MaNGADataCube` expecting that you're
+analyzing a MaNGA datacube.
 
 When running the DAP on a MaNGA datacube, you have to provide a
 configuration file; however, for derived classes, you may be able to
-fully instantiate the relevant data using the datacube file, which is
+fully instantiate the relevant data just using the datacube file, which is
 why we've provided the ``-f`` option.
 
-Note that the analysis plan file is an *optional* argument. If it is
-not given, the DAP will use the
-:ref:`execution-analysis-plan-default`.
+Note that the analysis plan file is an *optional* argument. If it is not given,
+the DAP will use the :ref:`execution-analysis-plan-default`.  As discussed in
+:ref:`plan`, you can also provide a derived class for the analysis plan, which
+should basically be provide fine-tuned control over the output directory
+structure.  If you're analyzing a non-MaNGA datacube, you will likely be fine
+just using the analysis plan base class.  However, because ``manga_dap``
+defaults to using the MaNGA specific plan class, you'll need to specify the base
+class (:class:`mangadap.config.analysisplan.AnalysisPlan`) when executing the
+code.
 
 To run the DAP on a single datacube using the default analysis plan,
 and assuming you have the DRPall file and the relevant LOGCUBE and
@@ -169,14 +167,14 @@ programmatically for many datacubes.
 Batch execution using automatically generated scripts
 -----------------------------------------------------
 
-.. warning::
+.. note::
 
-    **The batch execution of the DAP (even for MaNGA) is currently out-of-date.
-    These will be fixed soon.**
+    Version 4.x of the DAP has never been used for a batch execution/analysis of
+    the MaNGA data.  The code should work properly.  If it doesn't, please
+    `Submit an issue`_.
 
-The survey-level execution of the DAP uses the
-``$MANGADAP_DIR/bin/rundap`` script, which is a simple wrapper of
-:class:`~mangadap.scripts.rundap.rundap`.  This script
+The survey-level execution of the DAP uses the ``rundap`` script, which is a
+simple wrapper of :class:`~mangadap.survey.rundap.rundap`.  This script
 
  * sets up the DAP output directory structure
  * either confirms that a provided list of datacubes to analyze exist on
@@ -205,7 +203,7 @@ is:
 
 .. code-block:: console
     
-    rundap --platelist 7443 --ifudesignlist 12701 --redux_path /path/with/drp/output/ --analysis_path /path/for/dap/output/ -vv --log
+    rundap --platelist 7443 --ifudesignlist 12701 --redux_path /path/with/drp/output/drpver --analysis_path /path/for/dap/output/drpver/dapver -vv --log
 
 In this call, I've specified that the DRP data is in
 ``/path/with/drp/output/`` and that the DAP output should be placed
@@ -213,11 +211,10 @@ in ``/path/for/dap/output/`` instead of using the default
 :ref:`datamodel-directory-structure`.
 
 The script file this call produces is written to
-``/path/for/dap/output/log/[time]/7495/12704/mangadap-7495-12704``,
-where ``[time]`` is a time stamp of when ``rundap`` was executed. (If
-you execute ``rundap`` multiple times, it will create new directories
-using new time stamps each time.) The lines of the script file for
-each plate-ifu:
+``/path/for/dap/output/log/[time]/7495/12704/mangadap-7495-12704-LOGCUBE``,
+where ``[time]`` is a time stamp of when ``rundap`` was executed. (If you
+execute ``rundap`` multiple times, it will create new directories using new time
+stamps each time.) The lines of the script file for each plate-ifu:
 
  - touches the ``*.started`` file
  - executes ``manga_dap``
@@ -229,26 +226,25 @@ The example script generated by the above command would look something like this
 .. code-block:: bash
 
     # Auto-generated batch file
-    # Wed 27 May 2020 11:50:45
-
-    touch /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/7443/12701/mangadap-7443-12701.started
+    # Wed 06 Apr 2022 15:15:26
 
-    OMP_NUM_THREADS=1 manga_dap -c /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/common/7443/12701/mangadap-7443-12701-LOGCUBE.ini -r /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/redux/v3_0_1 -a /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1 -p /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/mpl10_plan.par --log /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/7443/12701/mangadap-7443-12701.log -vv
+    touch /path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/7443/12701/mangadap-7443-12701-LOGCUBE.started
 
-    OMP_NUM_THREADS=1 dap_ppxffit_qa 7443 12701 --analysis_path /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1 --plan_file /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/mpl10_plan.par
+    OMP_NUM_THREADS=1 manga_dap -c /path/for/dap/output/drpver/dapver/common/7443/12701/mangadap-7443-12701-LOGCUBE.ini -o /path/for/dap/output/drpver/dapver --log /path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/7443/12701/mangadap-7443-12701-LOGCUBE.log -vv
 
-    OMP_NUM_THREADS=1 spotcheck_dap_maps 7443 12701 --analysis_path /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1 --plan_file /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/mpl10_plan.par
+    OMP_NUM_THREADS=1 dap_ppxffit_qa -c /path/for/dap/output/drpver/dapver/common/7443/12701/mangadap-7443-12701-LOGCUBE.ini -o /path/for/dap/output/drpver/dapver -b 2.5
 
-    OMP_NUM_THREADS=1 dap_fit_residuals 7443 12701 --analysis_path /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1 --plan_file /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/mpl10_plan.par
+    OMP_NUM_THREADS=1 spotcheck_dap_maps -c /path/for/dap/output/drpver/dapver/common/7443/12701/mangadap-7443-12701-LOGCUBE.ini -o /path/for/dap/output/drpver/dapver -b 2.5
 
-    touch /uufs/chpc.utah.edu/common/home/sdss/mangawork/manga/spectro/analysis/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/7443/12701/mangadap-7443-12701.done
+    OMP_NUM_THREADS=1 dap_fit_residuals -c /path/for/dap/output/drpver/dapver/common/7443/12701/mangadap-7443-12701-LOGCUBE.ini -o /path/for/dap/output/drpver/dapver -b 2.5
 
+    touch /path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/7443/12701/mangadap-7443-12701-LOGCUBE.done
 
 To execute the script, you would then run:
 
 .. code-block:: bash
 
-    source /path/for/dap/output/v3_0_1/3.0.1/log/27May2020T17.50.29UTC/7443/12701/mangadap-7443-12701
+    source /path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/7443/12701/mangadap-7443-12701-LOGCUBE
 
 The ``rundap`` script allows you to construct scripts for all datacubes
 it can find on disk, all IFUs on a given plate, all combinations of a
@@ -258,65 +254,52 @@ IDs.
 .. note::
 
     The ``rundap`` script constructs the
-    :class:`~mangadap.survey.drpcomplete.DRPComplete` object and
-    writes its associated fits file (see
-    :ref:`metadatamodel-drpcomplete`). The data compiled into this
-    database can either be drawn from the DRPall file or from the
-    plateTargets data in ``mangacore``; the latter is the only reason
-    the DAP has ``mangacore`` as a dependency. For general use, you
-    should have ``rundap`` use the DRPall file. The use of the
-    plateTargets data is only necessary in the rare case when the DAP
-    is executed before the relevant DRPall file has been constructed.
+    :class:`~mangadap.survey.drpcomplete.DRPComplete` object and writes its
+    associated fits file; see :ref:`metadatamodel-drpcomplete`. The data
+    compiled into this database is pulled from the DRPall file, but some
+    corrections may be applied to the NSA redshift or photometry; see, e.g.,
+    :ref:`metadatamodel-redshift-fix`.
 
 To write the post-processing scripts, execute ``rundap`` with the
 ``--post`` and ``--post_plots`` options.  This produces two additional
 types of scripts:
 
-.. todo::
-
-    update the scripts below!
-
  - Scripts to produce QA plots for all IFUs on a given plate.  This file
    is written to, e.g.,
-   ``/path/for/dap/output/log/01Nov2019T16.58.40UTC/7443/7443_fitqa``
+   ``/path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/7443/7443_fitqa``
    and looks like this:
 
    .. code-block:: bash
 
         # Auto-generated batch file
-        # Fri 01 Nov 2019 10:58:52
+        # Thu 07 Apr 2022 13:33:27
 
-        touch /path/for/dap/output/v2_7_1/2.4.1/log/01Nov2019T16.58.40UTC/7443/7443_fitqa.started
+        touch /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/10001/10001_fitqa.started
 
-        dap_plate_fit_qa 7443 --analysis_path /path/for/dap/output/v2_7_1/2.4.1
+        OMP_NUM_THREADS=1 dap_plate_fit_qa 10001 --analysis_path /path/for/dap/output/drpver/dapver --plan_file /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/plan.toml
 
-        touch /path/for/dap/output/v2_7_1/2.4.1/log/01Nov2019T16.58.40UTC/7443/7443_fitqa.done
+        touch /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/10001/10001_fitqa.done
 
  - A script that builds the :ref:`metadatamodel-dapall` and writes its QA plots.  This
    file is written to, e.g.,
-   ``/path/for/dap/output/v2_7_1/2.4.1/log/01Nov2019T16.58.40UTC/build_dapall``
+   ``/path/for/dap/output/drpver/dapver/log/06Apr2022T22.15.26UTC/build_dapall``
    and looks like this:
 
    .. code-block:: bash
 
         # Auto-generated batch file
-        # Fri 01 Nov 2019 10:58:52
-
-        touch /path/for/dap/output/v2_7_1/2.4.1/log/01Nov2019T16.58.40UTC/build_dapall.started
-
-        construct_dapall --drpver v2_7_1 -r /path/with/drp/output/v2_7_1 --dapver 2.4.1 -a /path/for/dap/output/v2_7_1/2.4.1 -vv
-
-        dap_dapall_qa --drpver v2_7_1 --redux_path /path/with/drp/output/v2_7_1 --dapver 2.4.1 --analysis_path /path/for/dap/output/v2_7_1/2.4.1
+        # Thu 07 Apr 2022 13:33:27
 
-        touch /path/for/dap/output/v2_7_1/2.4.1/log/01Nov2019T16.58.40UTC/build_dapall.done
+        touch /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/build_dapall.started
 
+        OMP_NUM_THREADS=1 construct_dapall --drpver v3_1_1 -r /path/with/drp/output/drpver --dapver 3.1.0 -a /path/for/dap/output/drpver/dapver --plan_file /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/plan.toml -vv
 
-.. todo::
+        OMP_NUM_THREADS=1 dapall_qa --drpver v3_1_1 --redux_path /path/with/drp/output/drpver --dapver 3.1.0 --analysis_path /path/for/dap/output/drpver/dapver --plan_file /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/plan.toml
 
-    Check the details here.
+        touch /path/for/dap/output/drpver/dapver/log/07Apr2022T20.33.27UTC/build_dapall.done
 
 In the automated run of the DAP, any entry in the
-:ref:`metadatamodel-drpcomplete` file that meets the criteria set by
+:ref:`metadatamodel-drpcomplete` that meets the criteria set by
 :func:`mangadap.survey.drpcomplete.DRPComplete.can_analyze` will be
 analyzed. Currently the relevant criteria are:
  
@@ -337,7 +320,7 @@ If the code faults, all of the modules completed up until the fault occurred
 should have created a "reference" file that will effectively allow the code to
 pick up where it left off.  The mechanism used to determine if it can do this
 relies simply on the existence of the expected output file.  This means that if
-you change on of the parameters in your input :ref:`plan` file *without*
+you change one of the parameters in your input :ref:`plan` file *without*
 changing the keyword identifier for that module parameter set, the code may read
 in the existing file and keep going without incorporating your parameter change.
 
@@ -355,8 +338,9 @@ plots that provide spotchecks of the performance of the DAP; see
 :ref:`qualityassessment`.  The main QA plots for the analysis of a single
 datacube have a similar calling sequence as the main :ref:`execution-mangadap`.
 The three main scripts are ``spotcheck_dap_maps``, ``dap_ppxffit_qa``, and
-``dap_fit_residuals``.  Following the example above, these can be created after
-analysing the test datacube as follows:
+``dap_fit_residuals``.  Following the example execution of the
+:ref:`execution-mangadap` above, these can be created using the following
+subsequent calls:
 
 .. code-block:: console
 
@@ -394,10 +378,10 @@ that is sourced when you want to run the DAP.  The lines added to your
 
 .. code-block:: bash
 
-    export MANGA_SPECTRO_REDUX=/Volumes/MaNGA/redux
+    export MANGA_SPECTRO_REDUX=/path/with/drp/output
     export MANGADRP_VER=v3_1_1
 
-    export MANGA_SPECTRO_ANALYSIS=/Volumes/MaNGA/analysis
+    export MANGA_SPECTRO_ANALYSIS=/path/for/dap/output
     export MANGADAP_VER=3.1.0
 
 .. note::
diff --git a/docs/help/construct_dapall.rst b/docs/help/construct_dapall.rst
index 5a118497..04eee513 100644
--- a/docs/help/construct_dapall.rst
+++ b/docs/help/construct_dapall.rst
@@ -1,17 +1,14 @@
 .. code-block:: console
 
     $ construct_dapall -h
-    usage: construct_dapall [-h] [--plan_file PLAN_FILE] [--drpver DRPVER]
-                            [-r REDUX_PATH] [--dapver DAPVER] [-a ANALYSIS_PATH]
-                            [-m METHODS [METHODS ...]] [-v] [--quiet] [--double]
+    usage: construct_dapall [-h] [--drpver DRPVER] [-r REDUX_PATH] [--dapver DAPVER]
+                            [-a ANALYSIS_PATH] [--plan_file PLAN_FILE]
+                            [-m [METHODS ...]] [-v] [--quiet] [--double]
     
     Compile metadata for the DAPall file
     
     optional arguments:
       -h, --help            show this help message and exit
-      --plan_file PLAN_FILE
-                            parameter file with the MaNGA DAP execution plan to use
-                            instead of the default (default: None)
       --drpver DRPVER       DRP version (default: None)
       -r REDUX_PATH, --redux_path REDUX_PATH
                             Top-level directory with the DRP products; defaults to
@@ -21,8 +18,11 @@
                             Top-level output directory for the DAP results; defaults
                             to $MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER
                             (default: None)
-      -m METHODS [METHODS ...], --methods METHODS [METHODS ...]
-                            Only include output from this DAP method designation in
+      --plan_file PLAN_FILE
+                            toml file with the MaNGA DAP execution plan to use
+                            instead of the default (default: None)
+      -m [METHODS ...], --methods [METHODS ...]
+                            Only include output for these DAP method designations in
                             the output (default: None)
       -v, --verbose         Set verbosity level; can be omitted and set up to -vv
                             (default: 0)
diff --git a/docs/help/dap_plate_fit_qa.rst b/docs/help/dap_plate_fit_qa.rst
index 321b67b7..963ff188 100644
--- a/docs/help/dap_plate_fit_qa.rst
+++ b/docs/help/dap_plate_fit_qa.rst
@@ -4,7 +4,7 @@
     usage: dap_plate_fit_qa [-h] [--drpver DRPVER] [--dapver DAPVER]
                             [--redux_path REDUX_PATH]
                             [--analysis_path ANALYSIS_PATH] [--plan_file PLAN_FILE]
-                            [--daptype DAPTYPE] [--normal_backend]
+                            [--normal_backend]
                             plate
     
     Construct per-plate QA plots
@@ -23,6 +23,5 @@
       --plan_file PLAN_FILE
                             parameter file with the MaNGA DAP execution plan to use
                             instead of the default (default: None)
-      --daptype DAPTYPE     DAP processing type (default: None)
       --normal_backend
     
\ No newline at end of file
diff --git a/docs/help/dapall_qa.rst b/docs/help/dapall_qa.rst
index ae0ee460..7c2a5cbf 100644
--- a/docs/help/dapall_qa.rst
+++ b/docs/help/dapall_qa.rst
@@ -3,7 +3,7 @@
     $ dapall_qa -h
     usage: dapall_qa [-h] [--drpver DRPVER] [--dapver DAPVER]
                      [--redux_path REDUX_PATH] [--analysis_path ANALYSIS_PATH]
-                     [--plan_file PLAN_FILE] [--daptype DAPTYPE] [--normal_backend]
+                     [--plan_file PLAN_FILE] [--normal_backend]
     
     Construct QA plots using the DAPall metadata
     
@@ -18,6 +18,5 @@
       --plan_file PLAN_FILE
                             parameter file with the MaNGA DAP execution plan to use
                             instead of the default (default: None)
-      --daptype DAPTYPE     DAP processing type (default: None)
       --normal_backend
     
\ No newline at end of file
diff --git a/docs/help/rundap.rst b/docs/help/rundap.rst
index 6f5a06e4..235fab92 100644
--- a/docs/help/rundap.rst
+++ b/docs/help/rundap.rst
@@ -1,68 +1,68 @@
 .. code-block:: console
 
     $ rundap -h
-    usage: rundap [-h] [--clobber] [-v] [--quiet] [--print_version] [--loose]
-                  [--mplver MPLVER] [--redux_path REDUX_PATH] [--dapver DAPVER]
+    usage: rundap [-h] [--overwrite] [-v] [--quiet] [--print_version]
+                  [--drpver DRPVER] [--redux_path REDUX_PATH] [--dapver DAPVER]
                   [--analysis_path ANALYSIS_PATH] [--plan_file PLAN_FILE]
                   [--platelist PLATELIST] [--ifudesignlist IFUDESIGNLIST]
                   [--list_file LIST_FILE] [--combinatorics] [--sres_ext SRES_EXT]
-                  [--sres_fill SRES_FILL] [--covar_ext COVAR_EXT]
-                  [--use_plttargets] [--plttargets PLTTARGETS] [--on_disk] [--log]
-                  [--no_proc] [--no_plots] [--post] [--post_plots] [--dapall]
-                  [--label LABEL] [--nodes NODES] [--cpus CPUS] [--fast QOS]
-                  [--umask UMASK] [--walltime WALLTIME] [--toughness] [--create]
-                  [--submit] [--progress] [--queue QUEUE]
+                  [--sres_fill SRES_FILL] [--covar_ext COVAR_EXT] [--on_disk]
+                  [--can_analyze] [--log] [--no_proc] [--no_plots] [--post]
+                  [--post_plots] [--label LABEL] [--nodes NODES] [--cpus CPUS]
+                  [--fast QOS] [--umask UMASK] [--walltime WALLTIME] [--toughness]
+                  [--create] [--submit] [--progress] [--queue QUEUE]
+    
+    Perform analysis of integral-field data.
     
     optional arguments:
       -h, --help            show this help message and exit
-      --clobber             if all selected, will run dap for all
+      --overwrite           if all selected, will run dap for all
                             plates/ifudesigns/modes regardless of state (default:
                             False)
       -v, --verbose         Set verbosity level for manga_dap; can be omitted and
                             set up to -vv (default: 0)
       --quiet               suppress screen output (default: False)
       --print_version       print DAP version and stop (default: False)
-      --loose               Only throw warnings if the versioning is not
-                            identically as it should be for the designated MPL
-                            (default: False)
-      --mplver MPLVER       select MPL version to analyze (default: None)
+      --drpver DRPVER       MaNGA DRP version for analysis; $MANGADRP_VER by default
+                            (default: None)
       --redux_path REDUX_PATH
                             main DRP output path (default: None)
-      --dapver DAPVER       optional output version, different from product
-                            version (default: None)
+      --dapver DAPVER       optional output version, different from product version.
+                            This *only* affects the output directory structure. It
+                            does *not* select the version of the DAP to use.
+                            (default: None)
       --analysis_path ANALYSIS_PATH
                             main DAP output path (default: None)
       --plan_file PLAN_FILE
-                            parameter file with the MaNGA DAP execution plan to
-                            use instead of the default (default: None)
+                            parameter file with the MaNGA DAP execution plan to use
+                            instead of the default (default: None)
       --platelist PLATELIST
                             set list of plates to reduce (default: None)
       --ifudesignlist IFUDESIGNLIST
                             set list of ifus to reduce (default: None)
       --list_file LIST_FILE
-                            A file with the list of plates and ifudesigns to
-                            analyze (default: None)
+                            A file with the list of plates and ifudesigns to analyze
+                            (default: None)
       --combinatorics       force execution of all permutations of the provided
                             lists (default: False)
       --sres_ext SRES_EXT   Spectral resolution extension to use. Default set by
                             MaNGADataCube class. (default: None)
       --sres_fill SRES_FILL
-                            If present, use interpolation to fill any masked
-                            pixels in the spectral resolution vectors. Default set
-                            by MaNGADataCube class. (default: None)
+                            If present, use interpolation to fill any masked pixels
+                            in the spectral resolution vectors. Default set by
+                            MaNGADataCube class. (default: None)
       --covar_ext COVAR_EXT
                             Use this extension to define the spatial correlation
                             matrix. Default set by MaNGADataCube class. (default:
                             None)
-      --use_plttargets      Use platetargets files instead of the DRPall file to
-                            generate the DRP complete database (default: False)
-      --plttargets PLTTARGETS
-                            path to plateTargets file(s); if provided will force
-                            update to drpcomplete fits file (default: None)
-      --on_disk             When using the DRPall file to collate the data for
-                            input to the DAP, search for available DRP files on
-                            disk instead of using the DRPall file content.
-                            (default: False)
+      --on_disk             When using the DRPall file to collate the data for input
+                            to the DAP, search for available DRP files on disk
+                            instead of using the DRPall file content. (default:
+                            False)
+      --can_analyze         Only construct script files for datacubes that
+                            can/should be analyzed by the DAP. See :func:`~mangadap.
+                            survey.drpcomplete.DRPComplete.can_analyze`. (default:
+                            False)
       --log                 Have the main DAP executable produce a log file
                             (default: False)
       --no_proc             Do NOT perform the main DAP processing steps (default:
@@ -72,14 +72,11 @@
                             False)
       --post_plots          Create/Submit the post-processing plotting scripts
                             (default: False)
-      --dapall              Wait for any individual plate-ifu processes to finish
-                            and thenupdate the DAPall file (default: False)
-      --label LABEL         label for cluster job (default: None)
+      --label LABEL         label for cluster job (default: mangadap)
       --nodes NODES         number of nodes to use in cluster (default: 1)
       --cpus CPUS           number of cpus to use per node. Default is to use all
-                            available; otherwise, set to minimum of provided
-                            number and number of processors per node (default:
-                            None)
+                            available; otherwise, set to minimum of provided number
+                            and number of processors per node (default: None)
       --fast QOS            qos state (default: None)
       --umask UMASK         umask bit for cluster job (default: 0027)
       --walltime WALLTIME   walltime for cluster job (default: 240:00:00)
@@ -91,7 +88,6 @@
       --progress            instead of closing the script, report the progress of
                             the analysis on the cluster; this is required if you
                             want to submit the DAPall script immediately after
-                            completing the individual cube analysis (default:
-                            False)
+                            completing the individual cube analysis (default: False)
       --queue QUEUE         set the destination queue (default: None)
     
\ No newline at end of file
diff --git a/docs/include/links.rst b/docs/include/links.rst
index cbc94e98..b4d0a275 100644
--- a/docs/include/links.rst
+++ b/docs/include/links.rst
@@ -1,6 +1,6 @@
 .. distribution
 
-.. _pypi: https://pypi.org/project/sdss-mangadap/
+.. _pip: https://pypi.org/project/sdss-mangadap/
 .. _Anaconda: https://www.continuum.io/DOWNLOADS
 .. _conda: https://docs.conda.io/projects/conda/en/latest/index.html
 .. _virtualenv: https://virtualenv.pypa.io/en/latest/
diff --git a/docs/index.rst b/docs/index.rst
index 6a934213..81a2085a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,16 +1,12 @@
 The MaNGA Data Analysis Pipeline
 ================================
 
-.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)
-   :target: http://www.astropy.org/
-   :alt: Powered by Astropy
-
-.. image:: https://travis-ci.org/sdss/mangadap.svg?branch=master
-   :target: https://travis-ci.org/sdss/mangadap
+.. image:: https://github.com/sdss/mangadap/actions/workflows/ci_tests.yml/badge.svg
+   :target: https://github.com/sdss/mangadap/actions
    :alt: Build Status
 
-.. image:: https://coveralls.io/repos/github/sdss/mangadap/badge.svg?branch=master
-   :target: https://coveralls.io/github/sdss/mangadap?branch=master
+.. image:: https://codecov.io/gh/sdss/mangadap/branch/master/graph/badge.svg?token=S4veEPJwS1
+   :target: https://codecov.io/gh/sdss/mangadap
    :alt: Coverage Status
 
 .. image:: https://readthedocs.org/projects/sdss-mangadap/badge/?version=latest
@@ -21,6 +17,10 @@ The MaNGA Data Analysis Pipeline
    :target: https://github.com/sdss/mangadap/blob/master/LICENSE.md
    :alt: License
 
+.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)
+   :target: http://www.astropy.org/
+   :alt: Powered by Astropy
+
 The MaNGA data-analysis pipeline (MaNGA DAP) is the survey-led software package
 that has analyzed all galaxy data produced by the MaNGA data-reduction pipeline
 (MaNGA DRP).  Its goal is to produce high-level, science-ready data products
diff --git a/docs/installation.rst b/docs/installation.rst
index e571734a..624ab72a 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -4,31 +4,6 @@
 Installation
 ============
 
-Clone the repo
---------------
-
-To download the DAP software and associated data, clone the `mangadap
-GitHub repo`_ by executing:
-
-    .. code-block:: console
-
-        git clone https://github.com/sdss/mangadap.git
-
-This will create a ``mangadap`` directory in the current directory.
-Although we try to keep the ``master`` branch of the repository stable,
-we recommend using the most recent tag.  You can do so by executing:
-
-    .. code-block:: console
-
-        cd mangadap
-        ./checkout_current_tag
-
-.. warning::
-
-    There is a distribution of the DAP that can be installed via
-    `pip`, but the installation will be unsuccessful. We're still
-    working out the bugs...
-
 Install Python 3
 ----------------
 
@@ -36,7 +11,6 @@ The DAP is supported for Python 3 only. To install Python, you can do
 so along with a full package manager, like `Anaconda`_, or you can
 install python 3 directly from `python.org`_.
 
-
 Python environment setup
 ------------------------
 
@@ -65,34 +39,18 @@ easily managing `virtualenv`_ environments.
 Install the DAP code
 --------------------
 
-Once you have cloned the repository, the preferred method to install the DAP and
-ensure its dependencies are met is to perform a local pip install.  From the
-top-level ``mangadap`` directory, run:
+You can install the DAP directly from `pip`_;
 
 .. code-block:: console
 
-    pip install -e .
+    pip install sdss-mangadap
 
-This approach is preferred because it eases uninstalling the code:
+To uninstall:
 
 .. code-block:: console
     
     pip uninstall sdss-mangadap
 
-If you plan to develop the code, you should install the development dependencies
-as follows:
-
-.. code-block:: console
-
-    pip install -e ".[dev]"
-
-.. note::
-
-    The use of the quotations above is shell dependent; e.g., you need them for
-    zshell, but not for bash.  Also beware the exact characters used in the html
-    above may not be the quotation characters you need for the command line
-    (i.e., copy-pasting the line above may throw an error).
-
 Test your installation
 ----------------------
 
@@ -119,6 +77,38 @@ And then executing the tests with the same commands above.
     (e.g., from using collaboration access with Marvin), this script will pull
     the data from the (still public) `mangawork` directory on the SAS.
 
+Installation from source
+------------------------
+
+To install from the sources, first clone the `mangadap GitHub repo`_ by
+executing:
+
+.. code-block:: console
+
+    git clone https://github.com/sdss/mangadap.git
+
+This will create a ``mangadap`` directory in the current directory.
+Although we try to keep the ``master`` branch of the repository stable,
+we recommend using the most recent tag.  You can do so by executing:
+
+.. code-block:: console
+
+    cd mangadap
+    ./checkout_current_tag
+
+If you plan to develop the code (e.g., in your own fork of the code), you should
+install the development dependencies as follows:
+
+.. code-block:: console
+
+    pip install -e ".[dev]"
+
+.. note::
+
+    The use of the quotations above is shell dependent; e.g., you need them for
+    zshell, but not for bash.  Also beware the exact characters used in the html
+    above may not be the quotation characters you need for the command line
+    (i.e., copy-pasting the line above may throw an error).
 
 Problems?
 ---------
diff --git a/docs/modules/bin.rst b/docs/modules/bin.rst
index ac86e8f1..6e2802aa 100644
--- a/docs/modules/bin.rst
+++ b/docs/modules/bin.rst
@@ -8,29 +8,16 @@ Spatial Binning
 
 *Analysis class*: :class:`~mangadap.proc.spatiallybinnedspectra.SpatiallyBinnedSpectra`
 
-*Method definition*: :class:`~mangadap.proc.spatiallybinnedspectra.SpatiallyBinnedSpectraDef`
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.common_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common/[PLATE]/[IFUDESIGN]`` for MaNGA
 
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common``
+*Reference file*: see :class:`~mangadap.proc.spatiallybinnedspectra.SpatiallyBinnedSpectra.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING].fits.gz`` for MaNGA
 
-*Reference file*: ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY].fits.gz``
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.spatiallybinnedspectra.SpatiallyBinnedSpectraDef`
 
-*Config files*: ``$MANGADAP_DIR/mangadap/config/spatial_binning``
-
-*Example config*: ``vor10.ini``
-
-.. code-block:: ini
-
-    [default]
-    key                    = VOR10
-    galactic_reddening     = ODonnell
-    galactic_rv            = 3.1
-    method                 = voronoi
-    minimum_snr            = 1.0
-    operation              = mean
-    velocity_register      = False
-    stack_covariance_mode  = channels
-    stack_covariance_par   = 11
-    target_snr             = 10
+.. include:: ../tables/spatiallybinnedspectradef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/modules/emissionfit.rst b/docs/modules/emissionfit.rst
index 71d0dc8f..087e7db0 100644
--- a/docs/modules/emissionfit.rst
+++ b/docs/modules/emissionfit.rst
@@ -6,40 +6,16 @@ Emission-line Modeling
 
 *Analysis class*: :class:`~mangadap.proc.emissionlinemodel.EmissionLineModel`
 
-*Method definition*: :class:`~mangadap.proc.emissionlinemodel.EmissionLineModelDef`
-
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[PLATE]/[IFUDESIGN]/ref``
-
-*Reference file*: ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY]-[CONTINUUM_KEY]-[ELFIT_KEY].fits.gz``
-
-*Config files*: ``$MANGADAP_DIR/mangadap/config/emission_line_modeling``
-
-*Example config*: ``efitmpl9.ini``
-
-.. code-block:: ini
-
-    [default]
-    key                  = EFITMPL9
-    fit_method           = sasuke
-    minimum_snr          = 0.0
-    deconstruct_bins     = ignore
-    mom_vel_name         = Ha-6564
-    mom_disp_name
-    waverange
-    artifact_mask        = BADSKY
-    emission_lines       = ELPMPL9
-    etpl_line_sigma_mode = offset
-    etpl_line_sigma_min  = 10
-    baseline_order
-    window_buffer
-    reject_boxcar        = 101
-    continuum_templates  = MASTARHC
-    velscale_ratio       = 1
-    bias
-    moments
-    degree
-    mdegree              = 14
-    internal_reddening
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.method_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[METHOD]/[PLATE]/[IFUDESIGN]/ref`` for MaNGA
+
+*Reference file*: see :class:`~mangadap.proc.emissionlinemodel.EmissionLineModel.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING]-[CONTINUUM]-[ELFIT].fits.gz`` for MaNGA
+
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.emissionlinemodel.EmissionLineModelDef`
+
+.. include:: ../tables/emissionlinemodeldef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/modules/emissionmom.rst b/docs/modules/emissionmom.rst
index 7d7b8f64..52c8ca58 100644
--- a/docs/modules/emissionmom.rst
+++ b/docs/modules/emissionmom.rst
@@ -6,33 +6,24 @@ Emission-line Moments
 
 *Analysis class*: :class:`~mangadap.proc.emissionlinemoments.EmissionLineMoments`
 
-*Method definition*: :class:`~mangadap.proc.emissionlinemoments.EmissionLineMomentsDef`
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.method_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[METHOD]/[PLATE]/[IFUDESIGN]/ref`` for MaNGA
 
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[PLATE]/[IFUDESIGN]/ref``
-
-*Reference file*:
+*Reference file*: see :class:`~mangadap.proc.emissionlinemoments.EmissionLineMoments.default_paths`;
+for MaNGA the files are:
 
     * Before Gaussian emission-line modeling:
 
-        * ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY]-[CONTINUUM_KEY]-[ELMOM_KEY].fits.gz``
+        * ``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING]-[CONTINUUM]-[ELMOM].fits.gz``
 
     * After Gaussian emission-line modeling:
 
-        * ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY]-[CONTINUUM_KEY]-[ELFIT_KEY]-[ELMOM_KEY].fits.gz``
-
-*Config files*: ``$MANGADAP_DIR/mangadap/config/emission_line_moments``
-
-*Example config*: ``emomm.ini``
+        * ``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING]-[CONTINUUM]-[ELFIT]-[ELMOM].fits.gz``
 
-.. code-block:: ini
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.emissionlinemoments.EmissionLineMomentsDef`
 
-    [default]
-    key                = EMOMMPL9
-    minimum_snr        = 0.0
-    artifact_mask      = BADSKY
-    emission_passbands = ELBMPL9
-    redo_postmodeling  = True
-    fit_vel_name       = Ha-6564
+.. include:: ../tables/emissionlinemomentsdef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/modules/indices.rst b/docs/modules/indices.rst
index a79c433f..3bc35f13 100644
--- a/docs/modules/indices.rst
+++ b/docs/modules/indices.rst
@@ -6,26 +6,16 @@ Spectral-Index Measurements
 
 *Analysis class*: :class:`~mangadap.proc.spectralindices.SpectralIndices`
 
-*Method definition*: :class:`~mangadap.proc.spectralindices.SpectralIndicesDef`
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.method_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[METHOD]/[PLATE]/[IFUDESIGN]/ref`` for MaNGA
 
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[PLATE]/[IFUDESIGN]/ref``
+*Reference file*: see :class:`~mangadap.proc.spectralindices.SpectralIndices.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING]-[CONTINUUM]-[ELFIT]-[INDICES].fits.gz`` for MaNGA
 
-*Reference file*: ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY]-[CONTINUUM_KEY]-[ELFIT_KEY]-[SPINDEX_KEY].fits.gz``
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.spectralindices.SpectralIndicesDef`
 
-*Config files*: ``$MANGADAP_DIR/mangadap/config/spectral_indices``
-
-*Example config*: ``indxen.ini``
-
-.. code-block:: ini
-
-    [default]
-    key                      = INDXEN
-    minimum_snr              = 0.0
-    resolution_fwhm          = -1
-    compute_sigma_correction = True
-    artifact_mask            = BADSKY
-    absorption_indices       = EXTINDX
-    bandhead_indices         = BHBASIC
+.. include:: ../tables/spectralindicesdef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/modules/makecube.rst b/docs/modules/makecube.rst
index 5e5788a3..1fd44068 100644
--- a/docs/modules/makecube.rst
+++ b/docs/modules/makecube.rst
@@ -6,9 +6,11 @@ DAP LOGCUBE File Construction
 
 *Analysis class*: :class:`~mangadap.dapfits.construct_cube_file`
 
-*File Root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[PLATE]/[IFUDESIGN]``
+*File Root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.method_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[METHOD]/[PLATE]/[IFUDESIGN]`` for MaNGA
 
-*File Template*: ``manga-[PLATE]-[IFUDESIGN]-LOGCUBE-[DAPTYPE].fits.gz``
+*File Template*: see :class:`~mangadap.dapfits.construct_maps_file.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-LOGCUBE-[METHOD].fits.gz`` for MaNGA
 
 *Important class dependencies*:
 
diff --git a/docs/modules/makemaps.rst b/docs/modules/makemaps.rst
index ea785c24..75d3f153 100644
--- a/docs/modules/makemaps.rst
+++ b/docs/modules/makemaps.rst
@@ -6,9 +6,11 @@ DAP MAPS File Construction
 
 *Analysis class*: :class:`~mangadap.dapfits.construct_maps_file`
 
-*File Root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[PLATE]/[IFUDESIGN]``
+*File Root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.method_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/[METHOD]/[PLATE]/[IFUDESIGN]`` for MaNGA
 
-*File Template*: ``manga-[PLATE]-[IFUDESIGN]-MAPS-[DAPTYPE].fits.gz``
+*File Template*: see :class:`~mangadap.dapfits.construct_maps_file.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-MAPS-[METHOD].fits.gz`` for MaNGA
 
 *Important class dependencies*:
 
diff --git a/docs/modules/rdx.rst b/docs/modules/rdx.rst
index 7be62697..80b5fece 100644
--- a/docs/modules/rdx.rst
+++ b/docs/modules/rdx.rst
@@ -1,38 +1,21 @@
 
 .. _drp-redux-assessments:
 
-DRP assessments
-===============
+Basic Reduction Assessments
+===========================
 
 *Analysis class*: :class:`~mangadap.proc.reductionassessments.ReductionAssessment`
 
-*Method definition*: :class:`~mangadap.proc.reductionassessments.ReductionAssessmentDef`
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.common_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common/[PLATE]/[IFUDESIGN]`` for MaNGA
 
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common``
+*Reference file*: see :class:`~mangadap.proc.reductionassessments.ReductionAssessment.default_paths`;
+``manga-[PLATE]-[IFUDESIGN]-[RDXQA].fits.gz`` for MaNGA
 
-*Reference file*: ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY].fits.gz``
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.reductionassessments.ReductionAssessmentDef`
 
-*Config files*: ``$MANGADAP_DIR/mangadap/config/reduction_assessments``
-
-*Example config*: ``snrg.ini``
-
-.. code-block:: ini
-
-    # The response function used is that provided by Jim Gunn in June 2001,
-    # found here:
-    #
-    # http://www.sdss3.org/binaries/filter_curves.fits
-    #
-    # and parsed into text files.
-    [Path]
-    dapsrc                 = ${MANGADAP_DIR}
-
-    [default]
-    key                    = SNRG
-    wave_limits
-    response_function_file = ${Path:dapsrc}/data/filter_response/gunn_2001_g_response.db
-    in_vacuum              = True
-    covariance             = True
+.. include:: ../tables/reductionassessmentdef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/modules/stellarkin.rst b/docs/modules/stellarkin.rst
index ce75abb4..b3ac98fa 100644
--- a/docs/modules/stellarkin.rst
+++ b/docs/modules/stellarkin.rst
@@ -6,40 +6,16 @@ Stellar Kinematics
 
 *Analysis class*: :class:`~mangadap.proc.stellarcontinuummodel.StellarContinuumModel`
 
-*Method definition*: :class:`~mangadap.proc.stellarcontinuummodel.StellarContinuumModelDef`
-
-*Reference root*: ``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common``
-
-*Reference file*: ``manga-[PLATE]-[IFUDESIGN]-[DRPQA_KEY]-[BIN_KEY]-[CONTINUUM_KEY].fits.gz``
-
-*Config files*: ``$MANGADAP_DIR/mangadap/config/stellar_continuum_modeling``
-
-*Example config*: ``gau_mileshc.ini``
-
-.. code-block:: ini
-
-    [default]
-    key                    = MILESHCMPL9
-    fit_type               = stellar_kinematics
-    fit_method             = ppxf
-    fit_iter               = nonzero_templates
-    reject_boxcar          = 100
-    filter_boxcar
-    filter_op
-    filter_iter
-    filter_degree
-    filter_mdegree
-    minimum_snr            = 1.0
-    waverange
-    artifact_mask          = BADSKY
-    emission_line_mask     = ELPMPL8
-    template_library       = MILESHC
-    match_resolution       = False
-    velscale_ratio         = 4
-    moments                = 2
-    degree                 = 8
-    mdegree                = -1
-    bias
+*Reference root*: see :class:`~mangadap.config.analysisplan.AnalysisPlan.common_path`;
+``$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER/common/[PLATE]/[IFUDESIGN]`` for MaNGA
+
+*Reference file*: see :class:`~mangadap.proc.stellarcontinuummodel.StellarContinuumModel.default_paths`
+``manga-[PLATE]-[IFUDESIGN]-[RDXQA]-[BINNING]-[CONTINUUM].fits.gz`` for MaNGA
+
+*Optional Parameters*: see :ref:`plan`.  The table below lists the parameters
+defined by :class:`~mangadap.proc.stellarcontinuummodel.StellarContinuumModelDef`
+
+.. include:: ../tables/reductionassessmentdef.rst
 
 *Important class dependencies*:
 
diff --git a/docs/whatsnew.rst b/docs/whatsnew.rst
index a71423eb..1d009c2a 100644
--- a/docs/whatsnew.rst
+++ b/docs/whatsnew.rst
@@ -183,7 +183,7 @@ User-level changes/bug fixes
 
  * Major revision of the front-end to ease incorporation of non-MaNGA
    datacubes.
- * Major restructuring of directories to ease `pypi`_ distribution and
+ * Major restructuring of directories to ease `pip`_ distribution and
    provide a more formal structure of the executables in ``bin`` and
    their associated scripts.
  * Added ``bin/dap_status`` to check the status of a batch DAP run.
diff --git a/mangadap/config/__init__.py b/mangadap/config/__init__.py
index baf4bbcd..af236e24 100644
--- a/mangadap/config/__init__.py
+++ b/mangadap/config/__init__.py
@@ -1,7 +1,8 @@
-import os
-from ..version import version
 
-def get_environ():
+def get_manga_environ():
+
+    import os
+    from ..version import version
 
     # Default versions
     _MANGADRP_VER = 'v3_1_1'
@@ -20,9 +21,37 @@ def get_environ():
         if key in os.environ:
             environ[key] = os.environ[key]
 
-    return environ
+    environ['sdss-access'] = os.environ['SDSS_ACCESS_DIR'].split('/')[-1] \
+                                if 'SDSS_ACCESS_DIR' in os.environ else None
+    environ['idlutils'] = os.environ['IDLUTILS_DIR'].split('/')[-1] \
+                            if 'IDLUTILS_DIR' in os.environ else None
 
-manga_environ = get_environ()
+    return environ
 
-#from .manga import MaNGAAnalysisPlan
+manga_environ = get_manga_environ()
+
+def get_python_versions():
+
+    versions = {}
+    import sys
+    versions['python'] = '.'.join([str(v) for v in sys.version_info[:3]])
+    import numpy
+    versions['numpy'] = numpy.__version__
+    import scipy
+    versions['scipy'] = scipy.__version__
+    import matplotlib
+    versions['matplotlib'] = matplotlib.__version__
+    import astropy
+    versions['astropy'] = astropy.__version__
+    import pydl
+    versions['pydl'] = pydl.__version__
+    import ppxf
+    versions['ppxf'] = ppxf.__version__
+    import vorbin
+    versions['vorbin'] = vorbin.__version__
+    from ..version import version
+    versions['mangadap'] = version
+    return versions
+
+python_versions = get_python_versions()
 
diff --git a/mangadap/config/default_plan.toml b/mangadap/config/default_plan.toml
index e737a137..1279230f 100644
--- a/mangadap/config/default_plan.toml
+++ b/mangadap/config/default_plan.toml
@@ -31,7 +31,7 @@
  artifact_mask      = 'BADSKY'
 
 [default.eline_fits]
- key                  = 'EFITSSP'
+ key                  = 'EFITHC2'
  fit_method           = 'sasuke'
 
 [default.eline_fits.fit]
@@ -45,7 +45,7 @@
  mdegree              = 14
 
 [default.eline_fits.fit.templates]
- key              = 'MASTARSSP'
+ key              = 'MASTARHC2'
 
 [default.indices]
  key                      = 'INDXEN'
diff --git a/mangadap/config/defaults.py b/mangadap/config/defaults.py
index 7f182eaa..8bb4a27c 100644
--- a/mangadap/config/defaults.py
+++ b/mangadap/config/defaults.py
@@ -40,93 +40,3 @@ def sdss_maskbits_file():
     return maskbits_file if maskbits_file.exists() else None
 
 
-# USED BY:
-#   - scripts/dapall_qa.py
-#   - scripts/find_repeat_observations.py
-#   - survey/dapall.py
-def dapall_file(drpver=None, dapver=None, analysis_path=None):
-    """
-    Return the path to the DAPall file.
-
-    Args:
-        drpver (:obj:`str`, optional):
-            DRP version.  Default is to use :func:`drp_version`.
-        dapver (:obj:`str`, optional):
-            DAP version.  Default is to use :func:`dap_version`.
-        analysis_path (:obj:`str`, optional):
-            Path to the root analysis directory.  Default is to use
-            :func:`dap_analysis_path`
-
-    Returns:
-        :obj:`str`: Full path to the DAPall fits file.
-    """
-    _drpver = drp_version() if drpver is None else drpver
-    _dapver = dap_version() if dapver is None else dapver
-    _analysis_path = dap_analysis_path(drpver=_drpver, dapver=_dapver) \
-                        if analysis_path is None else Path(analysis_path).resolve()
-    return _analysis_path / f'dapall-{_drpver}-{_dapver}.fits'
-
-
-# USED BY:
-#   - scripts/rundap.py
-def dap_file_root(plate, ifudesign, mode=None):
-    """
-    Generate the root name of the MaNGA DAP parameter and script
-    files for a given plate/ifudesign/mode.
-    
-    Args:
-        plate (:obj:`int`):
-            Plate number
-        ifudesign (:obj:`int`):
-            IFU design number
-        mode (:obj:`str`, optional):
-            Mode of the DRP reduction; either ``RSS`` or ``CUBE``. If
-            None, the mode is excluded from the file root.
-
-    Returns:
-        :obj:`str`: Root name for the DAP file:
-        ``mangadap-[PLATE]-[IFUDESIGN]`` or
-        ``mangadap-[PLATE]-[IFUDESIGN]-LOG[MODE]``
-    """
-    return f'mangadap-{plate}-{ifudesign}' if mode is None else \
-                f'mangadap-{plate}-{ifudesign}-LOG{mode}'
-
-    
-# USED BY:
-#   - scripts/rundap.py
-def dap_config(plate, ifudesign, drpver=None, dapver=None, analysis_path=None,
-               directory_path=None):
-    """
-    Return the full path to the DAP configuration file.
-
-    The configuration file provides the input data necessary to
-    instantiate a :class:`mangadap.datacube.manga.MaNGADataCube`.
-    
-    Args:
-        plate (:obj:`int`):
-            Plate number
-        ifudesign (:obj:`int`):
-            IFU design number
-        drpver (:obj:`str`, optional):
-            DRP version. Default is to use :func:`drp_version`.
-        dapver (:obj:`str`, optional):
-            DAP version. Default is to use :func:`dap_version`.
-        analysis_path (:obj:`str`, optional): 
-            Path to the root analysis directory. Default is to use
-            :func:`dap_analysis_path`.
-        directory_path (:obj:`str`, optional):
-            Path to the directory with the DAP output files. Default
-            is to use :func:`dap_common_path`
-
-    Returns:
-        :obj:`str`: Full path to the DAP par file
-    """
-    # Make sure the directory path is defined
-    _directory_path = dap_common_path(plate=plate, ifudesign=ifudesign, drpver=drpver,
-                                      dapver=dapver, analysis_path=analysis_path) \
-                            if directory_path is None else Path(directory_path).resolve()
-    # Set the name of the par file; put this in its own function?
-    return _directory_path /  f'{dap_file_root(plate, ifudesign, "CUBE")}.ini'
-
-    
-
diff --git a/mangadap/config/manga.py b/mangadap/config/manga.py
index 4376a7a9..32b8cbd6 100644
--- a/mangadap/config/manga.py
+++ b/mangadap/config/manga.py
@@ -21,7 +21,7 @@
 import numpy
 from astropy.io import fits
 
-#from . import defaults
+from . import defaults
 from . import manga_environ
 from .analysisplan import AnalysisPlan
 from ..util.parser import DefaultConfig
@@ -193,8 +193,12 @@ def plate_target_files():
         string, the second provides the integer catalog index
         determined for each file.
     """
+    core_dir = Path(manga_environ['MANGACORE_DIR']).resolve()
+    if not core_dir.exists():
+        raise ValueError(f'MANGACORE directory does not exist! {core_dir}')
+
     # Default search string
-    search_str = Path(manga_environ['MANGACORE_DIR']).resolve() / 'platedesign' / 'platetargets'
+    search_str =  core_dir / 'platedesign' / 'platetargets'
     file_list = sorted(list(search_path.glob('plateTargets*.par')))
     nfiles = len(file_list)
     if nfiles == 0:
@@ -214,7 +218,7 @@ def redshift_fix_file():
     Returns:
         :obj:`str`: Expected path to the redshift-fix parameter file.
     """
-    return dap_data_root() / 'fix' / 'redshift_fix.par'
+    return defaults.dap_data_root() / 'fix' / 'redshift_fix.par'
 
 
 def photometry_fix_file():
@@ -224,7 +228,78 @@ def photometry_fix_file():
     Returns:
         :obj:`str`: Expected path to the photometry-fix parameter file.
     """
-    return dap_data_root() / 'fix' / 'photometry_fix.par'
+    return defaults.dap_data_root() / 'fix' / 'photometry_fix.par'
+
+
+def parse_plate_ifu_from_file(name):
+    """
+    Parse the plate and ifu numbers from the file name of a MaNGA DRP file.
+    """
+    return tuple([int(n) for n in name.split('-')[1:3]])
+
+
+# USED BY:
+#   - scripts/rundap.py
+def dap_config(plate, ifudesign, drpver=None, dapver=None, analysis_path=None,
+               directory_path=None):
+    """
+    Return the full path to the DAP configuration file.
+
+    The configuration file provides the input data necessary to
+    instantiate a :class:`mangadap.datacube.manga.MaNGADataCube`.
+    
+    Args:
+        plate (:obj:`int`):
+            Plate number
+        ifudesign (:obj:`int`):
+            IFU design number
+        drpver (:obj:`str`, optional):
+            DRP version. Default is to use :func:`drp_version`.
+        dapver (:obj:`str`, optional):
+            DAP version. Default is to use :func:`dap_version`.
+        analysis_path (:obj:`str`, optional): 
+            Path to the root analysis directory. Default is to use
+            :func:`dap_analysis_path`.
+        directory_path (:obj:`str`, optional):
+            Path to the directory with the DAP output files. Default
+            is to use :func:`dap_common_path`
+
+    Returns:
+        :obj:`str`: Full path to the DAP par file
+    """
+    # Make sure the directory path is defined
+    _directory_path = dap_common_path(plate=plate, ifudesign=ifudesign, drpver=drpver,
+                                      dapver=dapver, analysis_path=analysis_path) \
+                            if directory_path is None else Path(directory_path).resolve()
+    # Set the name of the par file; put this in its own function?
+    return _directory_path /  f'{dap_file_root(plate, ifudesign, mode="CUBE")}.ini'
+
+
+# USED BY:
+#   - scripts/dapall_qa.py
+#   - scripts/find_repeat_observations.py
+#   - survey/dapall.py
+def dapall_file(drpver=None, dapver=None, analysis_path=None):
+    """
+    Return the path to the DAPall file.
+
+    Args:
+        drpver (:obj:`str`, optional):
+            DRP version.  Default is to use :func:`drp_version`.
+        dapver (:obj:`str`, optional):
+            DAP version.  Default is to use :func:`dap_version`.
+        analysis_path (:obj:`str`, optional):
+            Path to the root analysis directory.  Default is to use
+            :func:`dap_analysis_path`
+
+    Returns:
+        :obj:`str`: Full path to the DAPall fits file.
+    """
+    _drpver = drp_version() if drpver is None else drpver
+    _dapver = dap_version() if dapver is None else dapver
+    _analysis_path = dap_analysis_path(drpver=_drpver, dapver=_dapver) \
+                        if analysis_path is None else Path(analysis_path).resolve()
+    return _analysis_path / f'dapall-{_drpver}-{_dapver}.fits'
 
 
 class MaNGAConfig:
@@ -340,18 +415,21 @@ def check_mode(mode):
         if mode not in options:
             raise ValueError(f'Unknown mode {mode}.  Must be in: {options}')
 
-#    @property
-#    def key(self):
-#        """
-#        Unique key used to identify the input data.
-#        """
-#        return f'{self.plate}-{self.ifudesign}'
-
     @property
     def output_root(self):
-#        return f'{self.instrument}-{self.key}'
         return f'{self.instrument}-{self.plate}-{self.ifudesign}'
 
+    @property
+    def cfg_root(self):
+        """
+        Generate the root name of the MaNGA DAP configuration and script files.
+    
+        Returns:
+            :obj:`str`: Root name for the DAP file:
+            ``mangadap-[PLATE]-[IFUDESIGN]-LOG[MODE]``
+        """
+        return f'mangadap-{self.plate}-{self.ifudesign}-LOG{self.mode}'
+
     @staticmethod
     def propagate_flags():
         """Flags that should be propagated from the observed data to the analysis data."""
@@ -604,6 +682,9 @@ def write_config(ofile, plate, ifudesign, log=True, z=None, vdisp=None, ell=None
             raise FileExistsError(f'{_ofile} already exists; to overwrite, set '
                                   'overwrite=True.')
 
+        if not _ofile.parent.exists():
+            _ofile.parent.mkdir(parents=True)
+
         # Build the configuration data
         cfg = ConfigParser(allow_no_value=True)
         cfg['default'] = {'drpver': drpver,
@@ -636,9 +717,13 @@ class MaNGAAnalysisPlan(AnalysisPlan):
     This class only redefines the output paths from the base class.
     """
     def __init__(self, planlist, cube=None, analysis_path=None):
-        if cube is None:
-            raise ValueError('For a MaNGA analysis plan, you must provide the cube because it '
-                             'is used to define the output paths.')
+#        if cube is None:
+#            raise ValueError('For a MaNGA analysis plan, you must provide the cube because it '
+#                             'is used to define the output paths.')
+        self.cube = cube
+        if self.cube is None:
+            warnings.warn('Instantiation of the MaNGA analysis plan did not include the cube.  '
+                          'Directories will be *undefined*, meaning the code may fault.')
         if analysis_path is None:
             analysis_path = dap_analysis_path(drpver=cube.drpver)
         super().__init__(planlist, cube=cube, analysis_path=analysis_path)
@@ -655,6 +740,9 @@ def common_path(self):
         Returns:
             `Path`_: Path object for the "common" output
         """
+        if self.cube is None:
+            raise ValueError('Path undefined because cube being processed was not provided '
+                             'when MaNGAAnalysisPlan was instantiated!')
         return self.analysis_path / 'common' / str(self.cube.plate) / str(self.cube.ifudesign)
 
     def method_path(self, plan_index=0, qa=False, ref=False):
@@ -682,6 +770,9 @@ def method_path(self, plan_index=0, qa=False, ref=False):
                 Raised if the plan index is invalid or if both qa and ref are true.
 
         """
+        if self.cube is None:
+            raise ValueError('Path undefined because cube being processed was not provided '
+                             'when MaNGAAnalysisPlan was instantiated!')
         if plan_index < 0 or plan_index >= self.nplans:
             raise ValueError(f'Invalid index ({plan_index}); 0 <= index < {self.nplans}.')
         if qa and ref:
diff --git a/mangadap/scripts/__init__.py b/mangadap/scripts/__init__.py
index 32b6fcbd..37a5e337 100644
--- a/mangadap/scripts/__init__.py
+++ b/mangadap/scripts/__init__.py
@@ -14,7 +14,7 @@
 from . import manga_dap
 from . import plate_fit_qa
 from . import ppxffit_qa
-#from . import rundap
+from . import rundap
 from . import spotcheck_dap_maps
 from . import template_flux_norm
 from . import write_dap_config
diff --git a/mangadap/scripts/construct_dapall.py b/mangadap/scripts/construct_dapall.py
index ba26eb34..bffc199e 100644
--- a/mangadap/scripts/construct_dapall.py
+++ b/mangadap/scripts/construct_dapall.py
@@ -16,8 +16,6 @@ def get_parser(cls, width=None):
         parser = super().get_parser(description='Compile metadata for the DAPall file',
                                     width=width)
 
-        parser.add_argument('--plan_file', type=str, help='parameter file with the MaNGA DAP '
-                            'execution plan to use instead of the default' , default=None)
         parser.add_argument('--drpver', type=str, help='DRP version', default=None)
         parser.add_argument('-r', '--redux_path', type=str,
                             help='Top-level directory with the DRP products; defaults to '
@@ -26,8 +24,11 @@ def get_parser(cls, width=None):
         parser.add_argument('-a', '--analysis_path', type=str, default=None,
                             help='Top-level output directory for the DAP results; defaults to '
                                 '$MANGA_SPECTRO_ANALYSIS/$MANGADRP_VER/$MANGADAP_VER')
-        parser.add_argument('-m', '--methods', type=str, nargs='+', default=None,
-                            help='Only include output from this DAP method designation in the '
+        parser.add_argument('--plan_file', type=str, help='toml file with the MaNGA DAP '
+                            'execution plan to use instead of the default' , default=None)
+
+        parser.add_argument('-m', '--methods', type=str, nargs='*', default=None,
+                            help='Only include output for these DAP method designations in the '
                                  'output')
         parser.add_argument('-v', '--verbose', action='count', default=0,
                             help='Set verbosity level; can be omitted and set up to -vv')
@@ -44,18 +45,18 @@ def main(args):
         import time
 
         from mangadap.util.log import init_DAP_logging, module_logging
-        from mangadap.par.analysisplan import AnalysisPlanSet
+        from mangadap.config.manga import MaNGAAnalysisPlan
         from mangadap.survey.dapall import DAPall
 
         t = time.perf_counter()
-        analysisplan = AnalysisPlanSet.default() if args.plan_file is None \
-                            else AnalysisPlanSet.from_par_file(args.plan_file)
+        plan = manga.MaNGAAnalysisPlan.default() if args.plan_file is None \
+                            else manga.MaNGAAnalysisPlan.from_toml(args.plan_file)
 
         # Initialize the logging objects and start the log
         init_DAP_logging(None)#, simple_warnings=False)
         loggers = module_logging(__name__, args.verbose)
 
-        DAPall(analysisplan, methods=args.methods, drpver=args.drpver, redux_path=args.redux_path,
+        DAPall(plan, methods=args.methods, drpver=args.drpver, redux_path=args.redux_path,
                dapver=args.dapver, analysis_path=args.analysis_path, loggers=loggers,
                quiet=args.quiet, single_precision=args.single_precision)
 
diff --git a/mangadap/scripts/dapall_qa.py b/mangadap/scripts/dapall_qa.py
index 0538b6f5..058875df 100644
--- a/mangadap/scripts/dapall_qa.py
+++ b/mangadap/scripts/dapall_qa.py
@@ -1,7 +1,6 @@
-import os
+
+from pathlib import Path
 import time
-import argparse
-import warnings
 
 from IPython import embed
 
@@ -14,11 +13,8 @@
 
 from mangadap.util.bitmask import BitMask
 from mangadap.dapfits import DAPQualityBitMask
-from mangadap.config import defaults
+from mangadap.config import defaults, manga
 from mangadap.proc.util import growth_lim
-from mangadap.proc.spatiallybinnedspectra import SpatiallyBinnedSpectra
-from mangadap.proc.stellarcontinuummodel import StellarContinuumModel
-from mangadap.proc.emissionlinemodel import EmissionLineModel
 from mangadap.config.analysisplan import AnalysisPlan
 from mangadap.util.fileio import channel_dictionary
 
@@ -36,32 +32,31 @@ def init_ax(fig, pos, facecolor=None, grid=False):
 
 
 def dapall_qa(drpall, dapall, analysis_path, daptype, eml, spi):
-    oroot = os.path.join(analysis_path, daptype, 'qa')
-    if not os.path.isdir(oroot):
-        os.makedirs(oroot)
+    # TODO: HARDCODED!
+    oroot = analysis_path / daptype / 'qa'
+    if not oroot.exists():
+        oroot.mkdir(parents=True)
 
     # Plot H-delta A equivalent width versus D4000
-    ew_d4000(drpall, dapall, daptype, eml, spi, ofile=os.path.join(oroot, 'dapall_ew_d4000.png'))
+    ew_d4000(drpall, dapall, daptype, eml, spi, ofile=str(oroot / 'dapall_ew_d4000.png'))
 
     # Plot the star-formation, mass diagram
-    mass_lha(drpall, dapall, daptype, eml, ofile=os.path.join(oroot, 'dapall_mass_lha.png'))
+    mass_lha(drpall, dapall, daptype, eml, ofile=str(oroot / 'dapall_mass_lha.png'))
 
     # Plot the approximate Faber-Jackson relation
-    mass_sigma(drpall, dapall, daptype, ofile=os.path.join(oroot, 'dapall_mass_sigma.png'))
+    mass_sigma(drpall, dapall, daptype, ofile=str(oroot / 'dapall_mass_sigma.png'))
 
     # Plot the approximate Tully-Fisher relation
-    velocity_gradient(drpall, dapall, daptype, eml,
-                      ofile=os.path.join(oroot, 'dapall_mass_vel.png'))
+    velocity_gradient(drpall, dapall, daptype, eml, ofile=str(oroot / 'dapall_mass_vel.png'))
 
     # Plot the MgFe and Hbeta absorption indices
-    mgfe_hbeta(drpall, dapall, daptype, spi, ofile=os.path.join(oroot, 'dapall_mgfe_hbeta.png'))
+    mgfe_hbeta(drpall, dapall, daptype, spi, ofile=str(oroot / 'dapall_mgfe_hbeta.png'))
 
     # Plot the radial coverage of each cube
-    radial_coverage_histogram(dapall, daptype,
-                              ofile=os.path.join(oroot, 'dapall_radialcoverage.png'))
+    radial_coverage_histogram(dapall, daptype, ofile=str(oroot / 'dapall_radialcoverage.png'))
 
     # Plot the redshift differences
-    redshift_distribution(dapall, daptype, ofile=os.path.join(oroot, 'dapall_redshift_dist.png'))
+    redshift_distribution(dapall, daptype, ofile=str(oroot / 'dapall_redshift_dist.png'))
 
 
 def ew_d4000(drpall, dapall, daptype, eml, spi, ofile=None):
@@ -516,7 +511,7 @@ def radial_coverage_histogram(dapall, daptype, ofile=None):
 
     # Determine the sample
     sdssMaskbits = defaults.sdss_maskbits_file()
-    targ1bm = BitMask.from_par_file(sdssMaskbits, 'MANGA_TARGET1')
+    targ1bm = BitMask.from_par_file(str(sdssMaskbits), 'MANGA_TARGET1')
 
     ancillary = dapall['MNGTARG3'][indx] > 0
     primaryplus = targ1bm.flagged(dapall['MNGTARG1'][indx],
@@ -706,7 +701,7 @@ def get_parser(cls, width=None):
         parser.add_argument("--plan_file", type=str, help="parameter file with the MaNGA DAP "
                             "execution plan to use instead of the default" , default=None)
 
-        parser.add_argument('--daptype', type=str, help='DAP processing type', default=None)
+#        parser.add_argument('--daptype', type=str, help='DAP processing type', default=None)
         parser.add_argument('--normal_backend', dest='bgagg', action='store_false', default=True)
 
         return parser
@@ -718,39 +713,29 @@ def main(args):
         if args.bgagg:
             pyplot.switch_backend('agg')
 
-        # Set the paths
-        redux_path = defaults.drp_redux_path(drpver=args.drpver) \
-                            if args.redux_path is None else args.redux_path
-        analysis_path = defaults.dap_analysis_path(drpver=args.drpver, dapver=args.dapver) \
-                                if args.analysis_path is None else args.analysis_path
-
-        daptypes = []
-        if args.daptype is None:
-            analysisplan = AnalysisPlanSet.default() if args.plan_file is None \
-                                else AnalysisPlanSet.from_par_file(args.plan_file)
-            for p in analysisplan:
-                bin_method = SpatiallyBinnedSpectra.define_method(p['bin_key'])
-                sc_method = StellarContinuumModel.define_method(p['continuum_key'])
-                el_method = EmissionLineModel.define_method(p['elfit_key'])
-                daptypes += [defaults.dap_method(bin_method['key'],
-                                                 sc_method['fitpar']['template_library_key'],
-                                                 el_method['continuum_tpl_key'])]
-        else:
-            daptypes = [args.daptype]
-
-        drpall_file = defaults.drpall_file(drpver=args.drpver, redux_path=redux_path)
-        dapall_file = defaults.dapall_file(drpver=args.drpver, dapver=args.dapver,
+        redux_path = manga.drp_redux_path(drpver=args.drpver) \
+                            if args.redux_path is None else Path(args.redux_path).resolve()
+        analysis_path = manga.dap_analysis_path(drpver=args.drpver, dapver=args.dapver) \
+                            if args.analysis_path is None else Path(args.analysis_path).resolve()
+
+        plan = manga.MaNGAAnalysisPlan.default(analysis_path=analysis_path) \
+                    if args.plan_file is None \
+                    else manga.MaNGAAnalysisPlan.from_toml(args.plan_file,
+                                                           analysis_path=analysis_path)
+
+        drpall_file = manga.drpall_file(drpver=args.drpver, redux_path=redux_path)
+        dapall_file = manga.dapall_file(drpver=args.drpver, dapver=args.dapver,
                                            analysis_path=analysis_path)
 
-        drpall_hdu = fits.open(drpall_file)
-        dapall_hdu = fits.open(dapall_file)
+        drpall_hdu = fits.open(str(drpall_file))
+        dapall_hdu = fits.open(str(dapall_file))
 
         eml = channel_dictionary(dapall_hdu, 0, prefix='ELG')
         spi = channel_dictionary(dapall_hdu, 0, prefix='SPI')
 
-        for daptype in daptypes:
-            dapall_qa(drpall_hdu['MANGA'].data, dapall_hdu[daptype].data, analysis_path,
-                      daptype, eml, spi)
+        for i, key in enumerate(plan.keys()):
+            dapall_qa(drpall_hdu['MANGA'].data, dapall_hdu[plan[key]['key']].data, analysis_path,
+                      plan[key]['key'], eml, spi)
 
         print('Elapsed time: {0} seconds'.format(time.perf_counter() - t))
 
diff --git a/mangadap/scripts/fit_residuals.py b/mangadap/scripts/fit_residuals.py
index 5e106e3e..ac2f8d1d 100644
--- a/mangadap/scripts/fit_residuals.py
+++ b/mangadap/scripts/fit_residuals.py
@@ -191,7 +191,7 @@ def get_stellar_continuum_fom_maps(cube, dapmaps):
     return extent, snrg, gmr_data(cube), rms, frms, rchi2, chi_grw
 
 
-def get_emission_line_data(cube, daptype, dapmaps, dapcube):
+def get_emission_line_data(cube, deconstruct_bins, dapmaps, dapcube):
     if not dapmaps.exists():
         raise FileNotFoundError('{0} does not exist.'.format(dapmaps))
     if not dapcube.exists():
@@ -202,20 +202,23 @@ def get_emission_line_data(cube, daptype, dapmaps, dapcube):
     maps_hdu = fits.open(str(dapmaps))
 
     # Get the observed spectra and errors
-    if 'HYB' in daptype:
+    if deconstruct_bins:
         # Fits are to unbinned spectra from DRP datacube
         npix = cube.flux.shape[-1]
-        flux = cube.copy_to_masked_array(attr='flux').T
-        error = numpy.sqrt(inverse(cube.copy_to_masked_array(attr='ivar').T))
+        flux = cube.copy_to_masked_array(attr='flux')
+        error = numpy.sqrt(inverse(cube.copy_to_masked_array(attr='ivar')))
         snrg = maps_hdu['SPX_SNR'].data.ravel()
+        # Fix the ordering
+        flux = flux.reshape(cube.flux.shape).T.reshape(npix,-1)
+        error = error.reshape(cube.flux.shape).T.reshape(npix,-1)
 
     else:
         # Fits are to binned spectra from DAP datacube
         npix = cube_hdu['FLUX'].data.shape[0]
-        flux = numpy.ma.MaskedArray(cube_hdu['FLUX'].data.reshape(npix,-1),
-                                    mask=cube_hdu['MASK'].data.reshape(npix,-1) > 0)
-        error = numpy.ma.power(numpy.ma.MaskedArray(cube_hdu['IVAR'].data.reshape(npix,-1),
-                                                mask=cube_hdu['MASK'].data.reshape(npix,-1) > 0),
+        flux = numpy.ma.MaskedArray(cube_hdu['FLUX'].data.T.reshape(npix,-1),
+                                    mask=cube_hdu['MASK'].data.T.reshape(npix,-1) > 0)
+        error = numpy.ma.power(numpy.ma.MaskedArray(cube_hdu['IVAR'].data.T.reshape(npix,-1),
+                                                mask=cube_hdu['MASK'].data.T.reshape(npix,-1) > 0),
                                -0.5)
         snrg = maps_hdu['BIN_SNR'].data.ravel()
    
@@ -790,9 +793,10 @@ def fit_residuals(cube, plan, method_dir, ref_dir, qa_dir, fwhm=None):
              fit='sc', ofile=ofile)
 
     # Emission-line fit qa plot
+    deconstruct_bins = plan['eline_fits']['fit']['deconstruct_bins'] != 'ignore'
     ofile_root = f'{cube.output_root}-LOGCUBE-{plan["key"]}-el-fitqa'
     wave, flux, error, model, snrg, rms, frms, rchi2 \
-            = get_emission_line_data(cube, plan["key"], maps_file, cube_file)
+            = get_emission_line_data(cube, deconstruct_bins, maps_file, cube_file)
     ofile = qa_dir / f'{ofile_root}-lambda.png'
     fom_lambda(cube.name, wave, flux, error, model, fit='el', ofile=ofile)
     ofile = qa_dir / f'{ofile_root}-growth.png'
diff --git a/mangadap/scripts/manga_dap_inspector.py b/mangadap/scripts/manga_dap_inspector.py
index 22be79c3..382f8d4d 100644
--- a/mangadap/scripts/manga_dap_inspector.py
+++ b/mangadap/scripts/manga_dap_inspector.py
@@ -536,11 +536,17 @@ def manga_dap_inspector(maps_file, model_file, ext=None, masked_spectra=True):
     _model_emission = select_spectrum(model_emission, pos)
 
     wavelim = [wave[0], wave[-1]]
-    fluxlim = [numpy.ma.amin(_masked_spec)-1, numpy.ma.amax(_masked_spec)+1]
-    continuum_residlim = [numpy.ma.amin(_masked_spec-_model_continuum)-0.1,
-                          numpy.ma.amax(_masked_spec-_model_continuum)+0.1]
-    residlim = [numpy.ma.amin(_masked_spec-_model_spec)-0.1,
-                numpy.ma.amax(_masked_spec-_model_spec)+0.1]
+    fluxlim = [numpy.ma.amin(masked_spec), numpy.ma.amax(masked_spec)]
+    continuum_residlim = [numpy.ma.amin(masked_spec-model_continuum)-0.1,
+                          numpy.ma.amax(masked_spec-model_continuum)+0.1]
+    residlim = [numpy.ma.amin(masked_spec-model_spec)-0.1,
+                numpy.ma.amax(masked_spec-model_spec)+0.1]
+
+#    fluxlim = [numpy.ma.amin(_masked_spec)-1, numpy.ma.amax(_masked_spec)+1]
+#    continuum_residlim = [numpy.ma.amin(_masked_spec-_model_continuum)-0.1,
+#                          numpy.ma.amax(_masked_spec-_model_continuum)+0.1]
+#    residlim = [numpy.ma.amin(_masked_spec-_model_spec)-0.1,
+#                numpy.ma.amax(_masked_spec-_model_spec)+0.1]
 
     spectrum_ax = fig.add_axes([ 0.40, 0.31, 0.58, 0.6 ], facecolor='0.95')
     spectrum_ax.set_xlim(wavelim)
diff --git a/mangadap/scripts/plate_fit_qa.py b/mangadap/scripts/plate_fit_qa.py
index 2e55db03..97e7051b 100644
--- a/mangadap/scripts/plate_fit_qa.py
+++ b/mangadap/scripts/plate_fit_qa.py
@@ -1,7 +1,10 @@
-import os
+
+from pathlib import Path
 import time
-import argparse
 import warnings
+
+from IPython import embed
+
 import numpy
 
 from matplotlib import pyplot, ticker, rc
@@ -9,11 +12,8 @@
 from astropy.io import fits
 
 from mangadap.dapfits import DAPQualityBitMask
-from mangadap.config import defaults
-from mangadap.proc.util import growth_lim
-from mangadap.proc.spatiallybinnedspectra import SpatiallyBinnedSpectra
-from mangadap.proc.stellarcontinuummodel import StellarContinuumModel
-from mangadap.proc.emissionlinemodel import EmissionLineModel
+from mangadap.config import manga
+#from mangadap.proc.util import growth_lim
 from mangadap.config.analysisplan import AnalysisPlan
 from mangadap.util.fileio import channel_dictionary
 
@@ -40,13 +40,15 @@ def is_critical(dapver, analysis_path, daptype, plt):
     critical = numpy.zeros(len(ifus), dtype=bool)
 
     for i,ifu in enumerate(ifus):
-        apath = os.path.join(analysis_path, daptype, str(plt), str(ifu))
-        maps_file = os.path.join(apath, 'manga-{0}-{1}-MAPS-{2}.fits.gz'.format(plt,ifu,daptype))
+        # TODO: Change this to plan.method_path()
+        apath = analysis_path / daptype / str(plt) / str(ifu)
+        # TODO: Change this to construct_maps_file.defaults_paths()
+        maps_file = apath / f'manga-{plt}-{ifu}-MAPS-{daptype}.fits.gz'
 
-        if not os.path.isfile(maps_file):
+        if not maps_file.exists():
             continue
 
-        hdu = fits.open(maps_file)
+        hdu = fits.open(str(maps_file))
         critical[i] = dapqualbm.flagged(hdu[0].header['dapqual'], 'CRITICAL')
 
     return ifus, critical
@@ -68,13 +70,16 @@ def compile_data(dapver, analysis_path, daptype, plt):
     el_rchi = []
 
     for ifu in ifus:
-        apath = os.path.join(analysis_path, daptype, str(plt), str(ifu))
-        maps_file = os.path.join(apath, 'manga-{0}-{1}-MAPS-{2}.fits.gz'.format(plt,ifu,daptype))
 
-        if not os.path.isfile(maps_file):
+        # TODO: Change this to plan.method_path()
+        apath = analysis_path / daptype / str(plt) / str(ifu)
+        # TODO: Change this to construct_maps_file.defaults_paths()
+        maps_file = apath / f'manga-{plt}-{ifu}-MAPS-{daptype}.fits.gz'
+
+        if not maps_file.exists():
             continue
 
-        hdu = fits.open(maps_file)
+        hdu = fits.open(str(maps_file))
 
         # Get the stellar data
 #        uniq, indx = map(lambda x: x[1:], numpy.unique(hdu['BINID'].data[1,:,:].ravel(),
@@ -117,16 +122,17 @@ def compile_data(dapver, analysis_path, daptype, plt):
 def plate_fit_qa(dapver, analysis_path, daptype, plt):
 
     # Make sure the output directory exists
-    plate_qa_dir = os.path.join(analysis_path, daptype, str(plt), 'qa')
-    if not os.path.isdir(plate_qa_dir):
-        os.makedirs(plate_qa_dir)
+    # TODO: HARDCODDED PATH (i.e., this won't work for non-MaNGA data)
+    plate_qa_dir = analysis_path / daptype / str(plt) / 'qa'
+    if not plate_qa_dir.exists():
+        plate_qa_dir.mkdir(parents=True)
 
-    ofile = os.path.join(plate_qa_dir, '{0}-fitqa.png'.format(plt))
+    ofile = plate_qa_dir / f'{plt}-fitqa.png'
 #    ofile = None
 
     # Determine if the analysis is a CRITICAL failure
     c_ifus, critical = is_critical(dapver, analysis_path, daptype, plt)
-    pltifu = ['{0}-{1}'.format(plt, ifu) for ifu in c_ifus]
+    pltifu = [f'{plt}-{ifu}' for ifu in c_ifus]
 
     # Grab the data
     d_ifus, sc_pltifu, sc_snrg, sc_frms, sc_rchi, el_pltifu, el_snrg, el_frms, el_rchi \
@@ -134,8 +140,7 @@ def plate_fit_qa(dapver, analysis_path, daptype, plt):
 
     # Check if there's anything to plot
     if len(sc_pltifu) == 0 & len(el_pltifu) == 0:
-        warnings.warn('No PLATEIFU analysis complete for plate={0}, daptype={1}.'.format(
-                                                                                    plt, daptype))
+        warnings.warn(f'No PLATEIFU analysis complete for plate={plt}, daptype={daptype}.')
         return
 
     # Basic coding check
@@ -272,7 +277,7 @@ def get_parser(cls, width=None):
         parser.add_argument("--plan_file", type=str, help="parameter file with the MaNGA DAP "
                             "execution plan to use instead of the default" , default=None)
 
-        parser.add_argument('--daptype', type=str, help='DAP processing type', default=None)
+#        parser.add_argument('--daptype', type=str, help='DAP processing type', default=None)
         parser.add_argument('--normal_backend', dest='bgagg', action='store_false', default=True)
 
         return parser
@@ -284,27 +289,18 @@ def main(args):
             pyplot.switch_backend('agg')
 
         # Set the paths
-        redux_path = defaults.drp_redux_path(drpver=args.drpver) \
-                            if args.redux_path is None else args.redux_path
-        analysis_path = defaults.dap_analysis_path(drpver=args.drpver, dapver=args.dapver) \
-                            if args.analysis_path is None else args.analysis_path
-
-        daptypes = []
-        if args.daptype is None:
-            analysisplan = AnalysisPlanSet.default() if args.plan_file is None \
-                                else AnalysisPlanSet.from_par_file(args.plan_file)
-            for p in analysisplan:
-                bin_method = SpatiallyBinnedSpectra.define_method(p['bin_key'])
-                sc_method = StellarContinuumModel.define_method(p['continuum_key'])
-                el_method = EmissionLineModel.define_method(p['elfit_key'])
-                daptypes += [defaults.dap_method(bin_method['key'],
-                                                 sc_method['fitpar']['template_library_key'],
-                                                 el_method['continuum_tpl_key'])]
-        else:
-            daptypes = [args.daptype]
-
-        for daptype in daptypes:
-            plate_fit_qa(args.dapver, analysis_path, daptype, args.plate)
+        redux_path = manga.drp_redux_path(drpver=args.drpver) \
+                            if args.redux_path is None else Path(args.redux_path).resolve()
+        analysis_path = manga.dap_analysis_path(drpver=args.drpver, dapver=args.dapver) \
+                            if args.analysis_path is None else Path(args.analysis_path).resolve()
+
+        plan = manga.MaNGAAnalysisPlan.default(analysis_path=analysis_path) \
+                    if args.plan_file is None \
+                    else manga.MaNGAAnalysisPlan.from_toml(args.plan_file,
+                                                           analysis_path=analysis_path)
+
+        for i, key in enumerate(plan.keys()):
+            plate_fit_qa(args.dapver, analysis_path, plan[key]['key'], args.plate)
 
         print('Elapsed time: {0} seconds'.format(time.perf_counter() - t))
 
diff --git a/mangadap/scripts/ppxffit_qa.py b/mangadap/scripts/ppxffit_qa.py
index 0dd22854..ad26f3da 100644
--- a/mangadap/scripts/ppxffit_qa.py
+++ b/mangadap/scripts/ppxffit_qa.py
@@ -508,6 +508,8 @@ def gmr_data(cube, min_frac=0.8):
         return None
 
     flux = cube.copy_to_masked_array()
+    # Fix the ordering
+    flux = flux.reshape(cube.flux.shape).T.reshape(npix,-1)
     gpm = numpy.logical_not(numpy.ma.getmaskarray(flux)).astype(float)
 
     # Wavelengths in these files are in angstroms
@@ -518,7 +520,7 @@ def gmr_data(cube, min_frac=0.8):
     g_wgt = interpolate.interp1d(g_wave[indx], g_eff[indx], bounds_error=False,
                                  fill_value=0.)(cube.wave)[None,:]*gpm
     g_mask = numpy.sum((g_wgt[:, 1:]>0).astype(float) * numpy.diff(cube.wave)[None, :],
-                       axis=1).reshape(cube.spatial_shape) < min_frac*(g_hi-g_lo)
+                       axis=1).reshape(cube.spatial_shape[::-1]) < min_frac*(g_hi-g_lo)
     if numpy.all(g_mask):
         warnings.warn('Datacube has insufficient coverage of the g-band to construct a g-r image.')
         return None
@@ -537,9 +539,9 @@ def gmr_data(cube, min_frac=0.8):
         return None
 
     g = numpy.sum(g_wgt[:, 1:] * flux[:, 1:] * numpy.diff(cube.wave)[None,:],
-                       axis=1).reshape(cube.spatial_shape)
+                       axis=1).reshape(cube.spatial_shape[::-1])
     r = numpy.sum(r_wgt[:, 1:] * flux[:, 1:] * numpy.diff(cube.wave)[None,:],
-                       axis=1).reshape(cube.spatial_shape)
+                       axis=1).reshape(cube.spatial_shape[::-1])
     return -2.5*numpy.ma.log10(numpy.ma.divide(numpy.ma.MaskedArray(g, mask=g_mask),
                                                numpy.ma.MaskedArray(r, mask=r_mask)))
 
diff --git a/mangadap/scripts/rundap.py b/mangadap/scripts/rundap.py
index c616dd16..b3477950 100644
--- a/mangadap/scripts/rundap.py
+++ b/mangadap/scripts/rundap.py
@@ -1,1326 +1,150 @@
-# Licensed under a 3-clause BSD style license - see LICENSE.rst
-# -*- coding: utf-8 -*-
-"""
-Defines the class used to automate the execution of the MaNGA DAP at
-Utah.
 
-Revision history
-----------------
+from IPython import embed
 
-    | **?? Nov 2014**: Architecture setup by J. Brownstein
-        <joelbrownstein@astro.utah.edu>
-    | **18 Nov 2014**: Handover to K. Westfall (KBW) for development.
-        (Attempt to) conform style to `PEP 8`_, except that lines are 99
-        characters long (docstrings still limited to 72 characters).
-        Also (attempt to) conform to docstrings style according to `PEP
-        257`_.
-    | **01 Dec 2014**: (KBW) Committed to SVN
-    | **02 Dec 2014**: (KBW) Changed drpver and idlutilsver to mangaver
-    | **13 Jan 2015**: (KBW) Changed it back, added platetargets
-    | **05 Feb 2015**: (KBW) Change to load module based on MPL
-    | **19 Mar 2015**: (KBW) Major changes to allow for more control
-        over the directory structure containing the DRP data and for the
-        DAP results; following changed in the drpfile and drpcomplete
-        classes.
-    | **27 Aug 2015**: (KBW) Sphinx documentation; prep for MPL-4;
-        accommodate changes in drpcomplete; removed arg as an attribute
-        because it was only used in
-        :func:`mangadap.scripts.rundap._read_arg`; despite not doing
-        anything before, commented out _write_module_commands for the
-        time-being; removed file_root() and parameter_file() functions
-        in favor of adding/using functions from
-        :mod:`mangadap.config.defaults`; version number set to 1_1_0.
-    | **06 Oct 2015**: (KBW) Added functionality to select the types of
-        additional output.  Minor changes to allow for DR13 QA plots.
-    | **22 Oct 2015**: (KBW) Added check that the selected MPL versions match
-        the current environmental versions.  Includes edit to
-        :class:`mangadap.survey.mangampl.MaNGAMPL` to include
-        MANGACORE_VER.  Changed default number of nodes to 9.  Added
-        definition of idl command for use on sciama cluster at the ICG,
-        Portsmouth.
-    | **04 Nov 2015**: (KBW) Added capability to turn off main DAP
-        processing call so that :class:`rundap` can be used to, e.g.,
-        just create the plots for existing data.
-    | **17 Feb 2016**: (KBW) Added try/except block for importing
-        pbs.queue; converted old drpfile to new
-        :class:`mangadap.drpfits.DRPFits`, and drpcomplete to new
-        :class:`mangadap.survey.drpcomplete.DRPComplete`.
-    | **13 May 2016**: (KBW) Substantial changes to accommodate
-        conversion of DAP from IDL to python
-    | **19 May 2016**: (KBW) Added command-line options for including
-        log file
-    | **11 Oct 2016**: (KBW) Version checking moved to
-        :class:`mangadap.survey.mangampl.MaNGAMPL`.
-    | **23 Aug 2017**: (KBW) Include ppxffit QA plots.
-    | **29 Aug 2017**: (KBW) Include DAPall post-processing.
-    | **16 Jan 2019**: (KBW) Major changes: Only one operation mode
-        (daily, all, redo removed; code always run in "redo" mode).
-        Remove old "prior" options.  Setup done using DRPall by default,
-        but option to use mangacore/platetargets.  'CUBE' vs. 'RSS' no
-        longer an option, only analyzes CUBEs.
-    | **23 Jan 2019**: (KBW) Add fit QA plots
+from mangadap.scripts import scriptbase
 
-----
 
-.. include license and copyright
-.. include:: ../include/copy.rst
-
-----
-
-.. include common links, assuming primary doc root is up one directory
-.. include:: ../include/links.rst
-"""
-import sys
-import time
-import shutil
-import os
-import glob
-import warnings
-import argparse
-
-try:
-    import pbs.queue
-except:
-    warnings.warn('Could not import pbs.queue!  Any cluster submission will fail!', ImportWarning)
-
-import numpy
-
-from mangadap.config import defaults
-from mangadap.survey.drpcomplete import DRPComplete
-from mangadap.survey.mangampl import MaNGAMPL
-from mangadap.util.parser import arginp_to_list
-from mangadap.util.fileio import create_symlink
-from mangadap.config.analysisplan import AnalysisPlanSet
-from mangadap.proc.spatiallybinnedspectra import SpatiallyBinnedSpectra
-from mangadap.proc.stellarcontinuummodel import StellarContinuumModel
-from mangadap.proc.emissionlinemodel import EmissionLineModel
-
-
-def parse_args(options=None, return_parser=False):
-
-    # Declare the ArgumentParser object
-    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-
-    # Read the optional run-mode arguments
-    parser.add_argument("--overwrite",
-                        help="if all selected, will run dap for all plates/ifudesigns/modes "
-                        " regardless of state", action="store_true", default=False)
-    parser.add_argument('-v', '--verbose', action='count',
-                        help='Set verbosity level for manga_dap; can be omitted and set up '
-                             'to -vv', default=0)
-    parser.add_argument("--quiet", help="suppress screen output", action="store_true",
-                        default=False)
-    parser.add_argument("--print_version", help="print DAP version and stop",
-                        action="store_true", default=False)
+class RunDap(scriptbase.ScriptBase):
+    """
+    Simple wrapper class for the rundap script.
+    """
+    @classmethod
+    def name(cls):
+        """
+        Return the name of the executable.
+        """
+        return 'rundap'
+
+    @classmethod
+    def get_parser(cls, width=None):
+
+        parser = super().get_parser(description='Perform analysis of integral-field data.',
+                                    width=width)
+
+        # Read the optional run-mode arguments
+        parser.add_argument("--overwrite",
+                            help="if all selected, will run dap for all plates/ifudesigns/modes "
+                            " regardless of state", action="store_true", default=False)
+        parser.add_argument('-v', '--verbose', action='count',
+                            help='Set verbosity level for manga_dap; can be omitted and set up '
+                                 'to -vv', default=0)
+        parser.add_argument("--quiet", help="suppress screen output", action="store_true",
+                            default=False)
+        parser.add_argument("--print_version", help="print DAP version and stop",
+                            action="store_true", default=False)
        
-    # These arguments are used to override default behavior
-    parser.add_argument("--loose", help="Only throw warnings if the versioning is "
-                        "not identically as it should be for the designated MPL",
-                        action="store_true", default=False)
-    parser.add_argument("--mplver", type=str, help="select MPL version to analyze",
-                        default=None)
-    parser.add_argument("--redux_path", type=str, help="main DRP output path", default=None)
-    parser.add_argument("--dapver", type=str,
-                        help="optional output version, different from product version",
-                        default=None)
-    parser.add_argument("--analysis_path", type=str, help="main DAP output path", default=None)
-
-    parser.add_argument("--plan_file", type=str, help="parameter file with the MaNGA DAP "
-                        "execution plan to use instead of the default" , default=None)
-    parser.add_argument("--platelist", type=str, help="set list of plates to reduce",
-                        default=None)
-    parser.add_argument("--ifudesignlist", type=str, help="set list of ifus to reduce",
-                        default=None)
-    parser.add_argument("--list_file", type=str,
-                        help='A file with the list of plates and ifudesigns to analyze',
-                        default=None)
-    parser.add_argument("--combinatorics", help="force execution of all permutations of the "
-                        "provided lists", action="store_true", default=False)
-
-    parser.add_argument('--sres_ext', type=str, default=None,
-                        help='Spectral resolution extension to use.  Default set by '
-                             'MaNGADataCube class.')
-    parser.add_argument('--sres_fill', type=str, default=None,
-                        help='If present, use interpolation to fill any masked pixels in the '
-                             'spectral resolution vectors. Default set by MaNGADataCube '
-                             'class.')
-    parser.add_argument('--covar_ext', type=str, default=None,
-                        help='Use this extension to define the spatial correlation matrix.  '
-                             'Default set by MaNGADataCube class.')
-
-    parser.add_argument("--use_plttargets", action="store_true", default=False,
-                        help="Use platetargets files instead of the DRPall file to "
-                             "generate the DRP complete database")
-    parser.add_argument("--plttargets", type=str, help="path to plateTargets file(s); "
-                        "if provided will force update to drpcomplete fits file", default=None)
-    parser.add_argument('--on_disk', action='store_true', default=False,
-                        help='When using the DRPall file to collate the data for input to '
-                             'the DAP, search for available DRP files on disk instead of '
-                             'using the DRPall file content.')
-
-    parser.add_argument("--log", help="Have the main DAP executable produce a log file",
-                        action="store_true", default=False)
-    parser.add_argument("--no_proc", help="Do NOT perform the main DAP processing steps",
-                        action="store_true", default=False)
-    parser.add_argument("--no_plots", help="Do NOT create QA plots", action="store_true",
-                        default=False)
-    parser.add_argument("--post", help="Create/Submit the post-processing scripts",
-                        action="store_true", default=False)
-    parser.add_argument("--post_plots", action="store_true", default=False,
-                        help="Create/Submit the post-processing plotting scripts")
-
-    parser.add_argument("--dapall", action="store_true", default=False,
-                        help='Wait for any individual plate-ifu processes to finish and then'
-                             'update the DAPall file')
-
-    # Read arguments specific to the cluster submission behavior
-    parser.add_argument("--label", type=str, help='label for cluster job', default=None)
-    parser.add_argument("--nodes", type=int, help='number of nodes to use in cluster',
-                        default=1)
-    parser.add_argument("--cpus", type=int,
-                        help='number of cpus to use per node.  Default is to use all available'
-                             '; otherwise, set to minimum of provided number and number of '
-                             'processors per node', default=None)
-    parser.add_argument("--fast", dest='qos', type=str, help='qos state', default=None)
-    parser.add_argument("--umask", type=str, help='umask bit for cluster job', default='0027')
-    parser.add_argument("--walltime", type=str, help='walltime for cluster job',
-                        default='240:00:00')
-    parser.add_argument("--toughness", dest='hard', action='store_false', default=True,
-                        help='turn off hard keyword for cluster submission')
-
-    parser.add_argument('--create', action='store_true', default=False,
-                        help='use the pbs package to create the cluster scripts')
-    parser.add_argument('--submit', action='store_true', default=False,
-                        help='submit the scripts to the cluster')
-    parser.add_argument('--progress', action='store_true', default=False,
-                        help='instead of closing the script, report the progress of the '
-                             'analysis on the cluster; this is required if you want to submit '
-                             'the DAPall script immediately after completing the individual '
-                             'cube analysis')
-
-    parser.add_argument("--queue", dest='queue', type=str, help='set the destination queue',
-                        default=None)
-
-    if return_parser:
+        # These arguments are used to override default behavior
+        parser.add_argument("--drpver", type=str, default=None,
+                            help='MaNGA DRP version for analysis; $MANGADRP_VER by default')
+        parser.add_argument("--redux_path", type=str, help="main DRP output path", default=None)
+        parser.add_argument("--dapver", type=str, default=None,
+                            help='optional output version, different from product version.  This '
+                                 '*only* affects the output directory structure.  It does *not* '
+                                 'select the version of the DAP to use.')
+        parser.add_argument("--analysis_path", type=str, help="main DAP output path", default=None)
+
+        parser.add_argument("--plan_file", type=str, help="parameter file with the MaNGA DAP "
+                            "execution plan to use instead of the default" , default=None)
+        parser.add_argument("--platelist", type=str, help="set list of plates to reduce",
+                            default=None)
+        parser.add_argument("--ifudesignlist", type=str, help="set list of ifus to reduce",
+                            default=None)
+        parser.add_argument("--list_file", type=str,
+                            help='A file with the list of plates and ifudesigns to analyze',
+                            default=None)
+        parser.add_argument("--combinatorics", help="force execution of all permutations of the "
+                            "provided lists", action="store_true", default=False)
+
+        parser.add_argument('--sres_ext', type=str, default=None,
+                            help='Spectral resolution extension to use.  Default set by '
+                                 'MaNGADataCube class.')
+        parser.add_argument('--sres_fill', type=str, default=None,
+                            help='If present, use interpolation to fill any masked pixels in the '
+                                 'spectral resolution vectors. Default set by MaNGADataCube '
+                                 'class.')
+        parser.add_argument('--covar_ext', type=str, default=None,
+                            help='Use this extension to define the spatial correlation matrix.  '
+                                 'Default set by MaNGADataCube class.')
+
+        parser.add_argument('--on_disk', action='store_true', default=False,
+                            help='When using the DRPall file to collate the data for input to '
+                                 'the DAP, search for available DRP files on disk instead of '
+                                 'using the DRPall file content.')
+        parser.add_argument('--can_analyze', action='store_true', default=False,
+                            help='Only construct script files for datacubes that can/should be '
+                                 'analyzed by the DAP.  See '
+                                 ':func:`~mangadap.survey.drpcomplete.DRPComplete.can_analyze`.')
+
+        parser.add_argument("--log", help="Have the main DAP executable produce a log file",
+                            action="store_true", default=False)
+        parser.add_argument("--no_proc", help="Do NOT perform the main DAP processing steps",
+                            action="store_true", default=False)
+        parser.add_argument("--no_plots", help="Do NOT create QA plots", action="store_true",
+                            default=False)
+        parser.add_argument("--post", help="Create/Submit the post-processing scripts",
+                            action="store_true", default=False)
+        parser.add_argument("--post_plots", action="store_true", default=False,
+                            help="Create/Submit the post-processing plotting scripts")
+
+        # Read arguments specific to the cluster submission behavior
+        parser.add_argument("--label", type=str, help='label for cluster job', default='mangadap')
+        parser.add_argument("--nodes", type=int, help='number of nodes to use in cluster',
+                            default=1)
+        parser.add_argument("--cpus", type=int,
+                            help='number of cpus to use per node.  Default is to use all available'
+                                 '; otherwise, set to minimum of provided number and number of '
+                                 'processors per node', default=None)
+        parser.add_argument("--fast", dest='qos', type=str, help='qos state', default=None)
+        parser.add_argument("--umask", type=str, help='umask bit for cluster job', default='0027')
+        parser.add_argument("--walltime", type=str, help='walltime for cluster job',
+                            default='240:00:00')
+        parser.add_argument("--toughness", dest='hard', action='store_false', default=True,
+                            help='turn off hard keyword for cluster submission')
+
+        parser.add_argument('--create', action='store_true', default=False,
+                            help='use the pbs package to create the cluster scripts')
+        parser.add_argument('--submit', action='store_true', default=False,
+                            help='submit the scripts to the cluster')
+        parser.add_argument('--progress', action='store_true', default=False,
+                            help='instead of closing the script, report the progress of the '
+                                 'analysis on the cluster; this is required if you want to submit '
+                                 'the DAPall script immediately after completing the individual '
+                                 'cube analysis')
+
+        parser.add_argument("--queue", dest='queue', type=str, help='set the destination queue',
+                            default=None)
         return parser
 
-    return parser.parse_args() if options is None else parser.parse_args(options)
-
-
-class rundap:
-    r"""
-    Class used for automated execution of the MaNGA DAP via submission
-    of scripts to a cluster.
-
-    [ More complete explanation of the class here. ]
-
-    .. note::
-    
-        This is purely a survey-level utility for running the DAP at
-        Utah, including submitting jobs to the cluster.  No user-level
-        usage of the DAP should be considered here, apart from not
-        hindering such usage of the primary python programs!
-
-    .. todo::
-
-        - Have :func:`_read_arg` return the boolean for :attr:`version`
-          instead of keeping it as an attribute.
-        - Ditch ``console`` and :func:`_read_arg` in favor of the
-          more typical ``parser`` and ``main`` pairing.
-
-    Args:
-        overwrite (:obj:`bool`, optional):
-            Overwrite and redo any existing analysis.
-        console (:obj:`bool`, optional):
-            The class has been executed from the terminal with
-            command-line arguments that should be parsed.
-        quiet (:obj:`bool`, optional):
-            Suppress output
-        print_version (:obj:`bool`, optional):
-            Print the class version and return.
-        strictver (:obj:`bool`, optional):
-            Strictly check the version requirements of the dependencies
-            defined in :mangadap.survey.mangampl.MaNGAMPL` for this
-            version of the DAP.  Default is True, meaning the code will
-            raise an exception if the expected version are not the same
-            as the environment.
-        mplver (:obj:`str`, optional):
-            MPL version to analyze.  Used with
-            :class:`mangadap.survey.mangampl.MaNGAMPL` to set the DAP
-            dependencies.  Ideally, this controls the modules that are
-            loaded before execution of the DAP.  The default version
-            (selected if mplver=None) is defined by
-            :class:`mangadap.survey.mangampl.MaNGAMPL`.
-        redux_path (:obj:`str`, optional):
-            The top-level path with the DRP files used to override the
-            default defined by
-            :func:`mangadap.config.defaults.drp_redux_path`.
-        dapver (:obj:`str`, optional):
-            The DAP version to use for the analysis, used to override
-            the default defined by
-            :func:`mangadap.config.defaults.dap_version`.  *Only
-            used to define the file paths, not used to switch to the
-            specified code version!*
-        analysis_path (:obj:`str`, optional):
-            The top-level path for the DAP output files, used to
-            override the default defined by
-            :func:`mangadap.config.defaults.dap_analysis_path`.
-        plan_file (:obj:`str`, optional):
-            Name of the plan file to use for *all* DRP data in this run,
-            used to override the default defined by
-            :func:`mangadap.par.analysisplan.AnalysisPlanSet.default`.
-        platelist (:obj:`int`, :obj:`str`, optional):
-            List of plates to analyze; default is to search the
-            :attr:`redux_path` for any DRP files; see
-            :class:`mangadap.survey.drpcomplete.DRPComplete`.
-        ifudesignlist (:obj:`int`, :obj:`str`, optional):
-            List of ifudesigns to analyze: default is to search the
-            :attr:`redux_path` for any DRP files; see
-            :class:`mangadap.survey.drpcomplete.DRPComplete`.
-        combinatorics (:obj:`bool`, optional): 
-            Use all unique combinations of the entered
-            plate/ifudesign/mode lists to create the full list of DRP
-            files to analyze.
-        list_file (:obj:`str`, optional):
-            File with a list of plates and ifudesigns to analyze. The
-            file must have two columns, with one plate-ifu
-            combination per file row.
-        sres_ext (:obj:`str`, optional):
-            The extension to use when constructing the
-            spectral resolution vectors for the MaNGA datacubes. See
-            :func:`mangadap.datacube.manga.MaNGADataCube.spectral_resolution`.
-        sres_fill (:obj:`bool`, optional):
-            Fill masked spectral-resolution data by simple linear
-            interpolation.
-        covar_ext (:obj:`str`, optional):
-            Extension in the MaNGA DRP CUBE file to use as the single
-            spatial correlation matrix for all wavelength channels.
-        use_platetargets (:obj:`bool`, optional): 
-            Flag to use the plateTargets files, instead of the DRPall
-            file, as the source of the input data need by the DAP.
-        platetargets (:obj:`str`, :obj:`list`, optional):
-            List of platetargets files to search through to find any
-            given plate-ifudesign combination.  Default is returned as
-            the first element in
-            :func:`mangadap.config.defaults.plate_target_files`.
-        on_disk (:obj:`bool`, optional):
-            When searching for available files to analyze, search the
-            DRP directory path instead of using the data in the DRPall
-            file.  This is implicitly true when using the plateTargets
-            files to collate the data used for the DAP input instead of
-            the DRPall file (i.e., when `use_platetargets=True`).
-        log (:obj:`bool`, optional):
-            Flag to create the DAP log files.
-        dapproc (:obj:`bool`, optional):
-            Flag to execute the main DAP processing.
-        pltifu_plots (:obj:`bool`, optional):
-            Create the QA plate-ifu specific plots.
-        post_process (:obj:`bool`, optional):
-            Prepare (and possibly perform) the post processing steps:
-            create the DAPall file and its associated QA plots, and the
-            fit QA plots for each plate.
-        post_plots (:obj:`bool`, optional):
-            Create the post-processing QA plots.
-        report_progress (:obj:`bool`, optional):
-            Report the progress of the analysis.
-        vebose (:obj:`int`, optional):
-            Verbosity level between 0 and 2.
-        label (:obj:`str`, optional):
-            Label to use in cluster queue.
-        nodes (:obj:`int`, optional):
-            Number of cluster nodes to use.
-        qos (:obj:`str`, optional):
-            Select a specific processor (e.g. sdss-fast when the manga
-            user).  This option is ignored if :attr:`q` is set.
-        umask (:obj:`str`, optional):
-            umask to set for output.
-        walltime (:obj:`str`, optional):
-            Maximum wall time for cluster job.
-        hard (:obj:`bool`, optional):
-            Same as hard keyword in cluster submission; see
-            :func:`pbs.queue.commit`.  This keyword is in the
-            **opposite** sense of the "--toughness" command-line
-            options; i.e. including --toughness on the command line sets
-            hard=False.
-        create (:obj:`bool`, optional):
-            Use the pbs package to create the scripts.
-        submit (:obj:`bool`, optional):
-            Submit all jobs to the cluster.
-        queue (:obj:`str`, optional):
-            Name of the destination queue.  When submitting jobs at
-            Utah, this should **not** be set (leaving it at the default
-            of None).  When submitting jobs at Portsmouth, this can be
-            used to select either sciama1.q, cluster.q (default), or
-            sciama3.q.
-
-    Attributes:
-        overwrite (bool): **Only used with :attr:`all`**.  Overwrite and
-            redo any existing analysis.
-        quiet (bool): Suppress output
-        print_version (bool): Print the version and then return
-        strictver (bool): Strictly check the version requirements for
-            this version of the dap.  If True, an exception is raised if
-            the expected version are not the same as the environment.
-        mpl (:class:`mangadap.survey.mangampl.MaNGAMPL`): MPL version;
-            see above.
-        redux_path (str): The top-level path with the DRP files.
-        dapver (str): The DAP version.
-        analysis_path (str): The top-level path for the DAP output
-            files.
-        plan_file (str): Name of the plan file to use for ALL DRP data
-            in this run.  Will be None if using the default plan.
-        plan (:class:`AnalysisPlanSet`): The set of analysis plans
-        daptypes (:obj:`list`): This list of daptypes, one per plan
-        platelist (list): List of plates to analyze.
-        ifudesignlist (list): List of ifudesigns to analyze.
-        combinatorics (bool): Use all unique combinations of the entered
-            plate/ifudesign/mode lists to create the full list of DRP
-            files to analyze.
-        use_platetargets (bool): See above
-        platetargets (list): List of platetargets files to search
-            through to find any given plate-ifudesign combination.
-        on_disk (bool): See above
-        log (str): See above
-        dapproc (bool): Flag to execute the main DAP processing
-        pltifu_plots (bool): Create the QA plate-ifu specific plots
-        post_process (:obj:`bool`): See above
-        post_plots (:obj:`bool`): See above
-        report_progress (:obj:`bool`): See abve
-        label (str): Label to use in cluster queue.
-        nodes (int): Number of cluster nodes to use.
-        qos (str): Specific processor to use (Utah specific)
-        umask (str): umask to set for output.
-        walltime (str): Maximum wall time for cluster job.
-        hard (bool): Same as hard keyword in cluster submission; see
-            :func:`pbs.queue.commit`.
-        create (bool): Use the pbs package to create the submission
-            scripts.
-        submit (bool): Submit all jobs to the cluster.
-        q (str): Queue destination (Portsmouth specific)
-        drpc (:class:`mangadap.survey.drpcomplete.DRPComplete`):
-            Database of the available DRP files and the parameters
-            necessary to write the DAP par files.
-
-    Raises:
-        FileNotFoundError:
-            Raised if a plate-ifu list file is provided but it does not
-            exist, or if the plan file does not exist.
-        ValueError:
-            Raised if all processing steps are turned off, if the
-            selected MPL is undefined; if the fast node is selected with
-            nodes > 1; if multiple run modes are selected; or if the
-            plate list, ifudesign list, or mode list are not provided
-            during a redo mode or if they are provided with any other
-            mode.
-    """
-    def __init__(self, overwrite=None, console=None, quiet=False, print_version=False,
-                 strictver=True, mplver=None, redux_path=None, dapver=None, analysis_path=None,
-                 plan_file=None, platelist=None, ifudesignlist=None, combinatorics=False,
-                 list_file=None, sres_ext=None, sres_fill=None, covar_ext=None,
-                 use_platetargets=False, platetargets=None, on_disk=False, log=False,
-                 dapproc=True, pltifu_plots=True, post_process=False, post_plots=False,
-                 report_progress=False, verbose=0, label='mangadap', nodes=1, cpus=None, qos=None,
-                 umask='0027', walltime='240:00:00', hard=True, create=False, submit=False,
-                 queue=None):
-
-        # Save run-mode options
-        self.overwrite = overwrite
-        self.quiet = quiet
-        self.print_version = print_version
-
-        # Override environment
-        self.strictver = strictver
-        self.mpl = mplver
-        self.redux_path = None if redux_path is None else os.path.abspath(redux_path)
-        self.dapver = dapver
-        self.analysis_path = None if analysis_path is None else os.path.abspath(analysis_path)
-
-        # List of files to analyze
-        self.plan_file = None if plan_file is None else os.path.abspath(plan_file)
-        if list_file is not None and not os.path.isfile(list_file):
-            raise FileNotFoundError('No file: {0}'.format(list_file))
-        if list_file is None:
-            self.platelist = arginp_to_list(platelist, evaluate=True)
-            self.ifudesignlist = arginp_to_list(ifudesignlist, evaluate=True)
-        else:
-            if platelist is not None or ifudesignlist is not None:
-                warnings.warn('Provided file with list of files supercedes other input.')
-            self.platelist, self.ifudesignlist = self._read_file_list(list_file)
-        self.combinatorics = combinatorics
-        self.sres_ext = sres_ext
-        self.sres_fill = sres_fill
-        self.covar_ext = covar_ext
-
-        # Select if plateTargets files should be used to generate the
-        # DRPComplete database, and possibly provide them directly 
-        # TODO: This is likely now outdated
-        self.use_platetargets = use_platetargets
-        self.platetargets = arginp_to_list(platetargets)
-        self.on_disk = on_disk
-
-        # Set the options for output
-        self.log = log
-        self.dapproc = dapproc
-        self.pltifu_plots = pltifu_plots
-        self.post_process = post_process
-        self.post_plots = post_plots
-
-        # Report the progess of the cluster, otherwise just finish
-        # script
-        self.report_progress = report_progress
-        self.verbose = verbose
-
-        # Cluster queue keywords
-        self.label = label
-        self.nodes = nodes
-        self.cpus = cpus
-        self.qos = qos
-        self.umask = umask
-        self.walltime = walltime
-        self.hard = hard
-        self.submit = submit
-
-        self.q = queue
-
-        # Read and parse command-line arguments
-        if console:
-            self._read_arg()
-
-        # Only print the version of the DAP
-        if self.print_version:
-            print('DAP Version: {0}'.format(__version__))
-            return
-
-        # If submitting to the cluster, make sure that scripts will be
-        # created!
-        if not self.create and self.submit:
-            self.create = True
+    @staticmethod
+    def main(args):
 
-        # If setting qos, the number of nodes must be 1
-        if self.qos is not None and self.nodes > 1:
-            raise ValueError('When selecting the fast node, must have nodes=1!')
-
-        # Check processing steps
-        nproc = 0
-        processes = []
-        if self.dapproc:
-            nproc += 1
-            processes += [ 'Main DAP blocks' ]
-        if self.pltifu_plots:
-            nproc += 1
-            processes += [ 'PLATEIFU-specific QA plots' ]
-        if self.post_process:
-            nproc += 1
-            processes += [ 'DAPall' ]
-        if self.post_plots:
-            nproc += 1
-            processes += [ 'DAPall and PLATE-specific QA plots' ]
-        if nproc < 1:
-            raise ValueError('No processing steps requested!')
-
-        if (self.dapproc or self.pltifu_plots) and self.submit and self.post_process \
-                and not self.report_progress:
-            warnings.warn('If both processing and post processing, need to report progress.  '
-                          'Setting to report progress.')
-            self.report_progress = True
-
-        # Make sure the selected MPL version is available
-        try:
-            self.mpl = MaNGAMPL(version=self.mpl, strictver=self.strictver)
-        except Exception as e:
-#            e = sys.exc_info()
-#            print_frame(e[0])
-#            raise ValueError('MPL is undefined: {0}'.format(e[1]))
-            raise ValueError('MPL is undefined!') from e
-
-        self.dapver = defaults.dap_version() if self.dapver is None else self.dapver
-
-        # Set the output paths
-        self.redux_path = defaults.drp_redux_path(self.mpl.drpver) if self.redux_path is None \
-                                    else os.path.abspath(self.redux_path)
-        self.analysis_path = defaults.dap_analysis_path(self.mpl.drpver, self.dapver) \
-                                    if self.analysis_path is None \
-                                    else os.path.abspath(self.analysis_path)
-
-        # Set the subdirectory from which the scripts are sourced and
-        # the various log files are written.  Currently based on start
-        # UTC; e.g., analysis_path/log/19May2016T09.17.42UTC
-        self.calling_path = os.path.join(self.analysis_path, 'log',
-                                         time.strftime('%d%b%YT%H.%M.%SUTC',time.gmtime()))
-
-        # Create the calling path. The existence test is executed, even
-        # though time stamp should mean that calling_path never exists.
-        # TODO: allow argument to set calling_path directly?
-        if not os.path.isdir(self.calling_path):
-            os.makedirs(self.calling_path)
-
-        # Allow for the default plan.
-        if self.plan_file is None:
-            warnings.warn('No analysis plan was provided.  DAP executions will adopt the default '
-                          'plan.')
-        else:
-            if not os.path.isfile(self.plan_file):
-                raise FileNotFoundError('No file: {0}'.format(self.plan_file))
-            _plan_file = self.plan_file
-            self.plan_file = os.path.join(self.calling_path, os.path.split(self.plan_file)[1])
-            if not os.path.isfile(self.plan_file):
-                shutil.copy2(_plan_file, self.plan_file)
-
-        # Construct the DAPTYPEs and check that the plans yeild a fully
-        # unique list
-        # TODO: This should probably be check done in AnalysisPlanSet
-        self.plan = AnalysisPlanSet.default() if self.plan_file is None \
-                        else AnalysisPlanSet.from_par_file(self.plan_file)
-        self.daptypes = []
-        for p in self.plan:
-            bin_method = SpatiallyBinnedSpectra.define_method(p['bin_key'])
-            sc_method = StellarContinuumModel.define_method(p['continuum_key'])
-            el_method = None if p['elfit_key'] is None \
-                                else EmissionLineModel.define_method(p['elfit_key'])
-            self.daptypes += [defaults.dap_method(bin_method['key'],
-                                                  sc_method['fitpar']['template_library_key'],
-                                                  'None' if el_method is None else
-                                                        el_method['continuum_tpl_key'])]
-        if len(numpy.unique(self.daptypes)) != self.plan.nplans:
-            raise ValueError('{0} do not yield unique DAPTYPEs.'.format(
-                                'Default plans' if self.plan_file is None 
-                                    else 'Plans in {0} '.format(self.plan_file)))
-
-        # Alert the user of the versions to be used
-        if self.q is not None:
-            print('Attempting to submit to queue: {0}'.format(self.q))
-        print('Versions: DAP:{0}, {1}'.format(self.dapver, self.mpl.mplver))
-        self.mpl.show()
-        self.mpl.verify_versions(quiet=self.quiet)
-        print('Paths:')
-        print('      REDUX: {0}'.format(self.redux_path))
-        print('   ANALYSIS: {0}'.format(self.analysis_path))
-        print('    CALLING: {0}'.format(self.calling_path))
-        if self.plan_file is not None:
-            print('  PLAN FILE: {0}'.format(self.plan_file))
-        print('Processing steps added to scripts:')
-        print('        {0}'.format(processes))
-
-        # Create and update the DRPComplete file if necessary
-        self.drpc = DRPComplete(platetargets=self.platetargets, drpver=self.mpl.drpver,
-                                redux_path=self.redux_path, dapver=self.dapver,
-                                analysis_path=self.analysis_path)
-        
-        # Update the DRPComplete list; force an update if platetarget
-        # files are provided
-        self.drpc.update(platelist=self.platelist, ifudesignlist=self.ifudesignlist,
-                         combinatorics=self.combinatorics, force=(self.platetargets is not None),
-                         use_platetargets=self.use_platetargets, on_disk=self.on_disk)
-
-        # Hereafter, self.platelist and self.ifuplatelist are the INPUT
-        # values, whereas self.drpc.platelist and self.drpc.ifuplatelist
-        # are the actual DRP reductions to analyze (including
-        # combinatorics and file existence tests).  See, e.g.,
-        # selected_drpfile_list().
-
-        # Find the rows in the DRPComplete database with the DRP file
-        # meta data
-        drpc_rows = self._selected_drpfile_rows()
-
-        print('Number of DRP files to process: {0}'.format(len(drpc_rows)))
-        if len(drpc_rows) == 0:
-            # If coded correctly, this function should never return here.
-            return
-
-        # Construct the post-processing scripts before
-        # constructing/submitting datacube processing scripts
-        if self.post_process or self.post_plots:
-            post_queue = self._build_post_queue(drpc_rows)
-
-        # Process the relevant datacubes
-        if self.dapproc or self.pltifu_plots:
-            # Create the script files for the set of drpfiles, and
-            # submit the scripts to the queue if self.submit is true
-            proc_queue = self._build_proc_queue(drpc_rows)
-
-            # Submit the queue to the cluster
-            if self.create:
-                proc_queue.commit(hard=self.hard, submit=self.submit)
-
-            # If not reporting progress, done
-            if not self.report_progress:
-                return
-
-            # Wait until the queue is finished
-            running = True
-            while running and self.submit:
-                time.sleep(60) 
-                percent_complete = proc_queue.get_percent_complete()
-                print('Percent complete ... {0:.1f}%'.format(percent_complete), end='\r')
-                if percent_complete == 100:
-                    running = False
-            print('Percent complete ... {0:.1f}%'.format(percent_complete))
-
-        if not self.post_process and not self.post_plots:
-            # Don't perform the post-processing
+        if args.print_version:
+            from mangadap import __version__
+            print(f'DAP Version: {__version__}')
             return
 
-        # Submit the post-processing queue to the cluster
-        if self.create:
-            post_queue.commit(hard=self.hard, submit=self.submit)
-
-    # ******************************************************************
-    #  UTILITY FUNCTIONS
-    # ******************************************************************
-    def _read_file_list(self, list_file):
-        db = numpy.genfromtxt(list_file, dtype=object)
-        if db.shape[1] != 2:
-            raise ValueError('Input file should contain 2 columns, plate and ifu.')
-        return db[:,0].astype(int).tolist(), db[:,1].astype(int).tolist()
-
-    def _read_arg(self):
-        """Read and interpret the terminal command-line arguments.
-
-        Function uses the argparse module, which provide a --help option
-        to list all available arguments.  The arguments mimic those
-        required by the class constructor.
-        """
-        arg = parse_args()
-
-        ################################################################
-        # Assign properties based on arguments; the suitability of these
-        # arguments is checked in __init__()
-
-        # Run-mode options
-        # Will OVERWRITE existing input from __init__()
-        if arg.overwrite is not None:
-            self.overwrite = arg.overwrite
-        if arg.quiet is not None:
-            self.quiet = arg.quiet
-        if arg.print_version is not None:
-            self.print_version = arg.print_version
-
-        # Set the versions to use
-        # Will OVERWRITE existing input from __init__()
-        self.strictver = not arg.loose
-        if arg.mplver is not None:
-            self.mpl = arg.mplver
-        if arg.redux_path is not None:
-            self.redux_path = arg.redux_path
-        if arg.dapver is not None:
-            self.dapver = arg.dapver
-        if arg.analysis_path is not None:
-            self.analysis_path = arg.analysis_path
-
-        if arg.plan_file is not None:
-            self.plan_file = os.path.abspath(arg.plan_file)
-
-        if arg.list_file is not None and not os.path.isfile(arg.list_file):
-            raise FileNotFoundError('No file: {0}'.format(arg.list_file))
-
-        if arg.list_file is None:
-            if arg.platelist is not None:
-                self.platelist = arginp_to_list(arg.platelist, evaluate=True)
-            if arg.ifudesignlist is not None:
-                self.ifudesignlist = arginp_to_list(arg.ifudesignlist, evaluate=True)
-        else:
-            if arg.platelist is not None or arg.ifudesignlist is not None:
-                warnings.warn('Provided file with list of files supercedes other input.')
-            self.platelist, self.ifudesignlist = self._read_file_list(arg.list_file)
-
-        self.combinatorics = arg.combinatorics
-        self.sres_ext = arg.sres_ext
-        self.sres_fill = arg.sres_fill
-        self.covar_ext = arg.covar_ext
-   
-        # Set the plateTargets and NSA catalog path
-        if arg.plttargets is not None:
-            self.platetargets = arginp_to_list(arg.plttargets)
-        self.use_platetargets = arg.use_plttargets
-        self.on_disk = arg.on_disk
-
-        # Overwrite any existing output options
-        self.log = arg.log
-        self.dapproc = not arg.no_proc
-        self.pltifu_plots = not arg.no_plots
-        self.post_process = arg.post
-        self.post_plots = arg.post_plots
-        if self.verbose < arg.verbose:
-            self.verbose = arg.verbose
-
-        # Set to report the progress of the cluster.  This is needed if
-        # waiting to submit the DAPall construction script
-        self.report_progress = arg.progress
-
-        # Set queue keywords
-        if arg.label is not None:
-            self.label = arg.label
-        if arg.nodes is not None:
-            self.nodes = arg.nodes
-        if arg.cpus is not None:
-            self.cpus = arg.cpus
-        if arg.qos is not None:
-            self.qos = arg.qos
-            self.nodes = 1          # Force the number of nodes to be 1
-        if arg.umask is not None:
-            self.umask = arg.umask
-        if arg.walltime is not None:
-            self.walltime = arg.walltime
-        if arg.hard is not None:
-            self.hard = arg.hard
-        if arg.create is not None:
-            self.create = arg.create
-        if arg.submit is not None:
-            self.submit = arg.submit
-
-        # Specify the destination queue
-        if arg.queue is not None:
-            self.q = arg.queue
-
-    def _check_paths(self, plate, ifudesign):
-        """
-        Check if the output paths exists for a given plate and ifudesign.
-        If not, create them.  The necessary output paths are:
-
-            - The calling path that contains a copy of the plan, the
-              script input and output files, and the touch files
-            - The main common path defined by
-              :func:`mangadap.config.defaults.dap_common_path`.
-            - The plan-based subdirectories based on the plan file
-
-        Args:
-            plate (:obj:`int`):
-                Plate number
-            ifudesign (:obj:`int`):
-                ifudesign number
-        """
-        # Generate the calling path for this plate/ifudesign
-        path = os.path.join(self.calling_path, str(plate), str(ifudesign))
-        if not os.path.isdir(path):
-            os.makedirs(path)
-
-        # Generate the main common path
-        path = defaults.dap_common_path(plate=plate, ifudesign=ifudesign, drpver=self.mpl.drpver,
-                                        dapver=self.dapver, analysis_path=self.analysis_path)
-        if not os.path.isdir(path):
-            os.makedirs(path)
-
-        # Check the reference directories, which creates the full plan
-        # path if necessary
-        for daptype in self.daptypes:
-            path = defaults.dap_method_path(daptype, plate=plate, ifudesign=ifudesign, ref=True,
-                                            drpver=self.mpl.drpver, dapver=self.dapver,
-                                            analysis_path=self.analysis_path)
-            if not os.path.isdir(path):
-                os.makedirs(path)
-
-
-    def _create_queue(self):
-        """
-        Create a queue instance.
-        
-        This function is written to be functional for submission to the
-        cluster queues both at Utah and using the sciama cluster at
-        Portsmouth.  The available queues are:
-
-        +-----------+--------+------+-----------+
-        |     Queue |  Nodes |  PPN |   GB/core |
-        +===========+========+======+===========+
-        | sciama1.q |     80 |   12 |         2 |
-        +-----------+--------+------+-----------+
-        | sciama2.q |     96 |   16 |         4 |
-        +-----------+--------+------+-----------+
-        | sciama3.q |     48 |   20 |       3.2 |
-        +-----------+--------+------+-----------+
+        from mangadap.survey.rundap import rundap
 
-        One should **not** submit to sciama3.q without permission from
-        Will Percival.  The default queue is cluster.q (sciama2.q); so
-        basically only use :attr:`q` to select sciama1.q.
-
-        Submissions to Utah should have their queue destination set to
-        None.  However, the fast node can be selected using :attr:`qos`.
-        """
-        # Instantiate
-        queue = pbs.queue(verbose=not self.quiet)
-
-        if self.q == 'ember':
-            # Submitting to Utah ember cluster
-            ppn = 12
-            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
-            walltime = self.walltime if int(self.walltime.split(':')[0]) < 72 else '72:00:00'
-            queue.create(label=self.label, nodes=self.nodes, qos=self.qos, umask=self.umask,
-                         walltime=walltime, ppn=ppn, cpus=cpus, partition='ember', alloc='sdss')
-        elif self.q is not None:
-            # All other self.q values expected for Portsmouth cluster,
-            # sciama.  In this case, the number of nodes is queue
-            # dependent, and qos is not set
-            if self.q == 'sciama1.q':
-                ppn = 12
-            elif self.q == 'sciama3.q':
-                ppn = 20
-            else:
-                ppn = 16
-            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
-            queue.create(label=self.label, nodes=self.nodes, umask=self.umask,
-                         walltime=self.walltime, queue=self.q, ppn=ppn, cpus=cpus)
+        # Handle special argument cases
+        if args.qos is not None and args.nodes > 1:
+            warnings.warn('Requesting the fast node requires node=1.  Ignoring input node number.')
+            nodes = 1               # Force the number of nodes to be 1
         else:
-            # self.q can be None when submitting to both the Portsmouth
-            # and Utah clusters.  In this case, the default queue
-            # destination and ppn is correct.  qos is also set, but this
-            # should only be used when submitting to Utah.
-            ppn = 16
-            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
-            queue.create(label=self.label, nodes=self.nodes, qos=self.qos, umask=self.umask,
-                         walltime=self.walltime, ppn=ppn, cpus=cpus)
-
-        return queue
-
-    # Files:
-    #       - mangadap-{plate}-{ifudesign}-LOG{mode} = script file = *
-    #       - *.ready = script/directory is ready for queue submission
-    #       - *.queued = script submitted to queue
-    #       - *.started = script assigned a node/processor and has started running
-    #       - *.done = completed execution of the full script
-    # Other:
-    #       - *.par = parameter file
-    #       - *.out, *.err = stdout and stderr output from the script
-    def _build_proc_queue(self, drpc_rows):
-        """
-        Create a queue instance, write the script files for all CPUs
-        using :func:`prepare_for_analysis` and append the job to the
-        queue, and then submit the jobs to the cluster queue.  If
-        :attr:`submit` is False, this is essentially a wrapper for
-        :func:`prepare_for_analysis`.
-
-        This function is written to be functional for submission to the
-        cluster queues both at Utah and using the sciama cluster at
-        Portsmouth.  The available queues are:
-
-        +-----------+--------+------+-----------+
-        |     Queue |  Nodes |  PPN |   GB/core |
-        +===========+========+======+===========+
-        | sciama1.q |     80 |   12 |         2 |
-        +-----------+--------+------+-----------+
-        | sciama2.q |     96 |   16 |         4 |
-        +-----------+--------+------+-----------+
-        | sciama3.q |     48 |   20 |       3.2 |
-        +-----------+--------+------+-----------+
-
-        One should **not** submit to sciama3.q without permission from
-        Will Percival.  The default queue is cluster.q (sciama2.q); so
-        basically only use :attr:`q` to select sciama1.q.
-
-        Submissions to Utah should have their queue destination set to
-        None.  However, the fast node can be selected using :attr:`qos`.
-
-        .. todo::
-            - This algorithm is slow because it has to search through
-              the drpcomplete fits file each time to find the
-              plate/ifudesign combo.  Should make this more efficient.
-
-        Args:
-            drpc_rows (`numpy.ndarray`):
-                A vector with the rows in the
-                :class:`mangadap.survey.drpcomplete.DRPComplete`
-                database with the DRP files to analyze.
-        """
-        # If submitting to the queue, create the queue object.  The
-        # creation of the queue object is system dependent; however,
-        # once created, the queue object is treated identically for both
-        # the Utah and Portsmouth clusters.
-        if self.create:
-            queue = self._create_queue()
-       
-        # Create the script files, regardless of whether or not they are
-        # submitted to the queue, appending the scripts to the queue if
-        # they are to be submitted
-        ndrp = len(drpc_rows)
-        for i, index in enumerate(drpc_rows):
-            # Write the compute script (write_compute_script also checks
-            # the path exists!)
-            scriptfile, stdoutfile, stderrfile \
-                    = self.write_compute_script(index, dapproc=self.dapproc,
-                                                plots=self.pltifu_plots, overwrite=self.overwrite)
-
-            # Set the status to ready
-            plate = self.drpc['PLATE'][index]
-            ifudesign = self.drpc['IFUDESIGN'][index]
-            self.set_pltifu_status(plate, ifudesign, status='ready')
-
-            if self.create:
-                queue.append('source {0}'.format(scriptfile), outfile=stdoutfile,
-                                  errfile=stderrfile)
-                self.set_pltifu_status(plate, ifudesign, status='queued')
-            else:
-                print('Preparing for analysis...{0:.1f}%'.format((i+1)*100/ndrp), end='\r')
-
-        if not self.create:
-            print('Preparing for analysis...{0:.1f}%'.format(100))
-
-        return queue if self.create else None
-
-    def _build_post_queue(self, drpc_rows):
-        """
-        Build the queue that performs the post-processing steps.
-        """
-        # Create the queue
-        if self.create:
-            queue = self._create_queue()
-
-        if self.post_process:
-            # Write the DAPall script and set its status
-            dapall_scr, dapall_out, dapall_err \
-                    = self.write_dapall_script(plots=self.post_plots, overwrite=self.overwrite)
-            self.set_status(dapall_scr, status='ready')
-            if self.create:
-                # Add it to the queue
-                queue.append('source {0}'.format(dapall_scr), outfile=dapall_out,
-                             errfile=dapall_err)
-                self.set_status(dapall_scr, status='queued')
-
-        # Write the plate QA plot scripts
-        if self.post_plots:
-            # Find the unique plates
-            plates = numpy.unique(self.drpc['PLATE'][drpc_rows])
-            for plt in plates:
-                # Write the plot script for this plate
-                scriptfile, stdoutfile, stderrfile \
-                        = self.write_plate_qa_script(plt, overwrite=self.overwrite)
-                self.set_status(scriptfile, status='ready')
-                
-                if self.create:
-                    queue.append('source {0}'.format(scriptfile), outfile=stdoutfile,
-                                 errfile=stderrfile)
-                    self.set_status(scriptfile, status='queued')
-
-        return queue if self.create else None
-
-    # ******************************************************************
-    # Management
-    # ******************************************************************
-    def set_pltifu_status(self, plate, ifudesign, mode=None, status='queued'):
-        """
-        Generate a touch file that signifies the status for a given
-        plate/ifudesign/mode.  The root of the touch file is set by
-        :func:`mangadap.config.defaults.dap_file_root`.
-
-        Args:
-            plate (int): Plate number
-            ifudesign (int): IFU design number
-            mode (str): (**Optional**) DRP 3D mode, 'RSS' or 'CUBE'
-            status (str): (**Optional**) That status signifier for the
-                touch file.
-        """
-        # Get the name of the status file        
-        root = defaults.dap_file_root(plate, ifudesign, mode=mode)
-        self.set_status(os.path.join(self.calling_path, str(plate), str(ifudesign), root),
-                        status)
-        
-    def set_status(self, root, status='queued'):
-        """
-        Generate a general touch file.
-
-        Args:
-            root (str): Full path and root name of the touch file
-            status (str): (**Optional**) That status signifier for the
-                touch file.
-        """
-        # Touch the file by opening and closing it
-        file = open('{0}.{1}'.format(root, status), 'w')
-        file.close()
-
-    def _selected_drpfile_rows(self):
-        """
-        Find the rows in the
-        :class:`mangadap.survey.drpcomplete.DRPComplete` database with
-        the selected plates and IFUs to analyze.
-
-        Returns:
-            `numpy.ndarray`: A vector with the rows in the DRPComplete
-            object with the relevant DRP file data.
-        """
-        # Find the rows in the DRPComplete database with the specified
-        # plate and IFUS
-
-        # All available PLATEIFUs
-        drpc_pltifu = numpy.array(['{0}-{1}'.format(p,i) 
-                                    for p,i in zip(self.drpc['PLATE'], self.drpc['IFUDESIGN'])])
-        # The PLATEIFUs to analyze
-        this_pltifu = numpy.array(['{0}-{1}'.format(p,i) 
-                                    for p,i in zip(self.drpc.platelist, self.drpc.ifudesignlist)])
-
-        # The rows with the PLATEIFUs to analyze
-        rows = numpy.array([numpy.where(drpc_pltifu == pi)[0][0] if pi in drpc_pltifu else -1 \
-                                for pi in this_pltifu])
-        if numpy.any(rows < 0):
-            raise ValueError('Should be able to find all plates and ifus.')
-        return rows
-
-    def write_compute_script(self, index, dapproc=True, plots=True, overwrite=False,
-                              relative_symlink=True):
-        """
-        Write the MaNGA DAP script file that is sent to a single CPU to
-        analyze a single DRP file with a given plate and ifudesign.
-
-        Args:
-            index (:obj:`int`, optional):
-                Row in the
-                :class:`mangadap.survey.drpcomplete.DRPComplete`
-                database with the DRP file to analysis.
-            dapproc (:obj:`bool`, optional):
-                Flag to execute the main DAP processing.
-            plots (:obj:`bool`, optional):
-                Create the QA plots.
-            overwrite (:obj:`bool`, optional):
-                Flag to overwrite any existing files.
-            relative_symlink (:obj:`bool`, optional):
-                Set the symlink to the par file to be a relative path
-                instead of the absolute path.
-
-        Returns:
-            :obj:`str`: Three strings with the name of the written
-            script file, the file for the output sent to STDOUT, and the
-            file for the output sent to STDERR.
-        """
-        # Get the plate and IFU from the DRPComplete database; alway use
-        # the CUBE file
-        plate = self.drpc['PLATE'][index]
-        ifudesign = self.drpc['IFUDESIGN'][index]
-        mode = 'CUBE'
-
-        # Check that the path exists, creating it if not
-        # TODO: More efficient way to do this?
-        self._check_paths(plate, ifudesign)
-
-        # Create the parameter file
-#        parfile = defaults.dap_par_file(plate, ifudesign, mode, drpver=self.mpl.drpver,
-#                                        dapver=self.dapver, analysis_path=self.analysis_path)
-        cfgfile = defaults.dap_config(plate, ifudesign, drpver=self.mpl.drpver, dapver=self.dapver,
-                                      analysis_path=self.analysis_path)
-
-        # Write the par file if it doesn't exist
-        if not os.path.isfile(cfgfile) or overwrite:
-            # overwrite defaults to True
-            self.drpc.write_config(cfgfile, index=index, sres_ext=self.sres_ext,
-                                   sres_fill=self.sres_fill, covar_ext=self.covar_ext)
-            # and create symlinks to it
-            for daptype in self.daptypes:
-                # Generate the ref subdirectory for this plan
-                path = defaults.dap_method_path(daptype, plate=plate, ifudesign=ifudesign,
-                                                ref=True, drpver=self.mpl.drpver,
-                                                dapver=self.dapver,
-                                                analysis_path=self.analysis_path)
-                create_symlink(cfgfile, path, relative_symlink=relative_symlink,
-                               overwrite=overwrite, quiet=True)
-
-        # Set the root path for the scripts, inputs, outputs, and logs
-        _calling_path = os.path.join(self.calling_path, str(plate), str(ifudesign))
-        scr_file_root = defaults.dap_file_root(plate, ifudesign)
-
-        # Set the names for the script, stdout, and stderr files
-        scriptfile = os.path.join(_calling_path, scr_file_root)
-        stdoutfile = '{0}.out'.format(scriptfile)
-        stderrfile = '{0}.err'.format(scriptfile)
-
-        ################################################################
-        # Script file already exists, so just return
-        if os.path.exists(scriptfile) and not overwrite:
-            return scriptfile, stdoutfile, stderrfile
-
-        # Main script components are:
-        #   - (dapproc) Run the DAP processing code
-        #   - (plots) Run the plotting scripts to plot the output
-        # ONE OF THESE 2 MUST BE TRUE
-
-        ################################################################
-        # Open the script file and write the date as a commented header
-        # line
-        file = open(scriptfile, 'w')
-        file.write('# Auto-generated batch file\n')
-        file.write('# {0}\n'.format(time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())))
-        file.write('\n')
-
-        # Create the started touch file
-        startfile = '{0}.started'.format(scriptfile)
-        file.write('touch {0}\n'.format(startfile))
-        file.write('\n')
-
-        # Command that runs the DAP
-        # TODO: Define a "default" plan?
-        if dapproc:
-            command = 'OMP_NUM_THREADS=1 manga_dap -c {0} -r {1} -a {2}'.format(
-                            cfgfile, self.redux_path, self.analysis_path)
-            if self.plan_file is not None:
-                command += ' -p {0}'.format(self.plan_file)
-            if self.log:
-                command += ' --log {0}.log'.format(scriptfile)
-            if self.verbose > 0:
-                command += (' -'+'v'*self.verbose)
-            file.write('{0}\n'.format(command))
-            file.write('\n')
-
-        # Plotting scripts
-        if plots:
-            command = 'OMP_NUM_THREADS=1 dap_ppxffit_qa {0} {1} --analysis_path {2}'.format(
-                            plate, ifudesign, self.analysis_path)
-            if self.plan_file is not None:
-                command += ' --plan_file {0}'.format(self.plan_file)
-            file.write('{0}\n'.format(command))
-            file.write('\n')
-
-            command = 'OMP_NUM_THREADS=1 spotcheck_dap_maps {0} {1} --analysis_path {2}'.format(
-                            plate, ifudesign, self.analysis_path)
-            if self.plan_file is not None:
-                command += ' --plan_file {0}'.format(self.plan_file)
-            file.write('{0}\n'.format(command))
-            file.write('\n')
-
-            command = 'OMP_NUM_THREADS=1 dap_fit_residuals {0} {1} --analysis_path {2}'.format(
-                            plate, ifudesign, self.analysis_path)
-            if self.plan_file is not None:
-                command += ' --plan_file {0}'.format(self.plan_file)
-            file.write('{0}\n'.format(command))
-            file.write('\n')
-
-        # Touch the done file
-        donefile = '{0}.done'.format(scriptfile)
-        file.write('touch {0}\n'.format(donefile))
-        file.write('\n')
-
-        # Done
-        file.close()
-
-        # Return the script file, file for stdout, and file for stderr
-        return scriptfile, stdoutfile, stderrfile
-    
-    def write_dapall_script(self, plots=True, overwrite=False):
-        """
-        Write the script used to construct the DAPall file and its
-        associated quality assessment plots.
-
-        Args:
-            plots (:obj:`bool`, optional):
-                Create the QA plots. Default is True.
-            overwrite (:obj:`bool`, optional):
-                Flag to overwrite any existing files.
-
-        Returns:
-            :obj:`str`: Three strings with the name of the written
-            script file, the file for the output sent to STDOUT, and the
-            file for the output sent to STDERR.
-        """
-        # Check that the path exists, creating it if not
-        if not os.path.isdir(self.calling_path):
-            os.makedirs(self.calling_path)
-        
-        # Set the names for the script, stdout, and stderr files
-        scriptfile = os.path.join(self.calling_path, 'build_dapall')
-        stdoutfile = '{0}.out'.format(scriptfile)
-        stderrfile = '{0}.err'.format(scriptfile)
-
-        # Script file already exists, so just return
-        if os.path.exists(scriptfile) and not overwrite:
-            return scriptfile, stdoutfile, stderrfile
-
-        # Open the script file and write the date as a commented header
-        # line
-        file = open(scriptfile, 'w')
-        file.write('# Auto-generated batch file\n')
-        file.write('# {0}\n'.format(time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())))
-        file.write('\n')
-
-        # Create the started touch file
-        startfile = '{0}.started'.format(scriptfile)
-        file.write('touch {0}\n'.format(startfile))
-        file.write('\n')
-
-        # Command that constructs the DAPall file
-        command = 'OMP_NUM_THREADS=1 '
-        command += 'construct_dapall --drpver {0} -r {1} --dapver {2} -a {3}'.format(
-                        self.mpl.drpver, self.redux_path, self.dapver, self.analysis_path)
-        if self.plan_file is not None:
-            command += ' --plan_file {0}'.format(self.plan_file)
-        if self.verbose > 0:
-            command += (' -'+'v'*self.verbose )
-        file.write('{0}\n'.format(command))
-        file.write('\n')
-
-        # Add the plotting commands
-        if plots:
-            command = 'OMP_NUM_THREADS=1 dapall_qa --drpver {0} --redux_path {1}'.format(
-                            self.mpl.drpver, self.redux_path) \
-                        + ' --dapver {0} --analysis_path {1}'.format(self.dapver,
-                                                                     self.analysis_path)
-            if self.plan_file is not None:
-                command += ' --plan_file {0}'.format(self.plan_file)
-            file.write('{0}\n'.format(command))
-            file.write('\n')
-
-        # Touch the done file
-        donefile = '{0}.done'.format(scriptfile)
-        file.write('touch {0}\n'.format(donefile))
-        file.write('\n')
-
-        file.close()
-        ################################################################
-
-        # Return the script file, file for stdout, and file for stderr
-        return scriptfile, stdoutfile, stderrfile
-    
-    def write_plate_qa_script(self, plate, overwrite=False):
-        """
-        Write the script used to create the plate fit QA plot.
-
-        Args:
-            plate (:obj:`int`):
-                The plate number for the plot.
-            overwrite (:obj:`bool`, optional):
-                Flag to overwrite any existing files.
-
-        Returns:
-            :obj:`str`: Three strings with the name of the written
-            script file, the file for the output sent to STDOUT, and the
-            file for the output sent to STDERR.
-        """
-        # Check that the path exists, creating it if not
-        path = os.path.join(self.calling_path, str(plate))
-        if not os.path.isdir(path):
-            os.makedirs(path)
-        
-        # Set the names for the script, stdout, and stderr files
-        scriptfile = os.path.join(self.calling_path, str(plate), '{0}_fitqa'.format(plate))
-        stdoutfile = '{0}.out'.format(scriptfile)
-        stderrfile = '{0}.err'.format(scriptfile)
-
-        # Script file already exists, so just return
-        if os.path.exists(scriptfile) and not overwrite:
-            return scriptfile, stdoutfile, stderrfile
-
-        # Open the script file and write the date as a commented header
-        # line
-        file = open(scriptfile, 'w')
-        file.write('# Auto-generated batch file\n')
-        file.write('# {0}\n'.format(time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())))
-        file.write('\n')
-
-        # Create the started touch file
-        startfile = '{0}.started'.format(scriptfile)
-        file.write('touch {0}\n'.format(startfile))
-        file.write('\n')
-
-        # Command that creates the plots
-        command = 'OMP_NUM_THREADS=1 dap_plate_fit_qa {0} --analysis_path {1}'.format(
-                        plate, self.analysis_path)
-        if self.plan_file is not None:
-            command += ' --plan_file {0}'.format(self.plan_file)
-        file.write('{0}\n'.format(command))
-        file.write('\n')
-
-        # Touch the done file
-        donefile = '{0}.done'.format(scriptfile)
-        file.write('touch {0}\n'.format(donefile))
-        file.write('\n')
-
-        file.close()
-        ################################################################
+            nodes = args.nodes
+
+        _rundap = rundap(overwrite=args.overwrite, quiet=args.quiet, drpver=args.drpver,
+                         redux_path=args.redux_path, dapver=args.dapver,
+                         analysis_path=args.analysis_path, plan_file=args.plan_file,
+                         platelist=args.platelist, ifudesignlist=args.ifudesignlist,
+                         combinatorics=args.combinatorics, list_file=args.list_file,
+                         sres_ext=args.sres_ext, sres_fill=args.sres_fill,
+                         covar_ext=args.covar_ext, on_disk=args.on_disk,
+                         can_analyze=args.can_analyze, log=args.log, dapproc=not args.no_proc,
+                         pltifu_plots=not args.no_plots, post_process=args.post,
+                         post_plots=args.post_plots, report_progress=args.progress,
+                         verbose=args.verbose, label=args.label, nodes=nodes, cpus=args.cpus,
+                         qos=args.qos, umask=args.umask, walltime=args.walltime, hard=args.hard,
+                         create=args.create, submit=args.submit, queue=args.queue)
 
-        # Return the script file, file for stdout, and file for stderr
-        return scriptfile, stdoutfile, stderrfile
-    
 
 
diff --git a/mangadap/survey/dapall.py b/mangadap/survey/dapall.py
index 5b523a1f..98d6a39b 100644
--- a/mangadap/survey/dapall.py
+++ b/mangadap/survey/dapall.py
@@ -24,10 +24,14 @@
 .. include:: ../include/links.rst
 """
 
+from pathlib import Path
 import logging
 import time
 import os
 import warnings
+
+from IPython import embed
+
 import numpy
 
 from scipy import interpolate
@@ -39,27 +43,14 @@
 from astropy.stats import sigma_clip
 
 from .drpcomplete import DRPComplete
-from ..dapfits import DAPMapsBitMask, DAPQualityBitMask
-from ..util.drpbitmask import DRPQuality3DBitMask
-from ..config import defaults
-from ..util.fileio import init_record_array, rec_to_fits_type, channel_dictionary, channel_units
+from ..dapfits import DAPMapsBitMask
+from ..config import manga
+from ..util.fileio import init_record_array, rec_to_fits_type, channel_dictionary
 from ..util.fitsutil import DAPFitsUtil
-from ..util.log import init_DAP_logging, module_logging, log_output
+from ..util.log import log_output
 from ..config.analysisplan import AnalysisPlan
-from ..par.absorptionindexdb import AbsorptionIndexDB
-from ..par.bandheadindexdb import BandheadIndexDB
-from ..par.emissionlinedb import EmissionLineDB
-from ..par.emissionmomentsdb import EmissionMomentsDB
-from ..proc.util import select_proc_method, sample_growth
-from ..proc.spatiallybinnedspectra import SpatiallyBinnedSpectra
-from ..proc.stellarcontinuummodel import StellarContinuumModel
-from ..proc.emissionlinemoments import EmissionLineMomentsDef
-#from ..proc.emissionlinemoments import available_emission_line_moment_databases
-from ..proc.emissionlinemodel import EmissionLineModel, EmissionLineModelDef
-#from ..proc.emissionlinemodel import available_emission_line_modeling_methods
-from ..proc.spectralindices import SpectralIndicesDef #, available_spectral_index_databases
-
-from matplotlib import pyplot
+from ..proc.util import sample_growth
+
 
 class DAPall:
     """
@@ -75,7 +66,7 @@ class DAPall:
     assumes that the DAP analysis of this cube failed.
 
     Args:
-        plan (:class:`mangadap.par.analysisplan.AnalysisPlanSet`):
+        plan (:class:`mangadap.config.analysisplan.AnalysisPlan`):
             The plan object used by the DAP.
         methods (:obj:`str`, :obj:`list`, optional):
             Specify a set of methods in the DAP plan file to include
@@ -85,18 +76,18 @@ class DAPall:
             alphanumeric.
         drpver (:obj:`str`, optional):
             DRP version.  Default determined by
-            :func:`mangadap.config.defaults.drp_version`.
+            :func:`mangadap.config.manga.drp_version`.
         redux_path (:obj:`str`, optional):
             Top-level directory with the DRP products; default is
             defined by
-            :func:`mangadap.config.defaults.drp_redux_path`.
+            :func:`mangadap.config.manga.drp_redux_path`.
         dapver (:obj:`str`, optional):
             DAP version.  Default determined by
-            :func:`mangadap.config.defaults.dap_version`.
+            :func:`mangadap.config.manga.dap_version`.
         analysis_path (:obj:`str`, optional):
             Top-level directory for the DAP output data; default is
             defined by
-            :func:`mangadap.config.defaults.dap_analysis_path`.
+            :func:`mangadap.config.manga.dap_analysis_path`.
         readonly (:obj:`bool`, optional):
             If it exists, open any existing file and disallow any
             modifications of the database. Default is to
@@ -109,7 +100,7 @@ class DAPall:
 
     Raises:
         TypeError: Raised if the input plan is not a
-            :class:`mangadap.par.analysisplan.AnalysisPlanSet` object.
+            :class:`mangadap.config.analysisplan.AnalysisPlan` object.
         FileNotFoundError: Raise if the DRPall or the DRPComplete file
             are not available.
     
@@ -171,18 +162,18 @@ def __init__(self, plan, methods=None, drpver=None, redux_path=None, dapver=None
         self.float_dtype = 'float32' if single_precision else 'float'
 
         # Path definitions
-        self.drpver = defaults.drp_version() if drpver is None else str(drpver)
-        self.redux_path = defaults.drp_redux_path(self.drpver) \
-                                if redux_path is None else str(redux_path)
+        self.drpver = manga.drp_version() if drpver is None else str(drpver)
+        self.redux_path = manga.drp_redux_path(self.drpver) \
+                                if redux_path is None else Path(redux_path).resolve()
 
-        self.dapver = defaults.dap_version() if dapver is None else str(dapver)
-        self.analysis_path = defaults.dap_analysis_path(self.drpver, self.dapver) \
-                                if analysis_path is None else str(analysis_path)
+        self.dapver = manga.dap_version() if dapver is None else str(dapver)
+        self.analysis_path = manga.dap_analysis_path(self.drpver, self.dapver) \
+                                if analysis_path is None else Path(analysis_path).resolve()
 
         # Set the name of the DRPall file
-        self.drpall_file = os.path.join(self.redux_path, 'drpall-{0}.fits'.format(self.drpver))
-        if not os.path.isfile(self.drpall_file):
-            raise FileNotFoundError('{0} does not exist!'.format(self.drpall_file))
+        self.drpall_file = manga.drpall_file(drpver=self.drpver, redux_path=self.redux_path)
+        if not self.drpall_file.exists():
+            raise FileNotFoundError(f'{self.drpall_file} does not exist!')
 
         # Initialize the bitmask
         self.maps_bm = DAPMapsBitMask()
@@ -213,16 +204,16 @@ def __init__(self, plan, methods=None, drpver=None, redux_path=None, dapver=None
         self.readonly = readonly
         if not self.quiet:
             log_output(self.loggers, 1, logging.INFO, '-'*50)
-            log_output(self.loggers, 1, logging.INFO, '{0:^50}'.format('DAPALL DATABASE'))
+            log_output(self.loggers, 1, logging.INFO, f'{"DAPALL DATABASE":^50}')
             log_output(self.loggers, 1, logging.INFO, '-'*50)
-            log_output(self.loggers, 1, logging.INFO, 'DRPall file: {0}'.format(self.drpall_file))
-            log_output(self.loggers, 1, logging.INFO, 'Output path: {0}'.format(self.analysis_path))
-            log_output(self.loggers, 1, logging.INFO, 'Output file: {0}'.format(self.file_name()))
+            log_output(self.loggers, 1, logging.INFO, f'DRPall file: {self.drpall_file}')
+            log_output(self.loggers, 1, logging.INFO, f'Output path: {self.analysis_path}')
+            log_output(self.loggers, 1, logging.INFO, f'Output file: {self.file_name}')
             if self.readonly:
                 log_output(self.loggers, 1, logging.INFO, 'READ-ONLY MODE')
 
         # If the output file already exists, read it
-        if os.path.isfile(self.file_path()):
+        if self.file_path.exists():
             self._read()
 
         # Check the combination of read-only and the read data makes
@@ -237,40 +228,29 @@ def __init__(self, plan, methods=None, drpver=None, redux_path=None, dapver=None
         if self.hdu is None or not self.readonly:
             self.update(plan, methods=methods)
 
-#    def __getitem__(self, key):
-#        return self.hdu['DAPALL'].data[key]
-
     @staticmethod
-    def _emission_line_moment_db_info(key):
-        if key == 'None':
+    def _emission_line_moment_db_info(plan_elmom):
+        if plan_elmom is None:
             return 'None', None
-        db = select_proc_method(key, EmissionLineMomentsDef,
-                                available_func=available_emission_line_moment_databases)
-        return db['passbands'], EmissionMomentsDB.from_key(db['passbands']).channel_names()
+        return plan_elmom['passbands'], plan_elmom.momdb.channel_names()
 
     @staticmethod
-    def _emission_line_db_info(key):
-        if key == 'None':
+    def _emission_line_db_info(plan_elfit):
+        if plan_elfit is None:
             return 'None', None
-        db = select_proc_method(key, EmissionLineModelDef,
-                                available_func=available_emission_line_modeling_methods)
-        return db['emission_lines'], EmissionLineDB.from_key(db['emission_lines']).channel_names()
+        return plan_elfit['fitpar']['emission_lines'], plan_elfit['fitpar'].emldb.channel_names()
 
     @staticmethod
-    def _spectral_index_db_info(key):
-        if key == 'None':
+    def _spectral_index_db_info(plan_sindx):
+        if plan_sindx is None:
             return 0, 'None', 'None', None
-        db = select_proc_method(key, SpectralIndicesDef,
-                                available_func=available_spectral_index_databases)
-        absdb = AbsorptionIndexDB.from_key(db['absindex'])
-        bhddb = BandheadIndexDB.from_key(db['bandhead'])
-        nindx = absdb.size + bhddb.size
-        units = numpy.array(absdb['units'].tolist() + ['']*bhddb.size)
-        channels = absdb.channel_names()
-        channels.update(bhddb.channel_names(offset=absdb.size))
+        nindx = plan_sindx.absdb.size + plan_sindx.bhddb.size
+        units = numpy.array(plan_sindx.absdb['units'].tolist() + ['']*plan_sindx.bhddb.size)
+        channels = plan_sindx.absdb.channel_names()
+        channels.update(plan_sindx.bhddb.channel_names(offset=plan_sindx.absdb.size))
         if len(channels) != nindx:
             raise ValueError('Spectral index channel names not unique!')
-        return db['absindex'], db['bandhead'], channels, units
+        return plan_sindx['absindex'], plan_sindx['bandhead'], channels, units
 
     # TODO: Fix this!
     def _read(self):
@@ -285,7 +265,7 @@ def _read(self):
         # Read the file
         if not self.quiet:
             log_output(self.loggers, 1, logging.INFO, 'Reading exiting file')
-        self.hdu = fits.open(self.file_path())
+        self.hdu = fits.open(str(self.file_path))
 
         # Methods are the extension names
         self.methods = [h.name for h in self.hdu][1:]
@@ -307,16 +287,13 @@ def _read(self):
 
         # Report
         if not self.quiet:
-            log_output(self.loggers, 1, logging.INFO, 'Rows per method: {0}'.format(self.ndap))
-            log_output(self.loggers, 1, logging.INFO, 'Methods: {0}'.format(self.methods))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Number of observations: {0}'.format(len(self.plate)))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Number of emission-line moments: {0}'.format(self.nmom))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Number of emission lines: {0}'.format(self.neml))
+            log_output(self.loggers, 1, logging.INFO, f'Rows per method: {self.ndap}')
+            log_output(self.loggers, 1, logging.INFO, f'Methods: {self.methods}')
+            log_output(self.loggers, 1, logging.INFO, f'Number of observations: {len(self.plate)}')
             log_output(self.loggers, 1, logging.INFO,
-                       'Number of spectral indices: {0}'.format(self.nindx))
+                       f'Number of emission-line moments: {self.nmom}')
+            log_output(self.loggers, 1, logging.INFO, f'Number of emission lines: {self.neml}')
+            log_output(self.loggers, 1, logging.INFO, f'Number of spectral indices: {self.nindx}')
 
     def _init_string_lengths(self):
         return { 'PLATEIFU':12, 
@@ -461,9 +438,8 @@ def _get_completed_observations(self):
         # Initialize the DRP complete file (read-only)
         drpc = DRPComplete(readonly=True, drpver=self.drpver, redux_path=self.redux_path,
                            dapver=self.dapver, analysis_path=self.analysis_path)
-        if not os.path.isfile(drpc.file_path()):
-            raise FileNotFoundError('Could not access DRP complete file: {0}'.format(
-                                                                                drpc.file_path()))
+        if not drpc.file_path.exists():
+            raise FileNotFoundError(f'Could not access DRP complete file: {drpc.file_path}')
     
         # Find the list of files that should have been analyzed
         indx = drpc.can_analyze()
@@ -486,9 +462,10 @@ def _construct_maps_file_list(self, method):
         Construct the list of MAPS files that should be in/added to the
         DAPall file for a given method.
         """
+        # TODO: HARDCODED !!
         # Construct the file names
-        return numpy.array(['{0}/{1}/{2}/{3}/manga-{2}-{3}-MAPS-{1}.fits.gz'.format(
-                                self.analysis_path, method, p, i) 
+        return numpy.array([self.analysis_path / method / str(p) / str(i)
+                                / f'manga-{p}-{i}-MAPS-{method}.fits.gz'
                                     for p,i in zip(self.plate, self.ifudesign)])
     
     def _add_channels_to_header(self, hdr, channels, prefix, comment, units=None):
@@ -501,16 +478,16 @@ def _add_channels_to_header(self, hdr, channels, prefix, comment, units=None):
         keys = numpy.array(list(channels.keys()))[srt]
         ndig = int(numpy.log10(ch[-1]))+1
         for c,k in zip(ch,keys):
-            hdr[prefix+'{0}'.format(c+1).zfill(ndig)] = (k, comment)
+            hdr[prefix+f'{c+1}'.zfill(ndig)] = (k, comment)
             if units is not None:
-                hdr[prefix+'U'+'{0}'.format(c+1).zfill(ndig)] = (units[c], comment+' unit')
+                hdr[prefix+'U'+f'{c+1}'.zfill(ndig)] = (units[c], comment+' unit')
         return hdr
 
     def _srd_snr_metric(self, dapmaps):
         """Grab the SNR metrics from the MAPS headers."""
         filters = [ 'G', 'R', 'I', 'Z' ]
-        return numpy.array([dapmaps['PRIMARY'].header['SNR{0}MED'.format(f)] for f in filters]), \
-                numpy.array([dapmaps['PRIMARY'].header['SNR{0}RING'.format(f)] for f in filters])
+        return numpy.array([dapmaps['PRIMARY'].header[f'SNR{f}MED'] for f in filters]), \
+                numpy.array([dapmaps['PRIMARY'].header[f'SNR{f}RING'] for f in filters])
 
     @staticmethod
     def _radial_coverage_metric(r, ell):
@@ -619,7 +596,7 @@ def _stellar_kinematics_metrics(self, dapmaps, redshift):
                                 or numpy.isclose(wsum, 0.) \
                             else numpy.ma.sum(bin_flux[center]*z[center])/wsum
         if not numpy.isfinite(stellar_z):
-            raise ValueError('Stellar_z is not finite: {0}'.format(stellar_z))
+            raise ValueError(f'Stellar_z is not finite: {stellar_z}')
 
         # Get the growth ranges of the stellar velocity
         stellar_vel_lo, stellar_vel_hi = sample_growth(svel.compressed(), [0.025,0.975])
@@ -699,7 +676,7 @@ def _halpha_kinematics_metrics(self, dapmaps, deconstruct, redshift):
                                 or numpy.isclose(wsum, 0.) \
                             else numpy.ma.sum(flux[center]*z[center])/wsum
         if not numpy.isfinite(halpha_z):
-            raise ValueError('H-alpha z is not finite: {0}'.format(halpha_z))
+            raise ValueError(f'H-alpha z is not finite: {halpha_z}')
 
 #        halpha_z = -999. if numpy.all(flux.mask[center]) or numpy.isclose(wsum, 0.) \
 #                            else numpy.ma.sum(flux[center]*z[center])/wsum
@@ -722,7 +699,7 @@ def _halpha_kinematics_metrics(self, dapmaps, deconstruct, redshift):
                                         or numpy.isclose(wsum, 0.) \
                                     else numpy.ma.sum(flux[within_1re]*sig2corr[within_1re])/wsum
         if not numpy.isfinite(halpha_gsigma2_1re):
-            raise ValueError('H-alpha gsigma^2 is not finite: {0}'.format(halpha_gsigma2_1re))
+            raise ValueError(f'H-alpha gsigma^2 is not finite: {halpha_gsigma2_1re}')
         halpha_gsigma_1re = numpy.sqrt(halpha_gsigma2_1re) if halpha_gsigma2_1re > 0 else -999.
 
         # Get the high-growth of the H-alpha velocity dispersion
@@ -855,16 +832,16 @@ def _spectral_index_metrics(self, dapmaps, deconstruct, specindex_units):
 
         return specindex_lo, specindex_hi, specindex_lo_clip, specindex_hi_clip, specindex_1re
 
+    @property
     def file_name(self):
         """Return the name of the DRP complete database."""
-        name = defaults.dapall_file(drpver=self.drpver, dapver=self.dapver,
-                                    analysis_path=self.analysis_path)
-        return os.path.split(name)[1]
+        return self.file_path.name
 
+    @property
     def file_path(self):
         """Return the full pat to the DRP complete database."""
-        return defaults.dapall_file(drpver=self.drpver, dapver=self.dapver,
-                                    analysis_path=self.analysis_path)
+        return manga.dapall_file(drpver=self.drpver, dapver=self.dapver,
+                                 analysis_path=self.analysis_path)
 
     def method_database(self, method, deconstruct, drpall):
         """
@@ -891,8 +868,7 @@ def method_database(self, method, deconstruct, drpall):
         # Add the basic information
         db['PLATE'] = self.plate
         db['IFUDESIGN'] = self.ifudesign
-        db['PLATEIFU'] = numpy.array(['{0}-{1}'.format(p,i) 
-                                            for p,i in zip(self.plate, self.ifudesign)])
+        db['PLATEIFU'] = numpy.array([f'{p}-{i}' for p,i in zip(self.plate, self.ifudesign)])
         # For now only analyzing cubes!
         db['MODE'][:] = 'CUBE'
         db['DAPTYPE'][:] = method
@@ -910,15 +886,15 @@ def method_database(self, method, deconstruct, drpall):
 
         # Itereate through each maps file
         for i in range(self.ndap):
-            print('Processing {0}; observation {1}/{2}'.format(method, i+1,self.ndap))#, end='\r')
+            print(f'Processing {method}; observation {i+1}/{self.ndap}')
 
             # Find the index in the drpall file
             indx = numpy.where(drpall['PLATEIFU'] == db['PLATEIFU'][i])[0]
             if len(indx) > 1:
-                warnings.warn('More than one entry in DRPall file: {0}.'.format(db['PLATEIFU'][i]))
+                warnings.warn(f'More than one entry in DRPall file: {db["PLATEIFU"][i]}.')
             if len(indx) != 1:
                 indx = [-1]
-                warnings.warn('Could not find {0} in DRPall file!'.format(db['PLATEIFU'][i]))
+                warnings.warn(f'Could not find {db["PLATEIFU"][i]} in DRPall file!')
             db['DRPALLINDX'][i] = indx[0]
 
             # Add data from the DRPall file
@@ -933,11 +909,11 @@ def method_database(self, method, deconstruct, drpall):
                                         if db['NSA_Z'][i] > 0 else -999.
 
             # Open the maps file
-            if not os.path.isfile(dapfiles[i]):
+            if not dapfiles[i].exists():
                 db['DAPDONE'][i] = 0    # A fault occurred such that the MAPS file was not produced
                 continue
             db['DAPDONE'][i] = 1        # DAP successfully produced the file
-            dapmaps = fits.open(dapfiles[i])
+            dapmaps = fits.open(str(dapfiles[i]))
 
             # Add information from the DAP MAPS file header
             for c in columns_from_maps_hdr:
@@ -1037,7 +1013,7 @@ def method_database(self, method, deconstruct, drpall):
                         - 17 + 2*numpy.ma.log10(db['LDIST_Z'][i]) + 2*log_Mpc_in_cm
             db['SFR_TOT'][i] = numpy.ma.power(10, log_halpha_luminosity_tot - 41.27).filled(-999.)
 
-        print('Processing {0}; observation {1}/{1}'.format(method, self.ndap))
+        print(f'Processing {method}; observation {self.ndap}/{self.ndap}')
 
         # Check that all the data is finite.
         found = False
@@ -1046,7 +1022,7 @@ def method_database(self, method, deconstruct, drpall):
                         or numpy.issubdtype(db[name].dtype, numpy.floating)):
                 continue
             if not numpy.all(numpy.isfinite(db[name])):
-                print('All values not finite in column {0} for method {1}'.format(name, method))
+                print(f'All values not finite in column {name} for method {method}')
                 found = True
         if found:
             raise ValueError('All values not finite in DAPall table.')
@@ -1062,7 +1038,7 @@ def update(self, plan, methods=None):
         output files have already been analyzed.
         
         Args:
-            plan (:class:`mangadap.par.analysisplan.AnalysisPlanSet`):
+            plan (:class:`mangadap.config.analysisplan.AnalysisPlan`):
                 The plan object used by the DAP.
             methods (:obj:`str`, :obj:`list`, optional):
                 Specify a set of methods in the DAP plan file to
@@ -1083,20 +1059,15 @@ def update(self, plan, methods=None):
         # Check the input type
         # TODO: Plan isn't really needed.  Could just troll the
         # analysis_path for the directory names...
-        if not isinstance(plan, AnalysisPlanSet):
-            raise TypeError('Input plan must have type AnalysisPlanSet.')
+        if not isinstance(plan, AnalysisPlan):
+            raise TypeError('Input plan must have type AnalysisPlan.')
 
         # Get the full list of available plan methods
         plan_methods = []
         deconstruct = []
-        for p in plan:
-            bin_method = SpatiallyBinnedSpectra.define_method(p['bin_key'])
-            sc_method = StellarContinuumModel.define_method(p['continuum_key'])
-            el_method = EmissionLineModel.define_method(p['elfit_key'])
-            plan_methods += [defaults.dap_method(bin_method['key'],
-                                                 sc_method['fitpar']['template_library_key'],
-                                                 el_method['continuum_tpl_key'])]
-            deconstruct += [el_method['deconstruct_bins'] != 'ignore']
+        for i, key in enumerate(plan.keys()):
+            plan_methods += [plan[key]['key']]
+            deconstruct += [plan[key]['eline_fits']['fit']['deconstruct_bins'] != 'ignore']
 
         # Check that the plan methods are unique.  This should never be
         # raised, unless it also causes problems in the DAP itself.
@@ -1107,7 +1078,8 @@ def update(self, plan, methods=None):
         if methods is None:
             self.methods = numpy.array(plan_methods)
             self.deconstruct = numpy.array(deconstruct)
-            use_plans = numpy.ones(len(plan_methods), dtype=bool)
+            plan_indx = numpy.arange(plan.nplans)
+#            use_plans = numpy.ones(len(plan_methods), dtype=bool)
         else:
             # Make sure the provided list is unique
             if len(numpy.unique(methods)) != len(methods):
@@ -1120,20 +1092,22 @@ def update(self, plan, methods=None):
             # TODO: Re-write this as a list compression
             self.methods = []
             self.deconstruct = []
-            use_plans = numpy.zeros(len(plan_methods), dtype=bool)
-            for i in range(len(plan_methods)):
+            plan_indx = []
+#            use_plans = numpy.zeros(len(plan_methods), dtype=bool)
+            for i, key in enumerate(plan.keys()):
                 if plan_method[i] in methods:
-                    use_plans[i] = True
+                    plan_indx += [i]
                     self.methods += [plan_method[i]]
                     self.deconstruct += [deconstruct[i]]
             self.methods = numpy.array(self.methods)
             self.deconstruct = numpy.array(self.deconstruct)
+            plan_indx = numpy.array(plan_indx)
 
         # Check that all the plans to use have the same emission-line
         # bandpass channels, ...
         elmom_keys, elmom_channels \
-                    = numpy.array([DAPall._emission_line_moment_db_info(p['elmom_key'])
-                                        for p in plan[use_plans]], dtype=object).T
+                    = numpy.array([DAPall._emission_line_moment_db_info(plan.elmom[key])
+                                        for key in plan.keys()], dtype=object).T
         if numpy.any([ len(k) != len(elmom_channels[0]) or len(set(k)-set(elmom_channels[0])) > 0
                         for k in elmom_channels]):
             raise ValueError('All plans must provide the same emission-line bandpass channels.')
@@ -1143,8 +1117,8 @@ def update(self, plan, methods=None):
 
         # ..., emission-line fit channels, and...
         elfit_keys, elfit_channels \
-                    = numpy.array([DAPall._emission_line_db_info(p['elfit_key'])
-                                        for p in plan[use_plans]], dtype=object).T
+                    = numpy.array([DAPall._emission_line_db_info(plan.elfit[key])
+                                        for key in plan.keys()], dtype=object).T
         if numpy.any([ len(k) != len(elfit_channels[0]) or len(set(k)-set(elfit_channels[0])) > 0
                         for k in elfit_channels]):
             raise ValueError('All plans must provide the same emission-line fit channels.')
@@ -1154,8 +1128,8 @@ def update(self, plan, methods=None):
 
         # ..., and spectral-index channels
         abs_keys, bhd_keys, spindx_channels, spindx_units \
-                    = numpy.array([DAPall._spectral_index_db_info(p['spindex_key'])
-                                        for p in plan[use_plans]], dtype=object).T
+                    = numpy.array([DAPall._spectral_index_db_info(plan.sindx[key])
+                                        for key in plan.keys()], dtype=object).T
         if numpy.any([ len(k) != len(spindx_channels[0]) or len(set(k)-set(spindx_channels[0])) > 0
                         for k in spindx_channels]):
             raise ValueError('All plans must provide the same emission-line fit channels.')
@@ -1173,20 +1147,14 @@ def update(self, plan, methods=None):
         # Report
         if not self.quiet:
             log_output(self.loggers, 1, logging.INFO, '-'*50)
-            log_output(self.loggers, 1, logging.INFO, '{0:^50}'.format('RUNNING DAPALL UPDATE'))
+            log_output(self.loggers, 1, logging.INFO, f'{"RUNNING DAPALL UPDATE":^50}')
             log_output(self.loggers, 1, logging.INFO, '-'*50)
-            log_output(self.loggers, 1, logging.INFO,
-                       'Completed observations: {0}'.format(self.ndap))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Available methods: {0}'.format(plan_methods))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Methods for output: {0}'.format(self.methods))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Emission line moments: {0}'.format(self.nmom))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Emission lines fit: {0}'.format(self.neml))
-            log_output(self.loggers, 1, logging.INFO,
-                       'Spectral indices: {0}'.format(self.nindx))
+            log_output(self.loggers, 1, logging.INFO, f'Completed observations: {self.ndap}')
+            log_output(self.loggers, 1, logging.INFO, f'Available methods: {plan_methods}')
+            log_output(self.loggers, 1, logging.INFO, f'Methods for output: {self.methods}')
+            log_output(self.loggers, 1, logging.INFO, f'Emission line moments: {self.nmom}')
+            log_output(self.loggers, 1, logging.INFO, f'Emission lines fit: {self.neml}')
+            log_output(self.loggers, 1, logging.INFO, f'Spectral indices: {self.nindx}')
 
         # If the HDUList is already initialized, close and reinitialize
         # it
@@ -1196,8 +1164,7 @@ def update(self, plan, methods=None):
 
         # Open the DRPall file
         if not self.quiet:
-            log_output(self.loggers, 1, logging.INFO,
-                       'Reading DRPall: {0}'.format(self.drpall_file))
+            log_output(self.loggers, 1, logging.INFO, f'Reading DRPall: {self.drpall_file}')
         hdu_drpall = fits.open(self.drpall_file)
         drpall = hdu_drpall[1].data
 
@@ -1227,7 +1194,7 @@ def update(self, plan, methods=None):
         self.hdu = fits.HDUList(self.hdu)
 
         # Write the file (use of this function is probably overkill)
-        DAPFitsUtil.write(self.hdu, self.file_path(), overwrite=True, checksum=True,
+        DAPFitsUtil.write(self.hdu, str(self.file_path), overwrite=True, checksum=True,
                           loggers=self.loggers)
         
 
diff --git a/mangadap/survey/drpcomplete.py b/mangadap/survey/drpcomplete.py
index 85a121e1..13cfbc7d 100644
--- a/mangadap/survey/drpcomplete.py
+++ b/mangadap/survey/drpcomplete.py
@@ -18,12 +18,9 @@
 .. include common links, assuming primary doc root is up one directory
 .. include:: ../include/links.rst
 """
-
+from pathlib import Path
 import os
-import time
 import warnings
-import glob
-
 
 from IPython import embed
 
@@ -36,57 +33,39 @@
 
 from ..datacube import MaNGADataCube
 from ..spectra import MaNGARSS
-#from ..config import defaults
 from ..config import manga
-from ..util.parser import arginp_to_list, list_to_csl_string, parse_drp_file_name
+from ..util.parser import arginp_to_list
 
 class DRPComplete:
     r"""
-    Database with information needed by the DAP to analyze the completed
-    DRP files.
+    Database with information needed by the DAP to analyze the completed DRP
+    files.
 
-    This class searches the defined paths for files resulting from the
-    3D phase of the MaNGA DRP, and then collates the information
-    necessary to run those files through the MaNGA DAP.  The necessary
-    parameters are pulled from the provided platetargets files; see
-    :func:`update`.
+    This class searches the defined paths for files resulting from the 3D phase
+    of the MaNGA DRP, and then collates the information necessary to run those
+    files through the MaNGA DAP.  The necessary metadata are pulled from the
+    DRPall file; see :func:`update`.
 
     Args:
-
         platelist (:obj:`str`, :obj:`list`, optional):
-            List of plates to search for.  Default is to search the full
-            DRP path.
+            List of plates to search for.  Default is to search the full DRP
+            path.
         ifudesignlist (:obj:`str`, :obj:`list`, optional):
-            List of ifudesign to search for.  Default is to search the
-            full DRP path.
+            List of IFUs to search for.  Default is to search the full DRP path.
         drpall (:obj:`str`, optional):
             The full path to the DRPall fits file.  Default is set by
-            :func:`mangadap.config.defaults.drpall_file`.
-        platetargets (:obj:`str`, :obj:`list`, optional):
-            List of platetargets files to search through to find any
-            given plate ifudesign combination.  Default is returned as
-            the first element in
-            :func:`mangadap.config.defaults.plate_target_files`.
-        catid (:obj:`str`, :obj:`list`, optional):
-            List of target catalog ID numbers.  Default is returned as
-            the second element in
-            :func:`mangadap.config.defaults.plate_target_files`.
+            :func:`mangadap.config.manga.drpall_file`.
         drpver (:obj:`str`, optional):
             DRP version, which is:
 
                 - used to define the default DRP redux path
-                - used when declaring a drpfits instance
                 - used in the name of the drpcomplete fits file
                 - included as a header keyword in the output file
 
-            Default is defined by
-            :func:`mangadap.config.defaults.drp_version`.
+            Default is defined by :func:`mangadap.config.manga.drp_version`.
         redux_path (:obj:`str`, optional): 
-            The path to the top level directory containing the DRP
-            output files; this is the same as the ``redux_path`` in
-            the :class:`mangadap.util.drpfits.DRPFits` class. Default
-            is defined by
-            :func:`mangadap.config.defaults.drp_redux_path`.
+            The path to the top-level directory containing the DRP output files.
+            Default is defined by :func:`mangadap.config.manga.drp_redux_path`.
         dapver (:obj:`str`, optional):
             DAP version, which is:
 
@@ -94,16 +73,14 @@ class DRPComplete:
                 - included as a header keyword in the output drpcomplete
                   fits file
 
-            Default is defined by
-            :func:`mangadap.config.defaults.dap_version`.
+            Default is defined by :func:`mangadap.config.manga.dap_version`.
         analysis_path (:obj:`str`, optional):
-            The path to the top level directory for the DAP output
-            files; this is **different** from the directory_path in the
-            :class:`mangadap.dapfile` class.  Default is defined by
-            :func:`mangadap.config.defaults.dap_analysis_path`
+            The path to the top level directory for the DAP output files.
+            Default is defined by
+            :func:`mangadap.config.manga.dap_analysis_path`
         directory_path (:obj:`str`, optional):
-            Direct path to the output file produced using
-            :func:`mangadap.config.defaults.dap_common_path`
+            Direct path for the output file.  Default is a directory called
+            ``common`` within the ``analysis_path`` directory.
         readonly (:obj:`bool`, optional):
             Flag that the drpcomplete fits file is only opened for
             reading, not for updating.  If True, many of the attributes
@@ -116,23 +93,18 @@ class DRPComplete:
             List of IFU designs to search for, see above.
         drpall (:obj:`str`):
             The DRPall file, see above.
-        platetargets (`numpy.ndarray`):
-            List of platetargets files to search through, see above.
-        catid (`numpy.ndarray`):
-            List of target catalog IDs, see above
         drpver (:obj:`str`):
             DRP version, see above.
-        redux_path (:obj:`str`):
+        redux_path (`Path`_):
             Path to the top level of directory for the DRP output, see
             above.
         dapver (:obj:`str`):
             DAP version, see above.
-        analysis_path (:obj:`str`):
+        analysis_path (`Path`_):
             Path to the top level directory for the DAP output files,
             see above.
-        directory_path (:obj:`str`):
-            Direct path to the output file produced using
-            :func:`mangadap.config.defaults.dap_common_path`
+        directory_path (`Path`_):
+            Direct path for the output file.
         readonly (:obj:`bool`):
             Flag that the drpcomplete fits file is only opened for
             reading, not for updating.
@@ -142,24 +114,24 @@ class DRPComplete:
             Number of observations in the file
 
     """
-    def __init__(self, platelist=None, ifudesignlist=None, drpall=None, platetargets=None,
-                 catid=None, drpver=None, redux_path=None, dapver=None, analysis_path=None,
-                 directory_path=None, readonly=False):
+    def __init__(self, platelist=None, ifudesignlist=None, drpall=None, drpver=None,
+                 redux_path=None, dapver=None, analysis_path=None, directory_path=None,
+                 readonly=False):
 
         # Input properties
         self.drpver = manga.drp_version() if drpver is None else str(drpver)
         self.redux_path = manga.drp_redux_path(self.drpver) \
-                                if redux_path is None else str(redux_path)
+                                if redux_path is None else Path(redux_path).resolve()
 
         self.dapver = manga.dap_version() if dapver is None else str(dapver)
         self.analysis_path = manga.dap_analysis_path(self.drpver, self.dapver) \
-                             if analysis_path is None else str(analysis_path)
+                             if analysis_path is None else Path(analysis_path).resolve()
         self.directory_path = self.analysis_path / 'common' if directory_path is None \
-                                    else str(directory_path)
+                                    else Path(directory_path).resolve()
 
         self.hdu = None
         self.nobs = None
-        if os.path.exists(self.file_path()):
+        if self.file_path.exists():
             print('READING')
             self._read()
 
@@ -167,8 +139,6 @@ def __init__(self, platelist=None, ifudesignlist=None, drpall=None, platetargets
             self.readonly=True
             self.platelist = None
             self.ifudesignlist = None
-            self.platetargets = None
-            self.catid = None
             return
 
         self.readonly = False
@@ -176,25 +146,9 @@ def __init__(self, platelist=None, ifudesignlist=None, drpall=None, platetargets
         self.platelist = arginp_to_list(platelist, evaluate=True)
         self.ifudesignlist = arginp_to_list(ifudesignlist, evaluate=True)
 
-        self.drpall = manga.drpall_file(drpver=self.drpver,redux_path=self.redux_path) \
+        self.drpall = manga.drpall_file(drpver=self.drpver, redux_path=self.redux_path) \
                             if drpall is None else drpall
         
-        if (platetargets is not None and catid is None) or \
-           (platetargets is None and catid is not None):
-            raise ValueError('To use user-provided platetargets files, must provide both '
-                             'platetargets and catid.')
-
-        self.platetargets = None
-        if platetargets is not None:
-            self.platetargets = numpy.array( arginp_to_list(platetargets) )
-            self.catid = numpy.array( arginp_to_list(catid) ).astype(int)
-        else:
-            try:
-                self.platetargets, self.catid = manga.plate_target_files()
-            except:
-                warnings.warn('Could not define platetargets files.  '
-                              'Updates must use DRPall file.')
-
     def __getitem__(self, key):
         return self.hdu['DRPC'].data[key]
 
@@ -204,43 +158,12 @@ def __len__(self):
     # ******************************************************************
     #  Utility functions
     # ******************************************************************
-
-    def _read_platetargets(self):
-        """
-        Read all the platetargets files using `pydl.pydlutils.yanny`_
-        and return a list of yanny structures.
-
-        Returns:
-            list: A list of yanny structures, one per platetargets file.
-
-        Raises:
-            FileNotFoundError: Raised if cannot open one or more
-                platetargets files.
-
-        .. todo::
-            This should be made more efficient by collating the required
-            plateTargets data into a single record array!
-        """
-        plttrg_data = []
-
-        root = '/'+os.path.join(*(self.platetargets[0].split('/')[:-1]))
-        print('plateTargets root directory: {0}'.format(root))
-
-        for p in self.platetargets:
-            pfile = p.split('/')[-1]
-            print('Reading plateTargets file: {0}'.format(pfile), end='\r')
-            # Check that the file exists
-            if not os.path.exists(p):
-                raise FileNotFoundError('Cannot open {0}!'.format(p))
-            # Read and append the data
-            plttrg_data.append( yanny(filename=p) )
-        print('\nReading plateTargets file: DONE')
-
-        return plttrg_data
-
     def _read_fix_data(self, fix_file):
         """
-        Read fix data from the provided file.
+        Read "fix" data from the provided file.
+
+        Fix data are generally corrections to the metadata provides by the NSA
+        for specific MaNGA observations.
 
         Args:
             fix_file (:obj:`str`):
@@ -250,10 +173,10 @@ def _read_fix_data(self, fix_file):
             yanny: A yanny structure with the data fixes.  Returns
             None and raises a warning if the file does not exist.
         """
-        if not os.path.isfile(fix_file):
-            warnings.warn('No redshift fix file available.')
+        if not fix_file.exists():
+            warnings.warn(f'DAP "fix" file does not exist: {fix_file}')
             return None
-        return yanny(filename=fix_file)
+        return yanny(filename=str(fix_file))
 
     def _read_redshift_fix(self):
         """
@@ -271,192 +194,9 @@ def _read_photometry_fix(self):
         """
         return self._read_fix_data(manga.photometry_fix_file())
 
-    def _match_platetargets(self, quiet=True):
-        """
-        Read the platetargets files and match the data therein to the
-        completed DRP files based on the plate and ifudesign.
-
-        If a plate-ifudesign combination is not found in the
-        plateTargets file, the other parameter values are set to -9999
-        and the MaNGA ID is set to NULL.
-
-        If the plate-ifudesign combination is found, the columns in the
-        plateTargets files that the code expects to find are 'plateid',
-        'ifudesign', 'mangaid', 'object_ra', and 'object_dec'; the
-        values that will be replaced with 'NULL' (str) or -9999 (int or
-        float) if they do not exist are 'nsa_version', 'nsa_nsaid',
-        'manga_target1', 'manga_target3', 'z' or 'nsa_z',
-        'nsa_elpetro_ba', 'nsa_elpetro_phi', 'nsa_elpetro_th50_R',
-        'nsa_vdisp'.
-        
-        .. todo::
-
-            - Instead of searching through all files for the correct
-              plate-ifudesign, use the MaNGA ID to get the correct
-              catalog?  Requires getting the MaNGA ID from somewhere,
-              but it takes a long time to read from the fits files...
-            - Is there some more concise method of performing the same
-              thing as what is done below with many try/except blocks?
-
-        Args:
-            quiet (:obj:`bool`, optional):
-                Suppress terminal output
-
-        Returns:
-            numpy.array: 14 arrays with: MaNGA ID, object right
-            ascension, object declination, catalog ID, index of the
-            entry in the catalog, catalog version, ID of object in the
-            catalog, main MaNGA survey target bitmask, ancillary MaNGA
-            survey target bitmask, velocity, velocity dispersion,
-            ellipticity, position angle, and effective radius.
-        """
-        # Read the platetargets file
-        plttrg_data = self._read_platetargets()
-        ntrg = len(plttrg_data)
-        print('Read {0} plateTargets file(s)'.format(ntrg))
-
-        # Initialize the output arrays (needed in case some DRP targets not found)
-        n_drp = len(self.platelist)
-        mangaid = []
-        # TODO: try mangaid = numpy.empty(n_drp, dtype=object) ?
-        objra = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        objdec = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        catid = numpy.full(n_drp, -9999, dtype=numpy.int32)
-        catindx = numpy.full(n_drp, -9999, dtype=numpy.int32)
-        trg_version = []
-        # TODO: try trg_version = numpy.empty(n_drp, dtype=object) ?
-        trg_id = numpy.full(n_drp, -9999, dtype=numpy.int32)
-        manga_trg1 = numpy.full(n_drp, -9999, dtype=numpy.int64)
-        manga_trg3 = numpy.full(n_drp, -9999, dtype=numpy.int64)
-        vel = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        veldisp = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        ell = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        pa = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-        Reff = numpy.full(n_drp, -9999.0, dtype=numpy.float64)
-
-        print('Searching platetargets file for observed galaxies...', end='\r')
-        for i in range(n_drp):
-            plttrg_j = 0
-            mangaid_i = 'NULL'
-            for j in range(ntrg):
-                indx = numpy.where((plttrg_data[j]['PLTTRGT']['plateid'] == self.platelist[i]) &
-                                (plttrg_data[j]['PLTTRGT']['ifudesign'] == self.ifudesignlist[i]))
-                if len(indx[0]) > 1:
-                    raise ValueError('Multiple instances of {0}-{1} (MaNGA ID={2}) in {3}'.format(
-                                     self.platelist[i], self.ifudesignlist[i], mangaid_i,
-                                     self.platetargets[j]))
-                if len(indx[0]) == 1:
-                    plttrg_j = j
-                    mangaid_i = plttrg_data[j]['PLTTRGT']['mangaid'][indx][0].decode("ascii")
-                    if not quiet:
-                        print('Found {0}-{1} (MaNGA ID={2}) in {3} (CAT={4})'.format(
-                                        self.platelist[i], self.ifudesignlist[i], mangaid_i,
-                                        self.platetargets[j], self.catid[j]))
-                    break
-
-            if len(indx[0]) == 0:
-                warnings.warn('Could not find {0}-{1} in any plateTargets file!'.format(
-                                                        self.platelist[i], self.ifudesignlist[i]))
-                mangaid = mangaid + ['NULL']
-                continue
-            mangaid.append(plttrg_data[plttrg_j]['PLTTRGT']['mangaid'][indx][0].decode("ascii"))
-            objra[i] = plttrg_data[plttrg_j]['PLTTRGT']['object_ra'][indx][0]
-            objdec[i] = plttrg_data[plttrg_j]['PLTTRGT']['object_dec'][indx][0]
-
-            # From David Wake:
-            #
-            # "MANGAID consists of CATID-CATIND, where CATID identifies
-            # a parent catalog, in the case of the main manga samples
-            # CATID = 1 which refers to nsa_v1_0_0.fits, and CATIND is
-            # the position within that catalog (zero indexed). So if you
-            # strip out CATIND from MANGAID you have the position within
-            # nsa_v1_0_0.fits without any matching required."
-            catid[i], catindx[i] = map(lambda x: int(x), mangaid[i].split('-'))
-            if catid[i] != self.catid[plttrg_j]:
-                warnings.warn('{0}-{1} (MaNGA ID={2}) found in {3} (CAT={4})!'.format(
-                                self.platelist[i], self.ifudesignlist[i], mangaid_i,
-                                self.platetargets[j], self.catid[j]))
-
-            try:
-                trg_version.append(plttrg_data[plttrg_j]['PLTTRGT']['nsa_version'][indx][0].decode(
-                                                                                        'ascii'))
-            except:
-                trg_version.append('NULL')
-
-            try:
-                trg_id[i] = plttrg_data[plttrg_j]['PLTTRGT']['nsa_nsaid'][indx][0]
-            except:
-                trg_id[i] = -9999
-                
-            try:
-                manga_trg1[i] = plttrg_data[plttrg_j]['PLTTRGT']['manga_target1'][indx][0]
-            except:
-                manga_trg1[i] = -9999
-
-            try:
-                manga_trg3[i] = plttrg_data[plttrg_j]['PLTTRGT']['manga_target3'][indx][0]
-            except:
-                manga_trg3[i] = -9999
-
-            # As of MPL-6, platetargets files now include the redshift
-            # from the targeting catalog which is the combination of the
-            # NSA data and the ancillary targets; the NSA only redshift
-            # column is 'nsa_z'.  To be compatible with previous
-            # versions, first try to grab the redshift using the keyword
-            # 'z', then try with 'nsa_z', then just set it to -9999.
-            try:
-                vel[i] = plttrg_data[plttrg_j]['PLTTRGT']['z'][indx][0] \
-                            * astropy.constants.c.to('km/s').value
-            except:
-                try:
-                    vel[i] = plttrg_data[plttrg_j]['PLTTRGT']['nsa_z'][indx][0] \
-                                * astropy.constants.c.to('km/s').value
-                except:
-                    vel[i] = -9999.0
-
-            try:
-                if plttrg_data[plttrg_j]['PLTTRGT']['nsa_elpetro_ba'][indx][0] < 0:
-                    raise
-                ell[i] = 1.0-plttrg_data[plttrg_j]['PLTTRGT']['nsa_elpetro_ba'][indx][0]
-            except:
-                ell[i] = -9999.0
-                warnings.warn('Ellipticity for {0}-{1} is bogus!'.format(self.platelist[i],
-                                                                         self.ifudesignlist[i]))
-
-            try:
-                # THERE'S A BUG IN THE MANGACORE/V1_2_0 PLATETARGETS FILES
-                pa[i] = plttrg_data[plttrg_j]['PLTTRGT']['nsa_elpetro_phi'][indx][0]
-                if pa[i] < 0:
-                    raise
-            except:
-                pa[i] = -9999.0
-                warnings.warn('PA for {0}-{1} is bogus!'.format(self.platelist[i],
-                                                                self.ifudesignlist[i]))
-
-            try:
-                Reff[i] = plttrg_data[plttrg_j]['PLTTRGT']['nsa_elpetro_th50_r'][indx][0]
-            except:
-                Reff[i] = -9999.0
-
-            try:
-                veldisp[i] = plttrg_data[plttrg_j]['PLTTRGT']['nsa_vdisp'][indx][0]
-            except:
-                veldisp[i] = -9999.0
-
-            # Correct for known nan issue
-            if numpy.isnan(ell[i]):
-                raise ValueError('nan encountered!')
-            if numpy.isnan(pa[i]):
-                raise ValueError('nan encountered!')
-
-        print('Searching platetargets file for observed galaxies...DONE')
-
-        return numpy.array(mangaid), objra, objdec, catid, catindx, numpy.array(trg_version), \
-               trg_id, manga_trg1, manga_trg3, vel, veldisp, ell, pa, Reff
-
     def _match_drpall(self):
         """
-        Find the data needed for the DRP complete databas in the DRPall
+        Find the data needed for the DRP complete database in the DRPall
         file.
 
         If a plate-ifudesign combination is not found in the DRPall
@@ -464,31 +204,32 @@ def _match_drpall(self):
         ID is set to NULL.
 
         If the plate-ifudesign combination is found, the columns in the
-        plateTargets files that the code expects to find are 'plate',
-        'ifudsgn', 'mangaid', 'objra', 'objdec'; the values that will be
-        replaced with 'NULL' (str) or -9999 (int or float) if they do
-        not exist are 'nsa_version', 'nsa_nsaid', 'mngtarg1',
-        'mngtarg3', 'z', 'nsa_elpetro_ba', 'nsa_elpetro_phi',
-        'nsa_elpetro_th50_r', 'nsa_vdisp'.
+        plateTargets files that the code expects to find are ``'plate'``,
+        ``'ifudsgn'``, ``'mangaid'``, ``'objra'``, ``'objdec'``; the values that
+        will be replaced with ``'NULL'`` (str) or -9999 (int or float) if they
+        do not exist are ``'nsa_version'``, ``'nsa_nsaid'``, ``'mngtarg1'``,
+        ``'mngtarg3'``, ``'z'``, ``'nsa_elpetro_ba'``, ``'nsa_elpetro_phi'``,
+        ``'nsa_elpetro_th50_r'``, ``'nsa_vdisp'``.
         
         Returns:
-            numpy.array: 14 arrays with: MaNGA ID, object right
-            ascension, object declination, catalog ID, index of the
-            entry in the catalog, catalog version, ID of object in the
-            catalog, main MaNGA survey target bitmask, ancillary MaNGA
-            survey target bitmask, velocity, velocity dispersion,
-            ellipticity, position angle, and effective radius.
-        """
+            :obj:`tuple`: 14 `numpy.ndarray`_ arrays with the MaNGA ID, object
+            right ascension, object declination, catalog ID, index of the entry
+            in the catalog, catalog version, ID of object in the catalog, main
+            MaNGA survey target bitmask, ancillary MaNGA survey target bitmask,
+            velocity, velocity dispersion, ellipticity, position angle, and
+            effective radius.
+        """
+        if not self.drpall.exists():
+            raise ValueError(f'DRPall file does not exist! {self.drpall}')
         # Open the drpall file
-        hdu = fits.open(self.drpall)
+        hdu = fits.open(str(self.drpall))
         # Check the plateifus are unique
         pltifu = hdu[1].data['PLATEIFU']
         if len(numpy.unique(pltifu)) != len(pltifu):
             raise ValueError('The PLATEIFU column in the DRPall file is not unique!')
 
         # Find the rows in the DRPall file with the correct; assumes the 
-        this_pltifu = numpy.array(['{0}-{1}'.format(p,i) 
-                                    for p,i in zip(self.platelist,self.ifudesignlist)])
+        this_pltifu = numpy.array([f'{p}-{i}' for p,i in zip(self.platelist,self.ifudesignlist)])
         rows = numpy.array([numpy.where(pltifu == pi)[0][0] if pi in pltifu else -1 \
                                 for pi in this_pltifu])
         if numpy.any(rows < 0):
@@ -558,7 +299,7 @@ def _read(self):
         if self.hdu is not None:
             self.hdu.close()
             self.hdu = None
-        self.hdu = fits.open(self.file_path())
+        self.hdu = fits.open(str(self.file_path))
         self.nobs = self.hdu['DRPC'].header['NAXIS2']
         print('Read data: {0} rows'.format(self.nobs))
 
@@ -574,7 +315,7 @@ def _confirm_access(self, reread=False):
         Returns:
             :obj:`bool`: Flag that :attr:`hdu` is read and valid.
         """
-        if not os.path.exists(self.file_path()):
+        if not self.file_path.exists():
             return False
         if self.hdu is None or reread:
             self._read()
@@ -614,8 +355,7 @@ def _find_completed_reductions(self, mindesign=19, combinatorics=False, on_disk=
             mindesign (:obj:`int`, optional):
                 Minimum bundle design to consider.  For example, to
                 ignore all the 7-fiber bundles, set `mindesign=19`
-                (default) to only select the bundles with 19 or more
-                fibers.
+                to only select the bundles with 19 or more fibers.
             combinatorics (:obj:`bool`, optional):
                 Create :attr:`platelist` and :attr:`ifudesignlist` by
                 determining all possible combinations of the input
@@ -626,7 +366,7 @@ def _find_completed_reductions(self, mindesign=19, combinatorics=False, on_disk=
                 DRPall file.
 
         Returns:
-            list: Two lists with the available plates and ifudesigns for
+            :obj:`tuple`: Two lists with the available plates and ifudesigns for
             which to collect data.
         """
         # Are the plate and IFU lists matched?
@@ -636,36 +376,34 @@ def _find_completed_reductions(self, mindesign=19, combinatorics=False, on_disk=
         # Get the list of files
         if matchedlist:
             # Lists already matched, just construct the file names
-            files = [os.path.join(*MaNGADataCube.default_paths(p, i, drpver=self.drpver,
-                                                               redux_path=self.redux_path)) \
+            files = [manga.MaNGAConfig(int(p), int(i), drpver=self.drpver,
+                                       redux_path=self.redux_path).file_path
                         for p,i in zip(self.platelist, self.ifudesignlist)]
         elif on_disk:
             # Find the DRP LOGCUBE files on disk
             print('Searching for completed DRP CUBE files...', end='\r')
-            files = glob.glob(os.path.join(self.redux_path, '*', 'stack', '*-LOGCUBE.fits.gz'))
+            files = sorted(list(self.redux_path.glob('*/stack/*-LOGCUBE.fits.gz')))
             print('Searching for completed DRP CUBE files...DONE.')
         else:
             # Use the DRPall file
-            drpall_hdu = fits.open(self.drpall)
+            drpall_hdu = fits.open(str(self.drpall))
             pltifu = drpall_hdu[1].data['PLATEIFU']
             if len(numpy.unique(pltifu)) != len(pltifu):
                 raise ValueError('The PLATEIFU column in the DRPall file is not unique!')
-            files = [os.path.join(*MaNGADataCube.default_paths(int(p), int(i), drpver=self.drpver,
-                                                               redux_path=self.redux_path)) \
+
+            files = [manga.MaNGAConfig(int(p), int(i), drpver=self.drpver,
+                                       redux_path=self.redux_path).file_path
                         for p,i in zip(drpall_hdu[1].data['plate'], drpall_hdu[1].data['ifudsgn'])]
 
-        # Only use those files that exist
-        for i in range(len(files)):
-            if not os.path.isfile(files[i]):
-                warnings.warn('No such file: {0}'.format(files[i]))
-                del files[i]
+        files = [f for f in files if f.exists()]
+        if len(files) == 0:
+            raise ValueError('Could not find any files to analyze.')
 
         # Get the list of plate and ifus
-        pltifu = numpy.array(list(map(lambda x: os.path.split(x)[-1].split('-')[1:3],
-                                      files))).astype(int)
+        pltifu = numpy.array([manga.parse_plate_ifu_from_file(f.name) for f in files]).astype(int)
 
         # Ignore any below the minimum ifusize
-        pltifu = pltifu[numpy.invert(pltifu[:,1]//100 < mindesign),:]
+        pltifu = pltifu[numpy.logical_not(pltifu[:,1]//100 < mindesign),:]
         if matchedlist:
             return pltifu[:,0], pltifu[:,1]
 
@@ -695,66 +433,29 @@ def _find_modes(self):
             `numpy.ndarray`: Array of modes for each input DRP file.
         """
         print('Checking for RSS counterparts...', end='\r')
-        has_rss = [os.path.isfile(os.path.join(*MaNGARSS.default_paths(p, i, drpver=self.drpver,
-                                                                    redux_path=self.redux_path)))
+        has_rss = [manga.MaNGAConfig(int(p), int(i), mode='RSS', drpver=self.drpver,
+                                       redux_path=self.redux_path).file_path.exists()
                         for p,i in zip(self.platelist, self.ifudesignlist)]
         print('Checking for RSS counterparts...DONE.')
         modes = numpy.ones(len(has_rss), dtype=int)
         modes[has_rss] = 2
         return modes
         
-    def _write_parameter_struct(self, ostream):
-        """
-        Write the structure of the DAP SDSS-style parameter set to a
-        file.
-
-        Args:
-            ostream (_io.TextIOWrapper) : File stream for output
-        
-        """
-        ostream.write('typedef struct {\n')
-        ostream.write('    long plate;\n')
-        ostream.write('    long ifudesign;\n')
-        ostream.write('    char mode[4];\n')
-        ostream.write('    double vel;\n')
-        ostream.write('    double vdisp;\n')
-        ostream.write('    double ell;\n')
-        ostream.write('    double pa;\n')
-        ostream.write('    double reff;\n')
-        ostream.write('} DAPPAR;\n')
-
-    def _write_parameter_list(self, ostream, index, mode):
-        """
-        Write a DAPPAR entry to the SDSS-style parameter file.
-
-        Args:
-            ostream (_io.TextIOWrapper) : File stream for output
-            index (int) : Index of the entry in :attr:`data` to write
-            mode (int) : Mode of the entry; see :func:`_find_modes`
-
-        """
-        ostream.write('DAPPAR {0:4d} {1:5d} {2:4s} {3:14.7e} {4:14.7e} {5:14.7e} {6:14.7e}'
-                      ' {7:14.7e}\n'.format(self.hdu['DRPC'].data['PLATE'][index],
-                                            self.hdu['DRPC'].data['IFUDESIGN'][index], mode,
-                                            self.hdu['DRPC'].data['VEL'][index],
-                                            self.hdu['DRPC'].data['VDISP'][index],
-                                            self.hdu['DRPC'].data['ELL'][index],
-                                            self.hdu['DRPC'].data['PA'][index],
-                                            self.hdu['DRPC'].data['REFF'][index]))
-
     # ******************************************************************
     #  User functions
     # ******************************************************************
+    @property
     def file_name(self):
         """Return the name of the DRP complete database."""
-        return ('drpcomplete_{0}.fits'.format(self.drpver))
+        return f'drpcomplete_{self.drpver}.fits'
 
+    @property
     def file_path(self):
         """Return the full pat to the DRP complete database."""
-        return os.path.join(self.directory_path, self.file_name())
+        return self.directory_path / self.file_name
 
     def update(self, platelist=None, ifudesignlist=None, combinatorics=False, force=False,
-               alldrp=False, use_platetargets=False, on_disk=False, quiet=False):
+               alldrp=False, on_disk=False, quiet=False):
         """
         Update the DRP complete file.
         
@@ -794,9 +495,6 @@ def update(self, platelist=None, ifudesignlist=None, combinatorics=False, force=
                 one built from scratch.
             alldrp (:obj:`bool`, optional):
                 Find the full list of available DRP files.
-            use_platetargets (:obj:`bool`, optional):
-                Generate the data using the platetargets files instead
-                of the DRPall file.
             on_disk (:obj:`bool`, optional):
                 When searching for available files to analyze, search
                 the DRP directory path instead of using the data in the
@@ -828,10 +526,10 @@ def update(self, platelist=None, ifudesignlist=None, combinatorics=False, force=
 
         # Check if these plate-ifus are already in the table.
         if not force and self._all_data_exists():
-            print('{0} up to date.'.format(self.file_path()))
+            print(f'{self.file_path} up to date.')
             return
         else:
-            print('Updating {0}.'.format(self.file_path()))
+            print(f'Updating {self.file_path}.')
 
         # Past this point, the drpcomplete fits file will be ovewritten
         # if it already exists.  Only DRP files defined by 
@@ -847,8 +545,7 @@ def update(self, platelist=None, ifudesignlist=None, combinatorics=False, force=
 
         # Get the data
         mangaid, objra, objdec, catid, catindx, trg_version, trg_id, manga_trg1, manga_trg3, vel, \
-                veldisp, ell, pa, Reff = (self._match_platetargets(quiet=quiet)
-                                            if use_platetargets else self._match_drpall())
+                veldisp, ell, pa, Reff = self._match_drpall()
 
         # Apply any corrections to the redshifts using the redshift fix file
         redshift_fix_data = self._read_redshift_fix()
@@ -892,111 +589,121 @@ def write(self, platelist, ifudesignlist, modes, mangaid, objra, objdec, catid,
         Write the drpcomplete fits binary table.
 
         Header keywords are:
+
             - VERSDRP: DRP version
             - RDXPTH: DRP reduction path
             - VERSDAP: DAP version
             - SISPTH: DAP analysis path
-            - PLTTRG *N*: plateTargets file *N*
-            - CATID *N*: ID Number of target catalog *N*
-            - AUTHOR: Set to 'K.B. Westfall <kbwestfall@gmail.com>'
 
         Binary table columns:
-            1. PLATE (1J): Plate number
-            2. IFUDESIGN (1J): IFU design
-            3. MODES (1B): Modes available 
+
+            1. PLATE: Plate number
+            2. IFUDESIGN: IFU design
+            3. MODES: Modes available 
+
                 - MODES=1: only 'CUBE' file is available
                 - MODES=2: both 'CUBE' and 'RSS' files are available
 
-            4. MANGAID (*n* A): MaNGA ID (same as 'CATID-CATINDX')
-            5. OBJRA (1D): Object right ascension
-            6. OBJDEC (1D): Object declination
-            7. CATID (1J): Catalog ID used for target selection
-            8. CATINDX (1J): Index in catalog with target data
-            9. TRG_VERSION (*n* A): Version of the catalog (e.g., NSA)
+            4. MANGAID: MaNGA ID (same as 'CATID-CATINDX')
+            5. OBJRA: Object right ascension
+            6. OBJDEC: Object declination
+            7. CATID: Catalog ID used for target selection
+            8. CATINDX: Index in catalog with target data
+            9. TRG_VERSION: Version of the catalog (e.g., NSA)
                 used in targetting (def='NULL' if not available)
-            10. TRG_ID (1J): Target ID in catalog (def=-9999 if not
-                available)
+            10. TRG_ID: Target ID in catalog (def=-9999 if not available)
             11. MANGA_TARGET1: Main MaNGA survey target bitmask
             12. MANGA_TARGET3: Ancillary MaNGA survey target bitmask
-            13. VEL (1D): Systemic velocity from catalog (def=-9999.0 if
+            13. VEL: Systemic velocity from catalog (def=-9999.0 if
                 not available)
-            14. VDISP (1D): Velocity dispersion from catalog
+            14. VDISP: Velocity dispersion from catalog
                 (def=-9999.0 if not available)
-            15. ELL (1D): Ellipticity (1-b/a) from catalog (def=-9999.0
+            15. ELL: Ellipticity (1-b/a) from catalog (def=-9999.0
                 if not available); Elliptical Petrosian value from NSA
-            16. PA (1D): Position angle from catalog (def=-9999.0 if not
+            16. PA: Position angle from catalog (def=-9999.0 if not
                 available); Elliptical Petrosian value from NSA
-            17. REFF (1D): Effective radius from catalog (def=-9999.0 if
+            17. REFF: Effective radius from catalog (def=-9999.0 if
                 not available); Elliptical Petrosian value from NSA
 
         Args:
-            platelist (list) : List of plates
-            ifudesignlist (list) : List of IFU designs
-            modes (numpy.array): Mode values, see above
-            mangaid (numpy.array): MaNGA IDs
-            objra (numpy.array): Object right ascensions
-            objdec (numpy.array): Object declinations
-            catid (numpy.array): Catalog ID used for target selection
-            catindx (numpy.array): Index in catalog with target data
-            trg_version (numpy.array): Version of the catalog (e.g.,
-                NSA) used in targetting
-            trg_id (numpy.array): Target ID in catalog
-            manga_trg1 (numpy.array): Main MaNGA survey target bitmask
-            manga_trg3 (numpy.array): Ancillary MaNGA survey target
-                bitmask
-            vel (numpy.array): Velocity from catalog, if available
-            veldisp (numpy.array): Velocity dispersion from catalog,
-                if available
-            ell (numpy.array): Ellipticity (1-b/a) from catalog, if
-                available
-            pa (numpy.array): Position angle from catalog, if
-                available
-            Reff (numpy.array): Effective radius from catalog, if
-                available
-            drpver (str) : (**Optional**) DRP version, see above.
-            redux_path (str) : (**Optional**) Path to the top level of
-                directory for the DRP output, see above.
-            dapver (str) : (**Optional**) DAP version, see above.
-            analysis_path (str) : (**Optional**) Path to the top level
-                directory for the DAP output files, see above.
-            overwrite (bool): (**Optional**) Overwrite any existing file.
+            platelist (:obj:`list`):
+                List of plates
+            ifudesignlist (:obj:`list`):
+                List of IFU designs
+            modes (`numpy.ndarray`_):
+                Mode values, see above
+            mangaid (`numpy.ndarray`_):
+                MaNGA IDs
+            objra (numpy.ndarray`_):
+                Object right ascensions
+            objdec (numpy.ndarray`_):
+                Object declinations
+            catid (numpy.ndarray`_):
+                Catalog ID used for target selection
+            catindx (numpy.ndarray`_):
+                Index in catalog with target data
+            trg_version (numpy.ndarray`_):
+                Version of the catalog (e.g., NSA) used in targetting
+            trg_id (numpy.ndarray`_):
+                Target ID in catalog
+            manga_trg1 (numpy.ndarray`_):
+                Main MaNGA survey target bitmask
+            manga_trg3 (numpy.ndarray`_):
+                Ancillary MaNGA survey target bitmask
+            vel (numpy.ndarray`_):
+                Velocity from catalog, if available
+            veldisp (numpy.ndarray`_):
+                Velocity dispersion from catalog, if available
+            ell (numpy.ndarray`_):
+                Ellipticity (1-b/a) from catalog, if available
+            pa (numpy.ndarray`_):
+                Position angle from catalog, if available
+            Reff (numpy.ndarray`_):
+                Effective radius from catalog, if available
+            drpver (:obj:`str`, optional):
+                DRP version, see above.
+            redux_path (`Path`_, optional):
+                Path to the top level of directory for the DRP output, see
+                above.
+            dapver (:obj:`str`, optional):
+                DAP version, see above.
+            analysis_path (`Path`_, optional):
+                Path to the top level directory for the DAP output files, see
+                above.
+            overwrite (:obj:`bool`, optional):
+                Overwrite any existing file.
 
         Raises:
-            AttributeError: Raised if drpcomplete fits file was opened
-                in read-only mode.
-            FileExistsError: Raised if the drpcomplete file exists and
-                overwrite=False.
+            AttributeError:
+                Raised if drpcomplete fits file was opened in read-only mode.
+            FileExistsError:
+                Raised if the drpcomplete file exists and overwrite=False.
         """
         if self.readonly:
             raise AttributeError('drpcomplete fits file was opened as read-only!')
 
-        out=self.file_path()
-        if os.path.isfile(out) and not overwrite:
-            raise FileExistsError('DRP complete file already exists: {0}'.format(out))
-
-        if not os.path.isdir(self.directory_path):
-            os.makedirs(self.directory_path)
+        out = self.file_path
+        if out.exists() and not overwrite:
+            raise FileExistsError(f'DRP complete file already exists: {out}')
 
-        # Create the primary header
-        nplttrg = 0 if self.platetargets is None else len(self.platetargets)
+        if not self.directory_path.exists():
+            self.directory_path.mkdir(parents=True)
 
         hdr = fits.Header()
         hdr['VERSDRP'] = self.drpver if drpver is None else drpver
-        hdr['RDXPTH'] = self.redux_path if redux_path is None else redux_path
+        hdr['RDXPTH'] = str(self.redux_path) if redux_path is None else str(redux_path)
         hdr['VERSDAP'] = self.dapver if dapver is None else dapver
-        hdr['SISPTH'] = self.analysis_path if analysis_path is None else analysis_path
-        for i in range(nplttrg):
-            hdr['PLTTRG{0}'.format(i+1)] = self.platetargets[i]
-            hdr['CATID{0}'.format(i+1)] = self.catid[i]
+        hdr['SISPTH'] = str(self.analysis_path) if analysis_path is None else str(analysis_path)
         hdr['AUTHOR'] = 'K.B. Westfall <kbwestfall@gmail.com>'
 
+        # TODO: Convert this into a DataTable?
         # Create the Binary Table
         cols = []
         cols.append(fits.Column(name='PLATE', format='1J', array=numpy.array(platelist)))
         cols.append(fits.Column(name='IFUDESIGN', format='1J', array=numpy.array(ifudesignlist)))
         cols.append(fits.Column(name='MODES', format='1B', array=modes))
         cols.append(fits.Column(name='MANGAID', format='{0}A'.format(mangaid.dtype.itemsize),
-                           array=mangaid))
+                                array=mangaid))
         cols.append(fits.Column(name='OBJRA', format='1D', array=objra))
         cols.append(fits.Column(name='OBJDEC', format='1D', array=objdec))
         cols.append(fits.Column(name='CATID', format='1J', array=catid))
@@ -1014,121 +721,12 @@ def write(self, platelist, ifudesignlist, modes, mangaid, objra, objdec, catid,
         cols.append(fits.Column(name='REFF', format='1D', array=Reff))
 
         # Create the HDUList and write it to the fits file
-        self.hdu = fits.HDUList([ fits.PrimaryHDU(header=hdr),
-                                  fits.BinTableHDU.from_columns(cols, name='DRPC') ])
-        print('Writing to disk: {0}'.format(out))
-        self.hdu.writeto(out, overwrite=overwrite)
+        self.hdu = fits.HDUList([fits.PrimaryHDU(header=hdr),
+                                 fits.BinTableHDU.from_columns(cols, name='DRPC')])
+        print(f'Writing to disk: {out}')
+        self.hdu.writeto(str(out), overwrite=overwrite)
         self.nobs = self.hdu['DRPC'].header['NAXIS2']
 
-#    def grab_data(self, plate=None, ifudesign=None, index=None, reread=False):
-#        """
-#        Return the data for a given plate-ifudesign, or index in the
-#        :attr:`data`.  Even though *plate*, *ifudesign*, and *index* are
-#        all optional, the arguments must contain either *plate* and
-#        *ifudesign* or *index*.
-#
-#        Args:
-#            plate (int): (**Optional**) Plate number
-#            ifudesign (int): (**Optional**) IFU design
-#            index (int): (**Optional**) Index of the row in :attr:`data`
-#                with the data to return
-#            reread (bool): (**Optional**) Force the database to be re-read
-#
-#        Returns:
-#            list: List of the 14 elements of :attr:`data`; see
-#            :func:`write`.
-#
-#        Raises:
-#            ValueError: Raised if the row with the data is unknown
-#                because either *index* is not defined or one or both of
-#                *plate* and *ifudesign* is not defined.  Also raised if
-#                *index* does not exist in the data array.
-#        """
-#        if (plate is None or ifudesign is None) and index is None:
-#            raise ValueError('Must provide plate and ifudesign or row index!')
-#
-#        if index is None:
-#            index = self.entry_index(plate, ifudesign, reread=reread)
-#        else:
-#            self._confirm_access(reread=reread)
-#            if index >= self.nobs:
-#                raise ValueError('Selected row index does not exist')
-#
-#        # TODO: Can't I just select index, i.e.,
-#        # self.hdu[1].data[index]?
-#
-#        return [ self.hdu['DRPC'].data['PLATE'][index], self.hdu['DRPC'].data['IFUDESIGN'][index],
-#                 self.hdu['DRPC'].data['MODES'][index], self.hdu['DRPC'].data['MANGAID'][index],
-#                 self.hdu['DRPC'].data['OBJRA'][index], self.hdu['DRPC'].data['OBJDEC'][index],
-#                 self.hdu['DRPC'].data['CATID'][index], self.hdu['DRPC'].data['CATINDX'][index],
-#                 self.hdu['DRPC'].data['TRG_VERSION'][index], self.hdu['DRPC'].data['TRG_ID'][indx],
-#                 self.hdu['DRPC'].data['NSA_V100_ID'][index], self.hdu['DRPC'].data['VEL'][index],
-#                 self.hdu['DRPC'].data['VDISP'][index], self.hdu['DRPC'].data['ELL'][index],
-#                 self.hdu['DRPC'].data['PA'][index], self.hdu['DRPC'].data['REFF'][index] ]
-
-    def write_par(self, ofile, mode, plate=None, ifudesign=None, index=None, reread=False,
-                  overwrite=True):
-        """
-        Write the SDSS-style parameter (Yanny) file for use with the
-        MaNGA DAP.
-
-        Args: 
-            ofile (str): Output file name
-            mode (str): Mode of the DRP file to analyze; must be either
-                'RSS' or 'CUBE'
-            plate (int): (**Optional**) Plate number
-            ifudesign (int): (**Optional**) IFU design
-            index (int): (**Optional**) Index of the row in :attr:`data`
-                with the data to return
-            reread (bool): (**Optional**) Force the database to be re-read
-            overwrite (bool): (**Optional**) Overwrite any existing parameter
-                file
-
-        Raises:
-            IOError: Raised if the parameter file already exists and
-                overwrite is False.
-            ValueError: Raised if
-
-                - the row with the data is unknown because either
-                  *index* is not defined or one or both of *plate* and
-                  *ifudesign* is not defined.
-                - *index* does not exist in the data array.
-                - the 'RSS' mode is selected but unavailable
-
-        """
-        if not self._confirm_access(reread=reread):
-            raise IOError('Could not access database!')
-
-        if os.path.exists(ofile) and not overwrite:
-            raise IOError('Parameter file already exists.  Set overwrite=True to overwrite.')
-
-        if (plate is None or ifudesign is None) and index is None:
-            raise ValueError('Must provide plate and ifudesign or row index!')
-
-        if mode != 'CUBE' and mode != 'RSS':
-            raise ValueError('Mode must be either CUBE or RSS.')
-
-        if index is None:
-            index = self.entry_index(plate, ifudesign, reread=reread)
-        else:
-            if index >= self.nobs:
-                raise ValueError('Selected row index does not exist')
-
-        if mode == 'RSS' and self.hdu['DRPC'].data['MODES'][index] != 2:
-            raise ValueError('RSS mode not available for plate={0},ifudesign={1} !'.format(
-                                                        self.hdu['DRPC'].data['PLATE'][index],
-                                                        self.hdu['DRPC'].data['IFUDESIGN'][index]))
-
-        # Write the SDSS parameter file
-        ostream = open(ofile, 'w')
-        ostream.write('\n')
-        self._write_parameter_struct(ostream)
-        ostream.write('\n')
-        ostream.write('\n')
-        self._write_parameter_list(ostream, index, mode)
-        ostream.write('\n')
-        ostream.close()
-
     def write_config(self, ofile, plate=None, ifudesign=None, index=None, sres_ext=None,
                      sres_fill=None, covar_ext=None, reread=False, overwrite=True):
         """
@@ -1137,7 +735,7 @@ def write_config(self, ofile, plate=None, ifudesign=None, index=None, sres_ext=N
         analysis.
 
         Args: 
-            ofile (:obj:`str`):
+            ofile (:obj:`str`, `Path`_):
                 Output file name.
             plate (:obj:`int`, optional):
                 Plate number.
@@ -1185,13 +783,14 @@ def write_config(self, ofile, plate=None, ifudesign=None, index=None, sres_ext=N
         elif index >= self.nobs:
             raise ValueError('Selected row index does not exist')
 
-        MaNGADataCube.write_config(ofile, self['PLATE'][index], self['IFUDESIGN'][index], log=True,
-                                   z=self['VEL'][index]/astropy.constants.c.to('km/s').value,
-                                   vdisp=self['VDISP'][index], ell=self['ELL'][index],
-                                   pa=self['PA'][index], reff=self['REFF'][index],
-                                   sres_ext=sres_ext, sres_fill=sres_fill, covar_ext=covar_ext,
-                                   drpver=self.drpver, redux_path=self.redux_path,
-                                   overwrite=overwrite)
+        manga.MaNGAConfig.write_config(ofile, self['PLATE'][index], self['IFUDESIGN'][index],
+                                       log=True,
+                                       z=self['VEL'][index]/astropy.constants.c.to('km/s').value,
+                                       vdisp=self['VDISP'][index], ell=self['ELL'][index],
+                                       pa=self['PA'][index], reff=self['REFF'][index],
+                                       sres_ext=sres_ext, sres_fill=sres_fill, covar_ext=covar_ext,
+                                       drpver=self.drpver, redux_path=self.redux_path,
+                                       overwrite=overwrite)
 
     def entry_index(self, plate, ifudesign, reread=False):
         """
@@ -1211,20 +810,18 @@ def entry_index(self, plate, ifudesign, reread=False):
                 Force the database to be re-read
 
         Returns:
-            int: Index of the row in :attr:`data` with the data for the
-            given *plate* and *ifudesign*
+            :obj:`int`: Index of the row in :attr:`data` with the data for the
+            given ``plate`` and ``ifudesign``.
 
         Raises:
             ValueError:
-                Raised if the given `plate` and `ifudesign` were not
-                found.
+                Raised if the given ``plate`` and ``ifudesign`` were not found.
         """
         if not self._confirm_access(reread=reread):
             raise IOError('Could not access database!')
         indx = (self['PLATE'] == plate) & (self['IFUDESIGN'] == ifudesign)
         if numpy.sum(indx) == 0:
-            raise ValueError('Could not find plate={0},ifudesign={1} in drpcomplete file!'.format(
-                                plate, ifudesign))
+            raise ValueError(f'plate={plate},ifudesign={ifudesign} not in drpcomplete file!')
         return numpy.where(indx)[0][0]
 
     def can_analyze(self, row=None):
diff --git a/mangadap/survey/mangampl.py b/mangadap/survey/mangampl.py
deleted file mode 100644
index ee9c07ca..00000000
--- a/mangadap/survey/mangampl.py
+++ /dev/null
@@ -1,314 +0,0 @@
-# Licensed under a 3-clause BSD style license - see LICENSE.rst
-# -*- coding: utf-8 -*-
-"""
-Defines a class to keep track of MaNGA MPL dependencies and versioning.
-
-.. note::
-    This class was created for the survey-level execution of the DAP.
-    The dependencies will necessarily grow, and will be handled using
-    module files.  So, even as it is, this class has little utility.
-
-.. todo::
-    Deprecate this. I'm not sure it's actually used (or useful)
-    anymore.
-
-----
-
-.. include license and copyright
-.. include:: ../include/copy.rst
-
-----
-
-.. include common links, assuming primary doc root is up one directory
-.. include:: ../include/links.rst
-"""
-import sys
-import os
-import warnings
-from distutils.version import StrictVersion
-
-# For version checking
-import numpy
-import scipy
-import matplotlib
-import astropy
-import pydl
-
-# TODO: Add DAP version
-
-class MaNGAMPL:
-    """
-
-    Create an object that keeps track of IDLUTILS, SDSS_ACCESS, MANGACORE, and
-    MANGADRP dependencies for a given MPL and other intermediate analyses.
-    Dependencies are selected by the "MPL" version as provided in the
-    following table:
-
-    **SDSS dependencies**
-    +--------+-------------+------------+--------+--------+--------+
-    |  MPL   | SDSS_ACCESS |  IDLUTILS  |   CORE |   DRP  |   DAP  |
-    +========+=============+============+========+========+========+
-    | MPL-1  |        None |    v5_5_16 | v1_0_0 | v1_0_0 |   None |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-2  |        None |    v5_5_17 | v1_0_0 | v1_1_2 |   None |
-    +--------+-------------+------------+--------+--------+--------+
-    | v1_2_0 |        None |    v5_5_19 | v1_1_0 | v1_2_0 |   None |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-3  |        None |    v5_5_22 | v1_1_0 | v1_3_3 | v1_0_0 |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-4  |       0.0.0 |    v5_5_23 | v1_2_0 | v1_5_1 |  1.1.1 |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-5  |       0.2.0 |    v5_5_26 | v1_3_1 | v2_0_1 |  2.0.2 |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-6  |        None |    v5_5_30 | v1_6_0 | v2_3_1 |  2.1.3 |
-    +--------+-------------+------------+--------+--------+--------+
-    | MPL-7  |        None |    v5_5_32 | v1_6_2 | v2_4_3 |  2.2.1 |
-    +--------+-------------+------------+--------+--------+--------+
-
-    **Python versions**
-    +--------+--------+--------+--------+------------+---------+-------+----------------+
-    | MPL    | python | numpy  |  scipy | matplotlib | astropy |  pydl | Fwd compatible |
-    +========+========+========+========+============+=========+=======+================+
-    | MPL-5  |  3.5.1 | 1.11.0 | 0.17.1 |       None |   1.1.2 | 0.5.0 |            yes |
-    +--------+--------+--------+--------+------------+---------+-------+----------------+
-    | MPL-6  |  3.6.3 | 1.13.3 |  1.0.0 |       None |   2.0.2 | 0.6.0 |            N/A |
-    +--------+--------+--------+--------+------------+---------+-------+----------------+
-    | MPL-7  |  3.6.3 | 1.13.3 |  1.0.0 |       None |   2.0.2 | 0.6.0 |            N/A |
-    +--------+--------+--------+--------+------------+---------+-------+----------------+
-
-    .. note::
-        - "Fwd compatible" means that you can run this version of the
-          DAP with the most recent version without needed to revert the
-          python packages to their previous versions.
-        - Only the survey-level routines are dependent on MANGACORE; the
-          core DAP processing software only depends on the listed python
-          packages.
-
-    .. todo::
-        - Change python dependencies to "minimum" versions?
-
-    .. warning::
-        - SDSS_ACCESS dependencies are only because of the plotting
-          package that has since moved into Marvin.
-        - For MPL-1, the MANGACORE version was set to trunk.  Here it
-          defaults to v1_0_0.
-        - Explicit dependence on matplotlib was introduced in MPL-6.
-
-    Args:
-        version (str): (**Optional**) Version as selected via matching
-            to the "MPL" string in the table listed above. Default:
-            MPL-6.
-        strictver (bool): (**Optional**) Strictly check the version
-            requirements for this version of the dap.  Default is True,
-            meaning the code will raise an exception if the expected
-            version are not the same as the environment.
-
-    Raises:
-        ValueError: Raised if the provided *version* is not one of the
-            MPL values in the above table.
-
-    Attributes:
-        mplver (str): Version as selected via matching to the "MPL"
-            string in the table listed above. Default: MPL-5.
-        accessver (str): SDSS_ACCESS version.
-        idlver (str): IDLUTILS version.
-        corever (str): MANGACORE version.
-        drpver (str): MANGADRP version.
-        dapver (str): MANGADAP version.
-        pythonver (str): Python version
-        numpyver (str): Numpy version
-        scipyver (str): Scipy version
-        matplotlibver (str): Matplotlib version
-        astropyver (str): Astropy version
-        pydlver (str): Pydl version
-        strictver (bool): Strictly check the version requirements for
-            this version of the dap.  If True, an exception is raised if
-            the expected version are not the same as the environment.
-
-    """
-    def __init__(self, version=None, strictver=True):
-        # Default to current environment
-        self.mplver = 'env' if version is None else version
-
-        mpls = self._available_mpls()
-        mpli = numpy.where(mpls[:,0] == self.mplver)
-        if len(mpli[0]) == 0:
-            mpls = self._available_mpls(write=True)
-            raise ValueError('{0} is not an available MPL!'.format(self.mplver))
-           
-        n = mpls.shape[0] 
-        i = 0
-        self.mplver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.accessver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.idlver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.corever = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.drpver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.dapver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.pythonver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.numpyver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.scipyver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.matplotlibver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.astropyver = mpls[mpli,i].ravel()[0]
-        i += 1
-        self.pydlver = mpls[mpli,i].ravel()[0]
-        i += 1
-
-        # Treat versioning strictly
-        self.strictver = strictver
-
-
-    def _available_mpls(self, write=False):
-        """
-        Return a list of the available MPLs to analyze, and printing it if requested.
-
-        .. todo:
-            Have this read a config file!
-
-        Args:
-            write (bool): Write the list of available MPLs to the
-            stdout.
-
-        Returns:
-            numpy.array: A string array with the MPL dependencies
-        """
-        #                             MPL SDSS_ACCESS  IDLUTILS      CORE       DRP       DAP
-        mpl_def = numpy.array([ [ 'MPL-1',      None, 'v5_5_16', 'v1_0_0', 'v1_0_0',     None,
-        #                            python     numpy     scipy  matplotlib  astropy      pydl
-                                       None,     None,     None,       None,    None,     None ],
-                                [ 'MPL-2',      None, 'v5_5_17', 'v1_0_0', 'v1_1_2',     None,
-                                       None,     None,     None,       None,    None,     None ],
-                                ['v1_2_0',      None, 'v5_5_19', 'v1_1_0', 'v1_2_0',     None,
-                                       None,     None,     None,       None,    None,     None ],
-                                [ 'MPL-3',      None, 'v5_5_22', 'v1_1_0', 'v1_3_3', 'v1_0_0',
-                                       None,     None,     None,       None,    None,     None ],
-                                [ 'MPL-4',   '0.0.0', 'v5_5_23', 'v1_2_0', 'v1_5_1',  '1.1.1',
-                                       None,     None,     None,       None,    None,     None ],
-                                [ 'MPL-5',   '0.2.0', 'v5_5_26', 'v1_3_1', 'v2_0_1',  '2.0.2',
-                                    '3.5.1', '1.11.0', '0.17.1',       None,  '1.1.2', '0.5.0' ],
-                                [ 'MPL-6',      None, 'v5_5_30', 'v1_6_0', 'v2_3_1',  '2.1.3',
-                                    '3.6.3', '1.13.3',    '1.0',       None,  '2.0.2', '0.6.0' ],
-                                [ 'MPL-7',      None, 'v5_5_32', 'v1_6_2', 'v2_4_3',  '2.2.1',
-                                    '3.6.3', '1.13.3',    '1.0',       None,  '2.0.2', '0.6.0' ],
-                                ['env'] + self.get_environment_versions()
-                                ])
-
-        nmpl = mpl_def.shape[1]
-        if write:
-            print('Available MPLs:')
-            print('{0:>7} {1:>7} {2:>8} {3:>6} {4:>6} {5:>6} {6:>6} {7:>6} {8:>6} {9:>10}'
-                  ' {10:>7} {11:>6}'.format('MPL', 'SDSSACC', 'IDLUTILS', 'CORE', 'DRP', 'DAP',
-                                            'PYTHON', 'NUMPY', 'SCIPY', 'MATPLOTLIB', 'ASTROPY',
-                                            'PYDL'))
-            for x in mpl_def[0:nmpl,:]:
-                _x = x.astype(str)
-                print('{0:>7} {1:>7} {2:>8} {3:>6} {4:>6} {5:>6} {6:>6} {7:>6} {8:>6} {9:>10}'
-                      ' {10:>7} {11:>6}'.format(*_x))
-
-        return mpl_def
-
-
-    def _version_mismatch(self, package, required_ver, environment_ver, quiet=False):
-        """
-        A version mismatch was detected.  Handle based on
-        :attr:`strictver`.
-        """
-        if self.strictver:
-            raise EnvironmentError('Environment mismatch for {0}: expected {1}, found {2}'.format(
-                                   package, required_ver, environment_ver))
-        elif not quiet:
-            warnings.warn('Environment mismatch for {0}: expected {1}, found {2}'.format(package,
-                          required_ver, environment_ver))
-
-
-    def get_environment_versions(self):
-        # These will throw KeyErrors if the appropriate environmental variables do not exist
-        try:
-            accessver_env = os.environ['SDSS_ACCESS_DIR'].split('/')[-1]
-        except KeyError:
-            accessver_env = None
-        try:
-            idlver_env = os.environ['IDLUTILS_DIR'].split('/')[-1]
-        except KeyError:
-            idlver_env = None
-        python_ver = '.'.join([ str(v) for v in sys.version_info[:3]])
-
-        return [ accessver_env, idlver_env, os.getenv('MANGACORE_VER'),
-                 os.getenv('MANGADRP_VER'), os.getenv('MANGADAP_VER'),
-                python_ver, numpy.__version__, scipy.__version__, matplotlib.__version__,
-                astropy.__version__, pydl.__version__]
-
-
-    def verify_versions(self, quiet=True):
-        """
-        Verify that the defined MPL versions are consistent with the
-        system where the DAP is being run.
-        """
-        env = self.get_environment_versions()
-
-        if not quiet:
-            print('Current environment: ')
-#            print('    SDSS_ACCESS: {0}'.format(env[0]))
-            print('       IDLUTILS: {0}'.format(env[1]))
-            print('      MANGACORE: {0}'.format(env[2]))
-            print('       MANGADRP: {0}'.format(env[3]))
-            print('       MANGADAP: {0}'.format(env[4]))
-            print('         PYTHON: {0}'.format(env[5]))
-            print('          NUMPY: {0}'.format(env[6]))
-            print('          SCIPY: {0}'.format(env[7]))
-            print('     MATPLOTLIB: {0}'.format(env[8]))
-            print('        ASTROPY: {0}'.format(env[9]))
-            print('           PYDL: {0}'.format(env[10]))
-
-        # Check versions
-#        if self.accessver != env[0]:
-#            self._version_mismatch('SDSS_ACCESS', self.accessver, env[0], quiet=quiet)
-
-        if self.idlver != env[1]:
-            self._version_mismatch('IDLUTILS', self.idlver, env[1], quiet=quiet)
-
-        if self.corever != env[2]:
-            self._version_mismatch('MANGACORE', self.corever, env[2], quiet=quiet)
-
-        if self.drpver != env[3]:
-            self._version_mismatch('MANGADRP', self.drpver, env[3], quiet=quiet)
-
-#        if self.dapver != env[4]:
-#            self._version_mismatch('MANGADAP', self.dapver, env[4], quiet=quiet)
-
-        if self.pythonver is not None and StrictVersion(env[5]) != StrictVersion(self.pythonver):
-            self._version_mismatch('python', self.pythonver, env[5], quiet=quiet)
-
-        if self.numpyver is not None and StrictVersion(env[6]) != StrictVersion(self.numpyver):
-            self._version_mismatch('numpy', self.numpyver, env[6], quiet=quiet)
-
-        if self.scipyver is not None and StrictVersion(env[7]) != StrictVersion(self.scipyver):
-            self._version_mismatch('scipy', self.scipyver, env[7], quiet=quiet)
-
-#        if self.matplotlibver is not None \
-#                and StrictVersion(env[8]) != StrictVersion(self.matplotlibver):
-#            self._version_mismatch('matplotlib', self.matplotlibver, env[8], quiet=quiet)
-
-        if self.astropyver is not None and StrictVersion(env[9]) != StrictVersion(self.astropyver):
-            self._version_mismatch('astropy', self.astropyver, env[9], quiet=quiet)
-
-        if self.pydlver is not None and StrictVersion(env[10]) != StrictVersion(self.pydlver):
-            self._version_mismatch('pydl', self.pydlver, env[10], quiet=quiet)
-
-
-    def show(self):
-        """Print the available MPL versions to stdout."""
-
-        print('{0}: IDLUTILS:{1}; COREVER:{2}; DRPVER:{3}; DAPVER:{4}'.format(
-              self.mplver, self.idlver, self.corever, self.drpver, self.dapver))
-
-
diff --git a/mangadap/survey/rundap.py b/mangadap/survey/rundap.py
new file mode 100644
index 00000000..5db3b7a2
--- /dev/null
+++ b/mangadap/survey/rundap.py
@@ -0,0 +1,938 @@
+"""
+Defines the class used to automate the batch execution of the MaNGA DAP.
+
+----
+
+.. include license and copyright
+.. include:: ../include/copy.rst
+
+----
+
+.. include common links, assuming primary doc root is up one directory
+.. include:: ../include/links.rst
+"""
+from pathlib import Path
+import time
+import shutil
+import warnings
+
+from IPython import embed
+
+import numpy
+
+try:
+    import pbs.queue
+except:
+    warnings.warn('Could not import pbs.queue!  Any cluster submission will fail!', ImportWarning)
+
+from ..config import manga, manga_environ, python_versions
+from .drpcomplete import DRPComplete
+from ..util.parser import arginp_to_list
+from ..util.fileio import create_symlink
+
+
+class rundap:
+    r"""
+    Class used for automated execution of the MaNGA DAP via submission
+    of scripts to a cluster.
+
+    [ More complete explanation of the class here. ]
+
+    .. note::
+    
+        This is purely a survey-level utility for running the DAP at
+        Utah, including submitting jobs to the cluster.  No user-level
+        usage of the DAP should be considered here, apart from not
+        hindering such usage of the primary python programs!
+
+    .. todo::
+
+        - Have :func:`_read_arg` return the boolean for :attr:`version`
+          instead of keeping it as an attribute.
+        - Ditch ``console`` and :func:`_read_arg` in favor of the
+          more typical ``parser`` and ``main`` pairing.
+
+    Args:
+        overwrite (:obj:`bool`, optional):
+            Overwrite and redo any existing analysis.
+        console (:obj:`bool`, optional):
+            The class has been executed from the terminal with
+            command-line arguments that should be parsed.
+        quiet (:obj:`bool`, optional):
+            Suppress output
+        print_version (:obj:`bool`, optional):
+            Print the class version and return.
+        drpver (:obj:`str`, optional):
+            DRP version to analyze.  Default is defined by
+            :func:`mangadap.config.manga.drp_version`
+        redux_path (:obj:`str`, optional):
+            The top-level path with the DRP files used to override the
+            default defined by
+            :func:`mangadap.config.manga.drp_redux_path`.
+        dapver (:obj:`str`, optional):
+            The DAP version to use for the analysis, used to override
+            the default defined by
+            :func:`mangadap.config.manga.dap_version`.  *Only
+            used to define the file paths, not used to switch to the
+            specified code version!*
+        analysis_path (:obj:`str`, optional):
+            The top-level path for the DAP output files, used to
+            override the default defined by
+            :func:`mangadap.config.manga.dap_analysis_path`.
+        plan_file (:obj:`str`, optional):
+            Name of the plan file to use for *all* DRP data in this run,
+            used to override the default defined by
+            :func:`mangadap.par.analysisplan.AnalysisPlanSet.default`.
+        platelist (:obj:`int`, :obj:`str`, optional):
+            List of plates to analyze; default is to search the
+            :attr:`redux_path` for any DRP files; see
+            :class:`mangadap.survey.drpcomplete.DRPComplete`.
+        ifudesignlist (:obj:`int`, :obj:`str`, optional):
+            List of ifudesigns to analyze: default is to search the
+            :attr:`redux_path` for any DRP files; see
+            :class:`mangadap.survey.drpcomplete.DRPComplete`.
+        combinatorics (:obj:`bool`, optional): 
+            Use all unique combinations of the entered
+            plate/ifudesign/mode lists to create the full list of DRP
+            files to analyze.
+        list_file (:obj:`str`, optional):
+            File with a list of plates and ifudesigns to analyze. The
+            file must have two columns, with one plate-ifu
+            combination per file row.
+        sres_ext (:obj:`str`, optional):
+            The extension to use when constructing the
+            spectral resolution vectors for the MaNGA datacubes. See
+            :func:`mangadap.datacube.manga.MaNGADataCube.spectral_resolution`.
+        sres_fill (:obj:`bool`, optional):
+            Fill masked spectral-resolution data by simple linear
+            interpolation.
+        covar_ext (:obj:`str`, optional):
+            Extension in the MaNGA DRP CUBE file to use as the single
+            spatial correlation matrix for all wavelength channels.
+        on_disk (:obj:`bool`, optional):
+            When searching for available files to analyze, search the
+            DRP directory path instead of using the data in the DRPall
+            file.  
+        can_analyze (:obj:`bool`, optional):
+            Only construct script files for datacubes that can/should be
+            analyzed by the DAP.  See
+            :func:`~mangadap.survey.drpcomplete.DRPComplete.can_analyze`.
+        log (:obj:`bool`, optional):
+            Flag to create the DAP log files.
+        dapproc (:obj:`bool`, optional):
+            Flag to execute the main DAP processing.
+        pltifu_plots (:obj:`bool`, optional):
+            Create the QA plate-ifu specific plots.
+        post_process (:obj:`bool`, optional):
+            Prepare (and possibly perform) the post processing steps:
+            create the DAPall file and its associated QA plots, and the
+            fit QA plots for each plate.
+        post_plots (:obj:`bool`, optional):
+            Create the post-processing QA plots.
+        report_progress (:obj:`bool`, optional):
+            Report the progress of the analysis.
+        vebose (:obj:`int`, optional):
+            Verbosity level between 0 and 2.
+        label (:obj:`str`, optional):
+            Label to use in cluster queue.
+        nodes (:obj:`int`, optional):
+            Number of cluster nodes to use.
+        qos (:obj:`str`, optional):
+            Select a specific processor (e.g. sdss-fast when the manga
+            user).  This option is ignored if :attr:`q` is set.
+        umask (:obj:`str`, optional):
+            umask to set for output.
+        walltime (:obj:`str`, optional):
+            Maximum wall time for cluster job.
+        hard (:obj:`bool`, optional):
+            Same as hard keyword in cluster submission; see
+            :func:`pbs.queue.commit`.  This keyword is in the
+            **opposite** sense of the "--toughness" command-line
+            options; i.e. including --toughness on the command line sets
+            hard=False.
+        create (:obj:`bool`, optional):
+            Use the pbs package to create the scripts.
+        submit (:obj:`bool`, optional):
+            Submit all jobs to the cluster.
+        queue (:obj:`str`, optional):
+            Name of the destination queue.  When submitting jobs at
+            Utah, this should **not** be set (leaving it at the default
+            of None).  When submitting jobs at Portsmouth, this can be
+            used to select either sciama1.q, cluster.q (default), or
+            sciama3.q.
+
+    Attributes:
+        overwrite (bool): **Only used with :attr:`all`**.  Overwrite and
+            redo any existing analysis.
+        quiet (bool): Suppress output
+        print_version (bool): Print the version and then return
+        mpl (:class:`mangadap.survey.mangampl.MaNGAMPL`): MPL version;
+            see above.
+        redux_path (str): The top-level path with the DRP files.
+        dapver (str): The DAP version.
+        analysis_path (str): The top-level path for the DAP output
+            files.
+        plan_file (str): Name of the plan file to use for ALL DRP data
+            in this run.  Will be None if using the default plan.
+        plan (:class:`AnalysisPlanSet`): The set of analysis plans
+        daptypes (:obj:`list`): This list of daptypes, one per plan
+        platelist (list): List of plates to analyze.
+        ifudesignlist (list): List of ifudesigns to analyze.
+        combinatorics (bool): Use all unique combinations of the entered
+            plate/ifudesign/mode lists to create the full list of DRP
+            files to analyze.
+        on_disk (bool): See above
+        can_analyze (bool): See above
+        log (str): See above
+        dapproc (bool): Flag to execute the main DAP processing
+        pltifu_plots (bool): Create the QA plate-ifu specific plots
+        post_process (:obj:`bool`): See above
+        post_plots (:obj:`bool`): See above
+        report_progress (:obj:`bool`): See abve
+        label (str): Label to use in cluster queue.
+        nodes (int): Number of cluster nodes to use.
+        qos (str): Specific processor to use (Utah specific)
+        umask (str): umask to set for output.
+        walltime (str): Maximum wall time for cluster job.
+        hard (bool): Same as hard keyword in cluster submission; see
+            :func:`pbs.queue.commit`.
+        create (bool): Use the pbs package to create the submission
+            scripts.
+        submit (bool): Submit all jobs to the cluster.
+        q (str): Queue destination (Portsmouth specific)
+        drpc (:class:`mangadap.survey.drpcomplete.DRPComplete`):
+            Database of the available DRP files and the parameters
+            necessary to write the DAP par files.
+
+    Raises:
+        FileNotFoundError:
+            Raised if a plate-ifu list file is provided but it does not
+            exist, or if the plan file does not exist.
+        ValueError:
+            Raised if all processing steps are turned off, if the
+            selected MPL is undefined; if the fast node is selected with
+            nodes > 1; if multiple run modes are selected; or if the
+            plate list, ifudesign list, or mode list are not provided
+            during a redo mode or if they are provided with any other
+            mode.
+    """
+    def __init__(self, overwrite=None, quiet=False, drpver=None, redux_path=None, dapver=None,
+                 analysis_path=None, plan_file=None, platelist=None, ifudesignlist=None,
+                 combinatorics=False, list_file=None, sres_ext=None, sres_fill=None,
+                 covar_ext=None, on_disk=False, can_analyze=False, log=False, dapproc=True,
+                 pltifu_plots=True, post_process=False, post_plots=False, report_progress=False,
+                 verbose=0, label='mangadap', nodes=1, cpus=None, qos=None, umask='0027',
+                 walltime='240:00:00', hard=True, create=False, submit=False, queue=None):
+
+        # Save run-mode options
+        self.overwrite = overwrite
+        self.quiet = quiet
+
+        # Override environment
+        self.drpver = drpver
+        self.redux_path = None if redux_path is None else Path(redux_path).resolve()
+        self.dapver = dapver
+        self.analysis_path = None if analysis_path is None else Path(analysis_path).resolve()
+
+        # List of files to analyze
+        self.plan_file = None if plan_file is None else Path(plan_file).resolve()
+        _list_file = None if list_file is None else Path(list_file).resolve()
+        if _list_file is not None and not _list_file.exists():
+            raise FileNotFoundError(f'No file: {_list_file}')
+        if list_file is None:
+            self.platelist = arginp_to_list(platelist, evaluate=True)
+            self.ifudesignlist = arginp_to_list(ifudesignlist, evaluate=True)
+        else:
+            if platelist is not None or ifudesignlist is not None:
+                warnings.warn('Provided both file and direct list of plate-ifus to process; '
+                              'file input takes precedence.')
+            self.platelist, self.ifudesignlist = self._read_file_list(_list_file)
+        self.combinatorics = combinatorics
+        self.sres_ext = sres_ext
+        self.sres_fill = sres_fill
+        self.covar_ext = covar_ext
+
+        self.on_disk = on_disk
+        self.can_analyze = can_analyze
+
+        # Set the options for output
+        self.log = log
+        self.dapproc = dapproc
+        self.pltifu_plots = pltifu_plots
+        self.post_process = post_process
+        self.post_plots = post_plots
+
+        # Report the progess of the cluster, otherwise just finish
+        # script
+        self.report_progress = report_progress
+        self.verbose = verbose
+
+        # Cluster queue keywords
+        self.label = label
+        self.nodes = nodes
+        self.cpus = cpus
+        self.qos = qos
+        self.umask = umask
+        self.walltime = walltime
+        self.hard = hard
+        self.create = create
+        self.submit = submit
+        self.q = queue
+
+        # If submitting to the cluster, make sure that scripts will be
+        # created!
+        if not self.create and self.submit:
+            self.create = True
+
+        # If setting qos, the number of nodes must be 1
+        if self.qos is not None and self.nodes > 1:
+            raise ValueError('When selecting the fast node, must have nodes=1!')
+
+        # Check processing steps
+        nproc = 0
+        processes = []
+        if self.dapproc:
+            nproc += 1
+            processes += [ 'Main DAP blocks' ]
+        if self.pltifu_plots:
+            nproc += 1
+            processes += [ 'PLATEIFU-specific QA plots' ]
+        if self.post_process:
+            nproc += 1
+            processes += [ 'DAPall' ]
+        if self.post_plots:
+            nproc += 1
+            processes += [ 'DAPall and PLATE-specific QA plots' ]
+        if nproc < 1:
+            raise ValueError('No processing steps requested!')
+
+        if (self.dapproc or self.pltifu_plots) and self.submit and self.post_process \
+                and not self.report_progress:
+            warnings.warn('If both processing and post processing, need to report progress.  '
+                          'Setting to report progress.')
+            self.report_progress = True
+
+        # Set versions
+        if self.drpver is None:
+            self.drpver = manga.drp_version()
+        if self.dapver is None:
+            self.dapver = manga.dap_version()
+
+        # Set the output paths
+        self.redux_path = manga.drp_redux_path(self.drpver) \
+                                if self.redux_path is None else self.redux_path
+        self.analysis_path = manga.dap_analysis_path(self.drpver, self.dapver) \
+                                    if self.analysis_path is None else self.analysis_path
+
+        # Set the subdirectory from which the scripts are sourced and
+        # the various log files are written.  Currently based on start
+        # UTC; e.g., analysis_path/log/19May2016T09.17.42UTC
+        self.calling_path = self.analysis_path / 'log' \
+                                / time.strftime('%d%b%YT%H.%M.%SUTC',time.gmtime())
+
+        # Create the calling path. The existence test is executed, even
+        # though time stamp should mean that calling_path never exists.
+        # TODO: allow argument to set calling_path directly?
+        if not self.calling_path.exists():
+            self.calling_path.mkdir(parents=True)
+
+        # Allow for the default plan.
+        if self.plan_file is None:
+            warnings.warn('No analysis plan was provided.  DAP executions will adopt the default '
+                          'plan.')
+        else:
+            if not self.plan_file.exists():
+                raise FileNotFoundError(f'Plan file does not exist: {self.plan_file}')
+            _plan_file = self.plan_file
+            self.plan_file = self.calling_path / self.plan_file.name
+            if not self.plan_file.exists():
+                shutil.copy2(_plan_file, self.plan_file)
+
+        # Construct the DAPTYPEs and check that the plans yield a fully
+        # unique list
+        self.plan = manga.MaNGAAnalysisPlan.default(analysis_path=self.analysis_path) \
+                        if self.plan_file is None \
+                        else manga.MaNGAAnalysisPlan.from_toml(self.plan_file,
+                                                               analysis_path=self.analysis_path)
+        self.daptypes = [self.plan[key]['key'] for key in self.plan.keys()]
+        if len(numpy.unique(self.daptypes)) != self.plan.nplans:
+            raise ValueError('{0} do not yield unique DAPTYPEs.'.format(
+                                'Default plans' if self.plan_file is None 
+                                    else f'Plans in {self.plan_file} '))
+
+        # Alert the user of the versions to be used
+        if self.q is not None:
+            print(f'Attempting to submit to queue: {self.q}')
+        print('Versions:')
+        for key in python_versions.keys():
+            print(f'    {key:>12}: {python_versions[key]:<30}')
+        print(f'    {"DRP":>12}: {self.drpver:<30}')
+        print(f'    {"DAP":>12}: {self.dapver:<30}')
+        print('Paths:')
+        print(f'      REDUX: {self.redux_path}')
+        print(f'   ANALYSIS: {self.analysis_path}')
+        print(f'    CALLING: {self.calling_path}')
+        if self.plan_file is not None:
+            print(f'  PLAN FILE: {self.plan_file}')
+        print('Processing steps added to scripts:')
+        print(f'        {processes}')
+
+        # Create and update the DRPComplete file if necessary
+        self.drpc = DRPComplete(drpver=self.drpver, redux_path=self.redux_path,
+                                dapver=self.dapver, analysis_path=self.analysis_path)
+        
+        # Update the DRPComplete list; force an update if platetarget
+        # files are provided
+        self.drpc.update(platelist=self.platelist, ifudesignlist=self.ifudesignlist,
+                         combinatorics=self.combinatorics, on_disk=self.on_disk)
+
+        # Hereafter, self.platelist and self.ifuplatelist are the INPUT
+        # values, whereas self.drpc.platelist and self.drpc.ifuplatelist
+        # are the actual DRP reductions to analyze (including
+        # combinatorics and file existence tests).  See, e.g.,
+        # selected_drpfile_list().
+
+        # Find the rows in the DRPComplete database with the DRP file
+        # meta data
+        drpc_rows = self._selected_drpfile_rows(can_analyze=self.can_analyze)
+
+        print(f'Number of DRP files to process: {len(drpc_rows)}')
+        if len(drpc_rows) == 0:
+            # If coded correctly, this function should never return here.
+            return
+
+        # Construct the post-processing scripts before
+        # constructing/submitting datacube processing scripts
+        if self.post_process or self.post_plots:
+            post_queue = self._build_post_queue(drpc_rows)
+
+        # Process the relevant datacubes
+        if self.dapproc or self.pltifu_plots:
+            # Create the script files for the set of drpfiles, and
+            # submit the scripts to the queue if self.submit is true
+            proc_queue = self._build_proc_queue(drpc_rows)
+
+            # Submit the queue to the cluster
+            if self.create:
+                proc_queue.commit(hard=self.hard, submit=self.submit)
+
+            # If not reporting progress, done
+            if not self.report_progress:
+                return
+
+            # Wait until the queue is finished
+            running = True
+            while running and self.submit:
+                time.sleep(60) 
+                percent_complete = proc_queue.get_percent_complete()
+                print(f'Percent complete ... {percent_complete:.1f}%', end='\r')
+                if percent_complete == 100:
+                    running = False
+            print(f'Percent complete ... {percent_complete:.1f}%')
+
+        if not self.post_process and not self.post_plots:
+            # Don't perform the post-processing
+            return
+
+        # Submit the post-processing queue to the cluster
+        if self.create:
+            post_queue.commit(hard=self.hard, submit=self.submit)
+
+    # ******************************************************************
+    #  UTILITY FUNCTIONS
+    # ******************************************************************
+    def _read_file_list(self, list_file):
+        db = numpy.genfromtxt(str(list_file), dtype=object)
+        if db.shape[1] != 2:
+            raise ValueError('Input file should contain 2 columns, plate and ifu.')
+        return db[:,0].astype(int).tolist(), db[:,1].astype(int).tolist()
+
+    def _create_queue(self):
+        """
+        Create a queue instance.
+        
+        This function is written to be functional for submission to the
+        cluster queues both at Utah and using the sciama cluster at
+        Portsmouth.  The available queues are:
+
+        +-----------+--------+------+-----------+
+        |     Queue |  Nodes |  PPN |   GB/core |
+        +===========+========+======+===========+
+        |     ember |     ?? |   12 |        ?? |
+        +-----------+--------+------+-----------+
+        | sciama1.q |     80 |   12 |         2 |
+        +-----------+--------+------+-----------+
+        | sciama2.q |     96 |   16 |         4 |
+        +-----------+--------+------+-----------+
+        | sciama3.q |     48 |   20 |       3.2 |
+        +-----------+--------+------+-----------+
+
+        One should **not** submit to sciama3.q without permission from
+        Will Percival.  The default queue is cluster.q (sciama2.q).
+
+        Submissions to Utah should have their queue destination set to
+        None.  However, the fast node can be selected using :attr:`qos`.
+        """
+        # Instantiate
+        queue = pbs.queue(verbose=not self.quiet)
+
+        if self.q == 'ember':
+            # Submitting to Utah ember cluster
+            ppn = 12
+            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
+            walltime = self.walltime if int(self.walltime.split(':')[0]) < 72 else '72:00:00'
+            queue.create(label=self.label, nodes=self.nodes, qos=self.qos, umask=self.umask,
+                         walltime=walltime, ppn=ppn, cpus=cpus, partition='ember', alloc='sdss')
+        elif self.q is not None:
+            # All other self.q values expected for Portsmouth cluster,
+            # sciama.  In this case, the number of nodes is queue
+            # dependent, and qos is not set
+            if self.q == 'sciama1.q':
+                ppn = 12
+            elif self.q == 'sciama3.q':
+                ppn = 20
+            else:
+                ppn = 16
+            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
+            queue.create(label=self.label, nodes=self.nodes, umask=self.umask,
+                         walltime=self.walltime, queue=self.q, ppn=ppn, cpus=cpus)
+        else:
+            # self.q can be None when submitting to both the Portsmouth
+            # and Utah clusters.  In this case, the default queue
+            # destination and ppn is correct.  qos is also set, but this
+            # should only be used when submitting to Utah.
+            ppn = 16
+            cpus = ppn if self.cpus is None else min(self.cpus, ppn)
+            queue.create(label=self.label, nodes=self.nodes, qos=self.qos, umask=self.umask,
+                         walltime=self.walltime, ppn=ppn, cpus=cpus)
+
+        return queue
+
+    # Files:
+    #       - mangadap-{plate}-{ifudesign}-LOG{mode} = script file = *
+    #       - *.ready = script/directory is ready for queue submission
+    #       - *.queued = script submitted to queue
+    #       - *.started = script assigned a node/processor and has started running
+    #       - *.done = completed execution of the full script
+    # Other:
+    #       - *.par = parameter file
+    #       - *.out, *.err = stdout and stderr output from the script
+    def _build_proc_queue(self, drpc_rows):
+        """
+        Create a queue instance, write the script files for all CPUs
+        using :func:`prepare_for_analysis` and append the job to the
+        queue, and then submit the jobs to the cluster queue.  If
+        :attr:`submit` is False, this is essentially a wrapper for
+        :func:`prepare_for_analysis`.
+
+        This function is written to be functional for submission to the
+        cluster queues both at Utah and using the sciama cluster at
+        Portsmouth; see :func:`_create_queue`.
+
+        .. todo::
+            - This algorithm is slow because it has to search through
+              the drpcomplete fits file each time to find the
+              plate/ifudesign combo.  Should make this more efficient.
+
+        Args:
+            drpc_rows (`numpy.ndarray`):
+                A vector with the rows in the
+                :class:`mangadap.survey.drpcomplete.DRPComplete`
+                database with the DRP files to analyze.
+        """
+        # If submitting to the queue, create the queue object.  The
+        # creation of the queue object is system dependent; however,
+        # once created, the queue object is treated identically for both
+        # the Utah and Portsmouth clusters.
+        if self.create:
+            queue = self._create_queue()
+       
+        # Create the script files, regardless of whether or not they are
+        # submitted to the queue, appending the scripts to the queue if
+        # they are to be submitted
+        ndrp = len(drpc_rows)
+        for i, index in enumerate(drpc_rows):
+            # Write the compute script (write_compute_script also checks
+            # the path exists!)
+            scriptfile, stdoutfile, stderrfile \
+                    = self.write_compute_script(index, dapproc=self.dapproc,
+                                                plots=self.pltifu_plots, overwrite=self.overwrite)
+
+            # Set the status to ready
+            plate = self.drpc['PLATE'][index]
+            ifudesign = self.drpc['IFUDESIGN'][index]
+            self.set_pltifu_status(plate, ifudesign, status='ready')
+
+            if self.create:
+                queue.append('source {0}'.format(scriptfile), outfile=stdoutfile,
+                                  errfile=stderrfile)
+                self.set_pltifu_status(plate, ifudesign, status='queued')
+            else:
+                print(f'Preparing for analysis...{(i+1)*100/ndrp:.1f}%', end='\r')
+
+        if not self.create:
+            print(f'Preparing for analysis...100%')
+
+        return queue if self.create else None
+
+    def _build_post_queue(self, drpc_rows):
+        """
+        Build the queue that performs the post-processing steps.
+        """
+        # Create the queue
+        if self.create:
+            queue = self._create_queue()
+
+        if self.post_process:
+            # Write the DAPall script and set its status
+            dapall_scr, dapall_out, dapall_err \
+                    = self.write_dapall_script(plots=self.post_plots, overwrite=self.overwrite)
+            self.set_status(dapall_scr, status='ready')
+            if self.create:
+                # Add it to the queue
+                queue.append(f'source {dapall_scr}', outfile=dapall_out, errfile=dapall_err)
+                self.set_status(dapall_scr, status='queued')
+
+        # Write the plate QA plot scripts
+        if self.post_plots:
+            # Find the unique plates
+            plates = numpy.unique(self.drpc['PLATE'][drpc_rows])
+            for plt in plates:
+                # Write the plot script for this plate
+                scriptfile, stdoutfile, stderrfile \
+                        = self.write_plate_qa_script(plt, overwrite=self.overwrite)
+                self.set_status(scriptfile, status='ready')
+                
+                if self.create:
+                    queue.append(f'source {scriptfile}', outfile=stdoutfile, errfile=stderrfile)
+                    self.set_status(scriptfile, status='queued')
+
+        return queue if self.create else None
+
+    # ******************************************************************
+    # Management
+    # ******************************************************************
+    def set_pltifu_status(self, plate, ifudesign, status='queued'):
+        """
+        Generate a touch file that signifies the status for a given
+        plate/ifudesign/mode.
+
+        Args:
+            plate (:obj:`int`):
+                Plate number
+            ifudesign (:obj:`int`):
+                IFU design number
+            status (:obj:`str`, optional):
+                The status signifier for the touch file.
+        """
+        # Get the name of the status file        
+        root = manga.MaNGAConfig(plate, ifudesign, drpver=self.drpver,
+                                 redux_path=self.redux_path).cfg_root
+        self.set_status(str(self.calling_path / str(plate) / str(ifudesign) / root), status)
+        
+    def set_status(self, root, status='queued'):
+        """
+        Generate a general touch file.
+
+        Args:
+            root (:obj:`str`): Full path and root name of the touch file
+            status (:obj:`str`, optional):
+                The status signifier for the touch file.
+        """
+        # Touch the status file
+        Path(f'{root}.{status}').touch()
+
+    def _selected_drpfile_rows(self, can_analyze=False):
+        """
+        Find the rows in the
+        :class:`mangadap.survey.drpcomplete.DRPComplete` database with
+        the selected plates and IFUs to analyze.
+
+        Args:
+            can_analyze (:obj:`bool`, optional):
+                In addition to returning the selected rows, only include those
+                rows with DRP datacubes that can be analyzed.  See
+                :func:`mangadap.survey.drpcomplete.DRPComplete.can_analyze`.
+
+        Returns:
+            `numpy.ndarray`_: A vector with the rows in the DRPComplete object
+            with the relevant DRP file data.
+        """
+        # Find the rows in the DRPComplete database with the specified
+        # plate and IFUS
+
+        # All available PLATEIFUs
+        drpc_pltifu = numpy.array([f'{p}-{i}' for p,i in 
+                                        zip(self.drpc['PLATE'],self.drpc['IFUDESIGN'])])
+        # The PLATEIFUs to analyze
+        this_pltifu = numpy.array([f'{p}-{i}' for p,i in 
+                                        zip(self.drpc.platelist, self.drpc.ifudesignlist)])
+
+        # The rows with the PLATEIFUs to analyze
+        rows = numpy.array([numpy.where(drpc_pltifu == pi)[0][0] if pi in drpc_pltifu else -1 \
+                                for pi in this_pltifu])
+        if numpy.any(rows < 0):
+            raise ValueError('Should be able to find all plates and ifus.')
+        if can_analyze:
+            keep = self.drpc.can_analyze()[rows]
+            rows = rows[keep]
+        return rows
+
+    def write_compute_script(self, index, dapproc=True, plots=True, overwrite=False,
+                              relative_symlink=True):
+        """
+        Write the MaNGA DAP script file that is sent to a single CPU to
+        analyze a single DRP file with a given plate and ifudesign.
+
+        Args:
+            index (:obj:`int`, optional):
+                Row in the
+                :class:`mangadap.survey.drpcomplete.DRPComplete`
+                database with the DRP file to analysis.
+            dapproc (:obj:`bool`, optional):
+                Flag to execute the main DAP processing.
+            plots (:obj:`bool`, optional):
+                Create the QA plots.
+            overwrite (:obj:`bool`, optional):
+                Flag to overwrite any existing files.
+            relative_symlink (:obj:`bool`, optional):
+                Set the symlink to the par file to be a relative path
+                instead of the absolute path.
+
+        Returns:
+            :obj:`str`: Three strings with the name of the written
+            script file, the file for the output sent to STDOUT, and the
+            file for the output sent to STDERR.
+        """
+        # Fake out the plan cube to just require the configuration information
+        self.plan.cube = manga.MaNGAConfig(self.drpc['PLATE'][index],
+                                           self.drpc['IFUDESIGN'][index], drpver=self.drpver,
+                                           redux_path=self.redux_path)
+        cfgfile = self.plan.common_path() / f'{self.plan.cube.cfg_root}.ini'
+
+        # Write the configuration file if it doesn't exist
+        if not cfgfile.exists() or overwrite:
+            # overwrite defaults to True
+            self.drpc.write_config(cfgfile, index=index, sres_ext=self.sres_ext,
+                                   sres_fill=self.sres_fill, covar_ext=self.covar_ext)
+            # and create symlinks to it
+            for i, key in enumerate(self.plan.keys()):
+                method_ref_dir = self.plan.method_path(plan_index=i, ref=True)
+                create_symlink(str(cfgfile), str(method_ref_dir),
+                               relative_symlink=relative_symlink, overwrite=overwrite, quiet=True)
+
+        # Set the root path for the scripts, inputs, outputs, and logs
+        _calling_path = self.calling_path / str(self.plan.cube.plate) \
+                            / str(self.plan.cube.ifudesign)
+
+        # Set the names for the script, stdout, and stderr files
+        scriptfile = _calling_path / f'{self.plan.cube.cfg_root}'
+        stdoutfile = Path(f'{scriptfile}.out').resolve()
+        stderrfile = Path(f'{scriptfile}.err').resolve()
+
+        ################################################################
+        # Script file already exists, so just return
+        if scriptfile.exists() and not overwrite:
+            return scriptfile, stdoutfile, stderrfile
+
+        # Main script components are:
+        #   - (dapproc) Run the DAP processing code
+        #   - (plots) Run the plotting scripts to plot the output
+        # ONE OF THESE 2 MUST BE TRUE
+
+        ################################################################
+        # Open the script file and write the date as a commented header
+        # line
+        if not scriptfile.parent.exists():
+            scriptfile.parent.mkdir(parents=True)
+
+        file = open(scriptfile, 'w')
+        file.write('# Auto-generated batch file\n')
+        file.write(f'# {time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())}\n')
+        file.write('\n')
+
+        # Create the started touch file
+        startfile = f'{scriptfile}.started'
+        file.write(f'touch {startfile}\n')
+        file.write('\n')
+
+        # Command that runs the DAP
+        if dapproc:
+            command = f'OMP_NUM_THREADS=1 manga_dap -c {cfgfile} -o {self.analysis_path}'
+            if self.plan_file is not None:
+                command += f' -p {self.plan_file}'
+            if self.log:
+                command += f' --log {scriptfile}.log'
+            if self.verbose > 0:
+                command += (' -'+'v'*self.verbose)
+            file.write(f'{command}\n')
+            file.write('\n')
+
+        # Plotting scripts
+        if plots:
+            command = f'OMP_NUM_THREADS=1 dap_ppxffit_qa -c {cfgfile} -o {self.analysis_path}'
+            if self.plan_file is not None:
+                command += f' -p {self.plan_file}'
+            command += f' -b 2.5'
+            file.write(f'{command}\n')
+            file.write('\n')
+
+            command = f'OMP_NUM_THREADS=1 spotcheck_dap_maps -c {cfgfile} -o {self.analysis_path}'
+            if self.plan_file is not None:
+                command += f' -p {self.plan_file}'
+            command += f' -b 2.5'
+            file.write(f'{command}\n')
+            file.write('\n')
+
+            command = f'OMP_NUM_THREADS=1 dap_fit_residuals -c {cfgfile} -o {self.analysis_path}'
+            if self.plan_file is not None:
+                command += f' -p {self.plan_file}'
+            command += f' -b 2.5'
+            file.write(f'{command}\n')
+            file.write('\n')
+
+        # Touch the done file
+        donefile = f'{scriptfile}.done'
+        file.write(f'touch {donefile}\n')
+        file.write('\n')
+
+        # Done
+        file.close()
+
+        # Return the script file, file for stdout, and file for stderr
+        return scriptfile, stdoutfile, stderrfile
+    
+    def write_dapall_script(self, plots=True, overwrite=False):
+        """
+        Write the script used to construct the DAPall file and its
+        associated quality assessment plots.
+
+        Args:
+            plots (:obj:`bool`, optional):
+                Create the QA plots. Default is True.
+            overwrite (:obj:`bool`, optional):
+                Flag to overwrite any existing files.
+
+        Returns:
+            :obj:`str`: Three strings with the name of the written
+            script file, the file for the output sent to STDOUT, and the
+            file for the output sent to STDERR.
+        """
+        # Check that the path exists, creating it if not
+        if not self.calling_path.exists():
+            self.calling_path.mkdir(parents=True)
+        
+        # Set the names for the script, stdout, and stderr files
+        scriptfile = self.calling_path / 'build_dapall'
+        stdoutfile = f'{scriptfile}.out'
+        stderrfile = f'{scriptfile}.err'
+
+        # Script file already exists, so just return
+        if scriptfile.exists() and not overwrite:
+            return scriptfile, stdoutfile, stderrfile
+
+        # Open the script file and write the date as a commented header
+        # line
+        file = open(scriptfile, 'w')
+        file.write('# Auto-generated batch file\n')
+        file.write(f'# {time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())}\n')
+        file.write('\n')
+
+        # Create the started touch file
+        startfile = f'{scriptfile}.started'
+        file.write(f'touch {startfile}\n')
+        file.write('\n')
+
+        # Command that constructs the DAPall file
+        command = 'OMP_NUM_THREADS=1 '
+        command += f'construct_dapall --drpver {self.drpver} -r {self.redux_path} ' \
+                   f'--dapver {self.dapver} -a {self.analysis_path}'
+        if self.plan_file is not None:
+            command += f' --plan_file {self.plan_file}'
+        if self.verbose > 0:
+            command += (' -'+'v'*self.verbose )
+        file.write(f'{command}\n')
+        file.write('\n')
+
+        # Add the plotting commands
+        if plots:
+            command = f'OMP_NUM_THREADS=1 dapall_qa --drpver {self.drpver} ' \
+                      f'--redux_path {self.redux_path} --dapver {self.dapver} ' \
+                      f'--analysis_path {self.analysis_path}'
+            if self.plan_file is not None:
+                command += f' --plan_file {self.plan_file}'
+            file.write(f'{command}\n')
+            file.write('\n')
+
+        # Touch the done file
+        donefile = f'{scriptfile}.done'
+        file.write(f'touch {donefile}\n')
+        file.write('\n')
+
+        file.close()
+        ################################################################
+
+        # Return the script file, file for stdout, and file for stderr
+        return scriptfile, stdoutfile, stderrfile
+    
+    def write_plate_qa_script(self, plate, overwrite=False):
+        """
+        Write the script used to create the plate fit QA plot.
+
+        Args:
+            plate (:obj:`int`):
+                The plate number for the plot.
+            overwrite (:obj:`bool`, optional):
+                Flag to overwrite any existing files.
+
+        Returns:
+            :obj:`str`: Three strings with the name of the written
+            script file, the file for the output sent to STDOUT, and the
+            file for the output sent to STDERR.
+        """
+        # Check that the path exists, creating it if not
+        path = self.calling_path / str(plate)
+        if not path.exists():
+            path.mkdir(parents=True)
+        
+        # Set the names for the script, stdout, and stderr files
+        scriptfile = self.calling_path / str(plate) / f'{plate}_fitqa'
+        stdoutfile = f'{scriptfile}.out'
+        stderrfile = f'{scriptfile}.err'
+
+        # Script file already exists, so just return
+        if scriptfile.exists() and not overwrite:
+            return scriptfile, stdoutfile, stderrfile
+
+        # Open the script file and write the date as a commented header
+        # line
+        file = open(scriptfile, 'w')
+        file.write('# Auto-generated batch file\n')
+        file.write(f'# {time.strftime("%a %d %b %Y %H:%M:%S",time.localtime())}\n')
+        file.write('\n')
+
+        # Create the started touch file
+        startfile = f'{scriptfile}.started'
+        file.write(f'touch {startfile}\n')
+        file.write('\n')
+
+        # Command that creates the plots
+        command = f'OMP_NUM_THREADS=1 dap_plate_fit_qa {plate} --analysis_path {self.analysis_path}'
+        if self.plan_file is not None:
+            command += f' --plan_file {self.plan_file}'
+        file.write(f'{command}\n')
+        file.write('\n')
+
+        # Touch the done file
+        donefile = f'{scriptfile}.done'
+        file.write(f'touch {donefile}\n')
+        file.write('\n')
+
+        file.close()
+        ################################################################
+
+        # Return the script file, file for stdout, and file for stderr
+        return scriptfile, stdoutfile, stderrfile
+    
+
+
diff --git a/mangadap/util/parser.py b/mangadap/util/parser.py
index 09d88853..275296ac 100644
--- a/mangadap/util/parser.py
+++ b/mangadap/util/parser.py
@@ -94,89 +94,6 @@ def list_to_csl_string(flist):
     return out
 
 
-def parse_drp_file_name(name):
-    """
-    Parse the name of a DRP file to provide the plate, ifudesign, and
-    mode.
-
-    Args:
-        name (str): Name of the DRP file.
-
-    Returns:
-        int, int, str: The plate, ifudesign, and mode ('RSS' or 'CUBE')
-        of the DRP file pulled from the file name.
-
-    Raises:
-        TypeError: Raised if *name* is not a string.
-        ValueError: Raised if if the file name does not look like a DRP
-            file because it does not include 'manga-', '-LOG', or
-            '.fits.gz'.
-    """
-
-    if (type(name) != str):
-        raise TypeError("File name must be a string.")
-    if (str.find(name, 'manga-') == -1 or str.find(name, '-LOG') == -1 or 
-        str.find(name, '.fits.gz') == -1):
-        raise ValueError("String does not look like a DRP fits file name.")
-
-    plate_start = str.find(name, '-')+1
-    plate_end = str.find(name,'-',plate_start)
-    plate = long(name[plate_start:plate_end])
-
-    ifudesign_end = str.find(name,'-',plate_end+1)
-    ifudesign = long(name[plate_end+1:ifudesign_end])
-
-    mode_start = str.find(name,'LOG')+3
-    mode = name[mode_start:str.find(name,'.fits.gz')]
-
-    return plate, ifudesign, mode
-
-
-#def parse_dap_file_name(name):
-#    """
-#    Parse the name of a DAP file and return the plate, ifudesign, mode,
-#    binning type, and iteration number.
-#
-#    Args:
-#        name (str): Name of the DAP file.
-#
-#    Returns:
-#        int, int, str, str, int : The plate, ifudesign, mode ('RSS' or
-#        'CUBE'), bin type, and iteration number of the DAP file, pulled
-#        from the name of the file.
-#
-#    Raises:
-#        TypeError: Raised if *name* is not a string.
-#        ValueError: Raised if if the file name does not look like a DRP
-#            file because it does not include 'manga-', '-BIN', or
-#            '.fits'.
-#    """
-#    if (type(name) != str):
-#        raise TypeError("File name must be a string.")
-#    if (str.find(name, 'manga-') == -1 or str.find(name, '-LOG') == -1
-#        or str.find(name, 'BIN-') == -1 or str.find(name, '.fits') == -1):
-#        raise ValueError("String does not look like a DAP fits file name.")
-#
-#    plate_start = str.find(name, '-')+1
-#    plate_end = str.find(name,'-',plate_start)
-#    plate = int(name[plate_start:plate_end])
-#
-#    ifudesign_end = str.find(name,'-',plate_end+1)
-#    ifudesign = int(name[plate_end+1:ifudesign_end])
-#
-#    mode_start = str.find(name,'LOG')+3
-#    mode_end = str.find(name,'_BIN-')
-#    mode = name[mode_start:mode_end]
-#
-#    bintype_end = str.find(name,'-',mode_end+5)
-#    bintype = name[mode_end+5:bintype_end]
-#
-#    niter_end = str.find(name,'.fits',bintype_end)
-#    niter = int(name[bintype_end+1:niter_end])
-#
-#    return plate, ifudesign, mode, bintype, niter
-
-    
 class DefaultConfig:
     """
     A wrapper for the ConfigParser class that handles None values and
diff --git a/mangadap/config/emission_line_modeling/efitmpl11_bpass.ini b/outofdate/emission_line_modeling/efitmpl11_bpass.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11_bpass.ini
rename to outofdate/emission_line_modeling/efitmpl11_bpass.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11_ismmask.ini b/outofdate/emission_line_modeling/efitmpl11_ismmask.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11_ismmask.ini
rename to outofdate/emission_line_modeling/efitmpl11_ismmask.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11_miles.ini b/outofdate/emission_line_modeling/efitmpl11_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11_miles.ini
rename to outofdate/emission_line_modeling/efitmpl11_miles.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11_miuscatthin.ini b/outofdate/emission_line_modeling/efitmpl11_miuscatthin.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11_miuscatthin.ini
rename to outofdate/emission_line_modeling/efitmpl11_miuscatthin.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11db_miles.ini b/outofdate/emission_line_modeling/efitmpl11db_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11db_miles.ini
rename to outofdate/emission_line_modeling/efitmpl11db_miles.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11hc.ini b/outofdate/emission_line_modeling/efitmpl11hc.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11hc.ini
rename to outofdate/emission_line_modeling/efitmpl11hc.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11hcdb.ini b/outofdate/emission_line_modeling/efitmpl11hcdb.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11hcdb.ini
rename to outofdate/emission_line_modeling/efitmpl11hcdb.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11ssp.ini b/outofdate/emission_line_modeling/efitmpl11ssp.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11ssp.ini
rename to outofdate/emission_line_modeling/efitmpl11ssp.ini
diff --git a/mangadap/config/emission_line_modeling/efitmpl11sspdb.ini b/outofdate/emission_line_modeling/efitmpl11sspdb.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/efitmpl11sspdb.ini
rename to outofdate/emission_line_modeling/efitmpl11sspdb.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitf.ini b/outofdate/emission_line_modeling/outofdate/efitf.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitf.ini
rename to outofdate/emission_line_modeling/outofdate/efitf.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitm.ini b/outofdate/emission_line_modeling/outofdate/efitm.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitm.ini
rename to outofdate/emission_line_modeling/outofdate/efitm.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitm_miuscat.ini b/outofdate/emission_line_modeling/outofdate/efitm_miuscat.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitm_miuscat.ini
rename to outofdate/emission_line_modeling/outofdate/efitm_miuscat.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmab.ini b/outofdate/emission_line_modeling/outofdate/efitmab.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmab.ini
rename to outofdate/emission_line_modeling/outofdate/efitmab.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmb.ini b/outofdate/emission_line_modeling/outofdate/efitmb.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmb.ini
rename to outofdate/emission_line_modeling/outofdate/efitmb.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmdb.ini b/outofdate/emission_line_modeling/outofdate/efitmdb.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmdb.ini
rename to outofdate/emission_line_modeling/outofdate/efitmdb.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl10.ini b/outofdate/emission_line_modeling/outofdate/efitmpl10.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl10.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl10.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl10_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl10_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl10_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl10_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl10db.ini b/outofdate/emission_line_modeling/outofdate/efitmpl10db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl10db.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl10db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl10db_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl10db_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl10db_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl10db_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8_bpass.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8_bpass.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8_bpass.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8_bpass.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscat.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8_miuscat.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscat.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8_miuscat.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscat_noism.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8_miuscat_noism.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscat_noism.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8_miuscat_noism.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscatthin.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8_miuscatthin.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8_miuscatthin.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8_miuscatthin.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8db.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8db.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl8db_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl8db_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl8db_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl8db_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9_bpass.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9_bpass.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9_bpass.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9_bpass.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9_ismmask.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9_ismmask.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9_ismmask.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9_ismmask.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9_miuscatthin.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9_miuscatthin.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9_miuscatthin.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9_miuscatthin.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9db.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9db.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmpl9db_miles.ini b/outofdate/emission_line_modeling/outofdate/efitmpl9db_miles.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmpl9db_miles.ini
rename to outofdate/emission_line_modeling/outofdate/efitmpl9db_miles.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmr.ini b/outofdate/emission_line_modeling/outofdate/efitmr.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmr.ini
rename to outofdate/emission_line_modeling/outofdate/efitmr.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitmrdb.ini b/outofdate/emission_line_modeling/outofdate/efitmrdb.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitmrdb.ini
rename to outofdate/emission_line_modeling/outofdate/efitmrdb.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efits.ini b/outofdate/emission_line_modeling/outofdate/efits.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efits.ini
rename to outofdate/emission_line_modeling/outofdate/efits.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitt0db.ini b/outofdate/emission_line_modeling/outofdate/efitt0db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitt0db.ini
rename to outofdate/emission_line_modeling/outofdate/efitt0db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitt1db.ini b/outofdate/emission_line_modeling/outofdate/efitt1db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitt1db.ini
rename to outofdate/emission_line_modeling/outofdate/efitt1db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitt2db.ini b/outofdate/emission_line_modeling/outofdate/efitt2db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitt2db.ini
rename to outofdate/emission_line_modeling/outofdate/efitt2db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/efitt3db.ini b/outofdate/emission_line_modeling/outofdate/efitt3db.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/efitt3db.ini
rename to outofdate/emission_line_modeling/outofdate/efitt3db.ini
diff --git a/mangadap/config/emission_line_modeling/outofdate/unneeded_efitm_bpass.ini b/outofdate/emission_line_modeling/outofdate/unneeded_efitm_bpass.ini
similarity index 100%
rename from mangadap/config/emission_line_modeling/outofdate/unneeded_efitm_bpass.ini
rename to outofdate/emission_line_modeling/outofdate/unneeded_efitm_bpass.ini
diff --git a/mangadap/config/emission_line_moments/emomf.ini b/outofdate/emission_line_moments/emomf.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emomf.ini
rename to outofdate/emission_line_moments/emomf.ini
diff --git a/mangadap/config/emission_line_moments/emomm.ini b/outofdate/emission_line_moments/emomm.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emomm.ini
rename to outofdate/emission_line_moments/emomm.ini
diff --git a/mangadap/config/emission_line_moments/emommab.ini b/outofdate/emission_line_moments/emommab.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommab.ini
rename to outofdate/emission_line_moments/emommab.ini
diff --git a/mangadap/config/emission_line_moments/emommpl10.ini b/outofdate/emission_line_moments/emommpl10.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommpl10.ini
rename to outofdate/emission_line_moments/emommpl10.ini
diff --git a/mangadap/config/emission_line_moments/emommpl11.ini b/outofdate/emission_line_moments/emommpl11.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommpl11.ini
rename to outofdate/emission_line_moments/emommpl11.ini
diff --git a/mangadap/config/emission_line_moments/emommpl8-siii.ini b/outofdate/emission_line_moments/emommpl8-siii.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommpl8-siii.ini
rename to outofdate/emission_line_moments/emommpl8-siii.ini
diff --git a/mangadap/config/emission_line_moments/emommpl8.ini b/outofdate/emission_line_moments/emommpl8.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommpl8.ini
rename to outofdate/emission_line_moments/emommpl8.ini
diff --git a/mangadap/config/emission_line_moments/emommpl9.ini b/outofdate/emission_line_moments/emommpl9.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emommpl9.ini
rename to outofdate/emission_line_moments/emommpl9.ini
diff --git a/mangadap/config/emission_line_moments/emoms.ini b/outofdate/emission_line_moments/emoms.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emoms.ini
rename to outofdate/emission_line_moments/emoms.ini
diff --git a/mangadap/config/emission_line_moments/emomt.ini b/outofdate/emission_line_moments/emomt.ini
similarity index 100%
rename from mangadap/config/emission_line_moments/emomt.ini
rename to outofdate/emission_line_moments/emomt.ini
diff --git a/etc/config.ini b/outofdate/etc/config.ini
similarity index 100%
rename from etc/config.ini
rename to outofdate/etc/config.ini
diff --git a/etc/mangadap.module b/outofdate/etc/mangadap.module
similarity index 100%
rename from etc/mangadap.module
rename to outofdate/etc/mangadap.module
diff --git a/example_ini/example_absorption_index_config.ini b/outofdate/example_ini/example_absorption_index_config.ini
similarity index 100%
rename from example_ini/example_absorption_index_config.ini
rename to outofdate/example_ini/example_absorption_index_config.ini
diff --git a/example_ini/example_bandhead_index_config.ini b/outofdate/example_ini/example_bandhead_index_config.ini
similarity index 100%
rename from example_ini/example_bandhead_index_config.ini
rename to outofdate/example_ini/example_bandhead_index_config.ini
diff --git a/example_ini/example_bitmask_config.ini b/outofdate/example_ini/example_bitmask_config.ini
similarity index 100%
rename from example_ini/example_bitmask_config.ini
rename to outofdate/example_ini/example_bitmask_config.ini
diff --git a/example_ini/example_emission_bandpass_filter_config.ini b/outofdate/example_ini/example_emission_bandpass_filter_config.ini
similarity index 100%
rename from example_ini/example_emission_bandpass_filter_config.ini
rename to outofdate/example_ini/example_emission_bandpass_filter_config.ini
diff --git a/example_ini/example_emission_line_config.ini b/outofdate/example_ini/example_emission_line_config.ini
similarity index 100%
rename from example_ini/example_emission_line_config.ini
rename to outofdate/example_ini/example_emission_line_config.ini
diff --git a/example_ini/example_reduction_assessment_config.ini b/outofdate/example_ini/example_reduction_assessment_config.ini
similarity index 100%
rename from example_ini/example_reduction_assessment_config.ini
rename to outofdate/example_ini/example_reduction_assessment_config.ini
diff --git a/example_ini/example_spatial_binning_config.ini b/outofdate/example_ini/example_spatial_binning_config.ini
similarity index 100%
rename from example_ini/example_spatial_binning_config.ini
rename to outofdate/example_ini/example_spatial_binning_config.ini
diff --git a/example_ini/example_spectral_template_config.ini b/outofdate/example_ini/example_spectral_template_config.ini
similarity index 100%
rename from example_ini/example_spectral_template_config.ini
rename to outofdate/example_ini/example_spectral_template_config.ini
diff --git a/example_par/dr15_plan.par b/outofdate/example_par/dr15_plan.par
similarity index 100%
rename from example_par/dr15_plan.par
rename to outofdate/example_par/dr15_plan.par
diff --git a/mangadap/config/reduction_assessments/snrg.ini b/outofdate/reduction_assessments/snrg.ini
similarity index 100%
rename from mangadap/config/reduction_assessments/snrg.ini
rename to outofdate/reduction_assessments/snrg.ini
diff --git a/mangadap/config/reduction_assessments/snrr.ini b/outofdate/reduction_assessments/snrr.ini
similarity index 100%
rename from mangadap/config/reduction_assessments/snrr.ini
rename to outofdate/reduction_assessments/snrr.ini
diff --git a/mangadap/config/spatial_binning/all.ini b/outofdate/spatial_binning/all.ini
similarity index 100%
rename from mangadap/config/spatial_binning/all.ini
rename to outofdate/spatial_binning/all.ini
diff --git a/mangadap/config/spatial_binning/hybrid10.ini b/outofdate/spatial_binning/hybrid10.ini
similarity index 100%
rename from mangadap/config/spatial_binning/hybrid10.ini
rename to outofdate/spatial_binning/hybrid10.ini
diff --git a/mangadap/config/spatial_binning/nre.ini b/outofdate/spatial_binning/nre.ini
similarity index 100%
rename from mangadap/config/spatial_binning/nre.ini
rename to outofdate/spatial_binning/nre.ini
diff --git a/mangadap/config/spatial_binning/spx.ini b/outofdate/spatial_binning/spx.ini
similarity index 100%
rename from mangadap/config/spatial_binning/spx.ini
rename to outofdate/spatial_binning/spx.ini
diff --git a/mangadap/config/spatial_binning/square2_0.ini b/outofdate/spatial_binning/square2_0.ini
similarity index 100%
rename from mangadap/config/spatial_binning/square2_0.ini
rename to outofdate/spatial_binning/square2_0.ini
diff --git a/mangadap/config/spatial_binning/vor10.ini b/outofdate/spatial_binning/vor10.ini
similarity index 100%
rename from mangadap/config/spatial_binning/vor10.ini
rename to outofdate/spatial_binning/vor10.ini
diff --git a/mangadap/config/spectral_indices/bhindxn.ini b/outofdate/spectral_indices/bhindxn.ini
similarity index 100%
rename from mangadap/config/spectral_indices/bhindxn.ini
rename to outofdate/spectral_indices/bhindxn.ini
diff --git a/mangadap/config/spectral_indices/indxe.ini b/outofdate/spectral_indices/indxe.ini
similarity index 100%
rename from mangadap/config/spectral_indices/indxe.ini
rename to outofdate/spectral_indices/indxe.ini
diff --git a/mangadap/config/spectral_indices/indxen.ini b/outofdate/spectral_indices/indxen.ini
similarity index 100%
rename from mangadap/config/spectral_indices/indxen.ini
rename to outofdate/spectral_indices/indxen.ini
diff --git a/mangadap/config/spectral_indices/indxend.ini b/outofdate/spectral_indices/indxend.ini
similarity index 100%
rename from mangadap/config/spectral_indices/indxend.ini
rename to outofdate/spectral_indices/indxend.ini
diff --git a/mangadap/config/stellar_continuum_modeling/gau_bc03.ini b/outofdate/stellar_continuum_modeling/gau_bc03.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/gau_bc03.ini
rename to outofdate/stellar_continuum_modeling/gau_bc03.ini
diff --git a/mangadap/config/stellar_continuum_modeling/gau_m11_marcs.ini b/outofdate/stellar_continuum_modeling/gau_m11_marcs.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/gau_m11_marcs.ini
rename to outofdate/stellar_continuum_modeling/gau_m11_marcs.ini
diff --git a/mangadap/config/stellar_continuum_modeling/gau_m11_miles.ini b/outofdate/stellar_continuum_modeling/gau_m11_miles.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/gau_m11_miles.ini
rename to outofdate/stellar_continuum_modeling/gau_m11_miles.ini
diff --git a/mangadap/config/stellar_continuum_modeling/gau_miuscat_thin.ini b/outofdate/stellar_continuum_modeling/gau_miuscat_thin.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/gau_miuscat_thin.ini
rename to outofdate/stellar_continuum_modeling/gau_miuscat_thin.ini
diff --git a/mangadap/config/stellar_continuum_modeling/mileshc_noism.ini b/outofdate/stellar_continuum_modeling/mileshc_noism.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/mileshc_noism.ini
rename to outofdate/stellar_continuum_modeling/mileshc_noism.ini
diff --git a/mangadap/config/stellar_continuum_modeling/mileshcmpl11.ini b/outofdate/stellar_continuum_modeling/mileshcmpl11.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/mileshcmpl11.ini
rename to outofdate/stellar_continuum_modeling/mileshcmpl11.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/gau_mileshc.ini b/outofdate/stellar_continuum_modeling/outofdate/gau_mileshc.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/gau_mileshc.ini
rename to outofdate/stellar_continuum_modeling/outofdate/gau_mileshc.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl10.ini b/outofdate/stellar_continuum_modeling/outofdate/mileshcmpl10.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl10.ini
rename to outofdate/stellar_continuum_modeling/outofdate/mileshcmpl10.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl8.ini b/outofdate/stellar_continuum_modeling/outofdate/mileshcmpl8.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl8.ini
rename to outofdate/stellar_continuum_modeling/outofdate/mileshcmpl8.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl8_noism.ini b/outofdate/stellar_continuum_modeling/outofdate/mileshcmpl8_noism.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl8_noism.ini
rename to outofdate/stellar_continuum_modeling/outofdate/mileshcmpl8_noism.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl9.ini b/outofdate/stellar_continuum_modeling/outofdate/mileshcmpl9.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/mileshcmpl9.ini
rename to outofdate/stellar_continuum_modeling/outofdate/mileshcmpl9.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/seoul.ini b/outofdate/stellar_continuum_modeling/outofdate/seoul.ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/seoul.ini
rename to outofdate/stellar_continuum_modeling/outofdate/seoul.ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/unneeded_gau_bpass.hide_ini b/outofdate/stellar_continuum_modeling/outofdate/unneeded_gau_bpass.hide_ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/unneeded_gau_bpass.hide_ini
rename to outofdate/stellar_continuum_modeling/outofdate/unneeded_gau_bpass.hide_ini
diff --git a/mangadap/config/stellar_continuum_modeling/outofdate/unneeded_gau_mileshc_noism.hide_ini b/outofdate/stellar_continuum_modeling/outofdate/unneeded_gau_mileshc_noism.hide_ini
similarity index 100%
rename from mangadap/config/stellar_continuum_modeling/outofdate/unneeded_gau_mileshc_noism.hide_ini
rename to outofdate/stellar_continuum_modeling/outofdate/unneeded_gau_mileshc_noism.hide_ini
diff --git a/mangadap/survey/util.py b/outofdate/survey.util.py
similarity index 100%
rename from mangadap/survey/util.py
rename to outofdate/survey.util.py
diff --git a/readthedocs.yml b/readthedocs.yml
index 7d1b0206..a50c2d46 100644
--- a/readthedocs.yml
+++ b/readthedocs.yml
@@ -4,7 +4,7 @@ build:
    image: latest
 
 python:
-   version: 3.9
+   version: 3.8
    install:
      - method: pip
        path: .
diff --git a/setup.cfg b/setup.cfg
index a7b08181..50296781 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-name = mangadap
+name = sdss-mangadap
 description = MaNGA Data Analysis Pipeline
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -34,6 +34,7 @@ install_requires =
     requests>=2.25
     ipython>=7.20
     configobj>=5.0.6
+    tomli>=2.0
     numpy>=1.19
     scipy>=1.5
     matplotlib>=3.3
@@ -41,8 +42,8 @@ install_requires =
     pydl>=0.7.0
     ppxf==7.4.3
     vorbin==3.1.4
-scripts =
-    bin/rundap
+#scripts =
+#    bin/rundap
 
 [options.extras_require]
 test =
@@ -83,6 +84,7 @@ console_scripts =
     dapall_qa = mangadap.scripts.dapall_qa:DapAllQA.entry_point
     manga_dap = mangadap.scripts.manga_dap:MangaDap.entry_point
     manga_dap_inspector = mangadap.scripts.manga_dap_inspector:MangaDapInspector.entry_point
+    rundap = mangadap.scripts.rundap:RunDap.entry_point
     spotcheck_dap_maps = mangadap.scripts.spotcheck_dap_maps:SpotcheckDapMaps.entry_point
     write_dap_config = mangadap.scripts.write_dap_config:WriteDapConfig.entry_point
 
diff --git a/setup.py b/setup.py
index 965ce5f4..9e7ecc77 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
 # Adapted from pypeit


Test patch:
diff --git a/mangadap/tests/test_analysisplan.py b/mangadap/tests/test_analysisplan.py
index c61b910e..76d19eb6 100644
--- a/mangadap/tests/test_analysisplan.py
+++ b/mangadap/tests/test_analysisplan.py
@@ -2,7 +2,6 @@
 from IPython import embed
 
 from mangadap.tests.util import data_test_file
-#from mangadap.config.analysisplan import AnalysisPlanSet, NewAnalysisPlan
 from mangadap.config.analysisplan import AnalysisPlan
 from mangadap.config.manga import MaNGAAnalysisPlan, MaNGAConfig
 
@@ -13,7 +12,7 @@ def test_default():
     assert list(plan.keys())[0] == 'default', 'Name changed'
 
     assert plan.rdxqa['default']['key'] == 'SNRG', 'Default DRP reduction QA key changed.'
-    assert plan.elfit['default']['key'] == 'EFITSSP', 'Default emission-line fit key changed.'
+    assert plan.elfit['default']['key'] == 'EFITHC2', 'Default emission-line fit key changed.'
     assert plan.common_path() == Path('.').resolve() / 'common', 'Common path changed'