diff --git a/src/Science.hpp b/src/Science.hpp
index 89d4c884a0dc956dabc65d9630cf030f65cd980a..cd5c38f754ead297aca9c7e09f6a338d2d164096 100644
--- a/src/Science.hpp
+++ b/src/Science.hpp
@@ -253,14 +253,38 @@ void compute_lambda2(TArrayn::DTArray & lambda2, TArrayn::DTArray & u,
 /*  Creates and outputs data for plotting a bivariate histogram with NS*NT bins
  *  between tracers S1 and T1 to "filename.csv" (don't include file extension).
  */
-void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
-              const TArrayn::DTArray &v, const TArrayn::DTArray &w,
-              const char T1_name, const char S1_name, const int NS,
-              const int NT, double T1_max, double S1_max, double T1_min,
-              double S1_min, const int Nx, const int Ny, const int Nz,
-              string filename, const int plotnum, bool mapped,
-              TArrayn::DTArray *xgrid, TArrayn::DTArray *ygrid,
-              TArrayn::DTArray *zgrid);
+struct QSPOptions {
+  int NS;
+  int NT;
+  string filename;
+  double S1_max;
+  double S1_min;
+  double T1_max;
+  double T1_min;
+  string T1_name;
+  string S1_name;
+};
+
+struct QSPData {
+  TArrayn::DTArray *u;
+  TArrayn::DTArray *v;
+  TArrayn::DTArray *w;
+  TArrayn::DTArray *temp;
+  TArrayn::DTArray *rho;
+  TArrayn::DTArray *salinity;
+  TArrayn::DTArray *custom_T1;
+  TArrayn::DTArray *custom_S1;
+  TArrayn::DTArray *xgrid;
+  TArrayn::DTArray *ygrid;
+  TArrayn::DTArray *zgrid;
+  int Nx;
+  int Ny;
+  int Nz;
+  int plotnum;
+  bool mapped;
+};
+
+void QSPCount(QSPOptions qsp_options, QSPData qsp_data);
 
 // Equation of state for seawater, polynomial fit from
 // Brydon, Sun, Bleck (1999) (JGR)
diff --git a/src/Science/QSPCount.cpp b/src/Science/QSPCount.cpp
index e2f73e92bb97613f1a849e3312a7bc6e926bcded..25fdae08abca743c8d0c48e222fd7722e134f65a 100644
--- a/src/Science/QSPCount.cpp
+++ b/src/Science/QSPCount.cpp
@@ -58,175 +58,282 @@ public:
   ~QSPVector() { free(data); }
 };
 
-int index(int row, int col, int rows, int cols) {
-  assert(row >= 0 && row < rows);
-  assert(col >= 0 && col < cols);
-  return (row * cols) + col;
+enum QSPType {
+  QSP_u,
+  QSP_v,
+  QSP_w,
+  QSP_ke,
+  QSP_temp,
+  QSP_rho,
+  QSP_salinity,
+  QSP_custom,
+};
+
+void QSP_write(int local_rank, const QSPVector &local_hist,
+               const QSPOptions &qsp_options, const QSPData &qsp_data) {
+  if (local_rank == 0) {
+    QSPVector glob_hist(qsp_options.NS, qsp_options.NT);
+    MPI_Reduce(local_hist.raw(), glob_hist.raw(), // send and receive buffers
+               qsp_options.NS * qsp_options.NT,   // Count
+               MPI_DOUBLE,                        // datatype
+               MPI_SUM, 0,      // Reduction operator and root process #
+               MPI_COMM_WORLD); // Communicator
+    string filename = qsp_options.filename + "." +
+                      boost::lexical_cast<string>(qsp_data.plotnum) + ".csv";
+    std::fstream outfile;
+    outfile.open(filename.c_str(), std::ios_base::out);
+    if (outfile.is_open()) {
+      outfile << qsp_options.T1_max << ',' << qsp_options.T1_min << ','
+              << qsp_options.S1_max << ',' << qsp_options.S1_min;
+      for (int i = 4; i < qsp_options.NT; i++) {
+        outfile << ',' << 0;
+      }
+      outfile << std::endl;
+      for (int ii = 0; ii < qsp_options.NS; ii++) {
+        outfile << glob_hist(ii, 0);
+        for (int jj = 1; jj < qsp_options.NT; jj++) {
+          outfile << ',' << glob_hist(ii, jj);
+        }
+        outfile << std::endl;
+      }
+    }
+  } else {
+    MPI_Reduce(local_hist.raw(), NULL,          // send and receive buffers
+               qsp_options.NS * qsp_options.NT, // count
+               MPI_DOUBLE,                      // datatype
+               MPI_SUM, 0,      // Reduction operator and root process
+               MPI_COMM_WORLD); // Communicator
+  }
 }
 
-void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
-              const TArrayn::DTArray &v, const TArrayn::DTArray &w,
-              const char T1_name, const char S1_name, const int NS,
-              const int NT, double T1_max, double S1_max, double T1_min,
-              double S1_min, const int Nx, const int Ny, const int Nz,
-              string filename, const int plotnum, bool mapped,
-              TArrayn::DTArray *xgrid, TArrayn::DTArray *ygrid,
-              TArrayn::DTArray *zgrid) {
+QSPType QSPConvert(const std::string &name) {
+  QSPType converted_type;
+  if (name.compare("u") == 0) {
+    converted_type = QSP_u;
+  } else if (name.compare("v") == 0) {
+    converted_type = QSP_v;
+  } else if (name.compare("w") == 0) {
+    converted_type = QSP_w;
+  } else if (name.compare("ke") == 0) {
+    converted_type = QSP_ke;
+  } else if (name.compare("temp") == 0) {
+    converted_type = QSP_temp;
+  } else if (name.compare("rho") == 0) {
+    converted_type = QSP_rho;
+  } else if (name.compare("salinity") == 0) {
+    converted_type = QSP_salinity;
+  } else if (name.compare("custom") == 0) {
+    converted_type = QSP_custom;
+  }
+  return converted_type;
+}
 
-  int local_rank;
-  MPI_Comm_rank(MPI_COMM_WORLD, &local_rank);
-  const TArrayn::DTArray *T1_ptr = NULL, *S1_ptr = NULL;
+TArrayn::DTArray *QSPPtr(const QSPData &qsp_data, const QSPType &type) {
+  TArrayn::DTArray *ptr = NULL;
 
-  switch (T1_name) {
-  case 'u':
-    T1_ptr = &u;
+  switch (type) {
+  case QSP_u:
+    ptr = qsp_data.u;
     break;
-  case 'v':
-    T1_ptr = &v;
+  case QSP_v:
+    ptr = qsp_data.v;
     break;
-  case 'w':
-    T1_ptr = &w;
+  case QSP_w:
+    ptr = qsp_data.w;
     break;
-  case 'k':
+  case QSP_ke: // This is an odd case.
     break;
-  case 't':
-    T1_ptr = &t;
+  case QSP_rho:
+    ptr = qsp_data.rho;
     break;
-  case 'T':
-    T1_ptr = &t;
+  case QSP_temp:
+    ptr = qsp_data.temp;
     break;
-  default:
-    return;
-  }
-
-  switch (S1_name) {
-  case 'u':
-    S1_ptr = &u;
-    break;
-  case 'v':
-    S1_ptr = &v;
-    break;
-  case 'w':
-    S1_ptr = &w;
-    break;
-  case 'k':
+  case QSP_salinity:
+    ptr = qsp_data.salinity;
     break;
-  case 't':
-    S1_ptr = &t;
+  case QSP_custom: // Make sure to set the pointer yourself. Can't do it here.
     break;
-  case 'T':
-    S1_ptr = &t;
-    break;
-  default:
-    return;
   }
 
-  int i_low = u.lbound(blitz::firstDim);
-  int j_low = u.lbound(blitz::secondDim);
-  int k_low = u.lbound(blitz::thirdDim);
-  int i_high = u.ubound(blitz::firstDim);
-  int j_high = u.ubound(blitz::secondDim);
-  int k_high = u.ubound(blitz::thirdDim);
+  return ptr;
+}
 
-  // This block calculates the global min/max values, in case the user
-  // didn't want to specify them.
+// compute the minimum and maximum values for either tracer, then substitute
+// any default values of +- infinity with the global min/max values instead.
+void QSPMaxMin(const QSPType &T1_type, const QSPType &S1_type,
+               TArrayn::DTArray *T1_ptr, TArrayn::DTArray *S1_ptr,
+               QSPOptions &qsp_options, const QSPData &qsp_data, int i_low,
+               int j_low, int k_low, int i_high, int j_high, int k_high) {
   double double_max = std::numeric_limits<double>::max();
-  double double_min = std::numeric_limits<double>::min();
-  if (T1_max == double_max || S1_max == double_max || S1_max == double_min ||
-      S1_min == double_min) {
 
-    // If One of the variables is K.E or rho, we need to hand-roll the max/min
-    // since, in general, the index of max(u) is the same as the index of max(v)
-    if (T1_name == 'k' || T1_name == 't' || S1_name == 'k' || S1_name == 't') {
-      double ke_max = -double_max, ke_min = double_max;
-      double rho_max = -double_max, rho_min = double_max;
-
-      // Main hand-rolled loop
-      for (int i = i_low; i <= i_high; i++) {
-        for (int j = j_low; j <= j_high; j++) {
-          for (int k = k_low; k <= k_high; k++) {
-            double ke_current = 0, tmp = 0;
-            if (Nx > 1) {
-              tmp = u(i, j, k);
-              ke_current += tmp * tmp;
-            }
-            if (Ny > 1) {
-              tmp = v(i, j, k);
-              ke_current += tmp * tmp;
-            }
-            if (Nz > 1) {
-              tmp = w(i, j, k);
+  // If One of the variables is K.E or rho, we need to hand-roll the max / min
+  // since, in general, the index of max(u) is the same as the index of max(v)
+  if (T1_type == QSP_ke || T1_type == QSP_rho || S1_type == QSP_ke ||
+      S1_type == QSP_rho) {
+    double ke_max = -double_max, ke_min = double_max;
+    double rho_max = -double_max, rho_min = double_max;
+    // Main hand-rolled loop
+    for (int i = i_low; i <= i_high; i++) {
+      for (int j = j_low; j <= j_high; j++) {
+        for (int k = k_low; k <= k_high; k++) {
+          double tmp;
+          if (T1_type == QSP_ke || S1_type == QSP_ke) {
+            double ke_current = 0;
+            tmp = (*qsp_data.u)(i, j, k);
+            ke_current += tmp * tmp;
+            if (qsp_data.Ny > 1) {
+              tmp = (*qsp_data.v)(i, j, k);
               ke_current += tmp * tmp;
             }
+            tmp = (*qsp_data.w)(i, j, k);
+            ke_current += tmp * tmp;
             ke_current = 0.5 * ke_current;
-            double rho_current = eqn_of_state_t(t(i, j, k));
             ke_max = ke_current > ke_max ? ke_current : ke_max;
             ke_min = ke_current < ke_min ? ke_current : ke_min;
+          }
+          if (T1_type == QSP_rho || S1_type == QSP_rho) {
+            double rho_current;
+            if (qsp_data.rho) {
+              rho_current = (*qsp_data.rho)(i, j, k);
+            } else if (qsp_data.temp && !qsp_data.salinity) {
+              rho_current = eqn_of_state_t((*qsp_data.temp)(i, j, k));
+            } else if (qsp_data.temp && qsp_data.salinity) {
+              rho_current = eqn_of_state((*qsp_data.temp)(i, j, k),
+                                         (*qsp_data.salinity)(i, j, k));
+            } else {
+              rho_current = 0;
+            }
             rho_max = rho_current > rho_max ? rho_current : rho_max;
             rho_min = rho_current < rho_min ? rho_current : rho_min;
           }
         }
       }
+    }
+    double glob_ke_max, glob_ke_min, glob_rho_max, glob_rho_min;
+    MPI_Allreduce(&ke_max, &glob_ke_max, 1, MPI_DOUBLE, MPI_MAX,
+                  MPI_COMM_WORLD);
+    MPI_Allreduce(&ke_min, &glob_ke_min, 1, MPI_DOUBLE, MPI_MIN,
+                  MPI_COMM_WORLD);
+    MPI_Allreduce(&rho_max, &glob_rho_max, 1, MPI_DOUBLE, MPI_MAX,
+                  MPI_COMM_WORLD);
+    MPI_Allreduce(&rho_min, &glob_rho_min, 1, MPI_DOUBLE, MPI_MIN,
+                  MPI_COMM_WORLD);
+    switch (T1_type) {
+    case QSP_ke:
+      qsp_options.T1_max =
+          qsp_options.T1_max == double_max ? glob_ke_max : qsp_options.T1_max;
+      qsp_options.T1_min =
+          qsp_options.T1_min == -double_max ? glob_ke_min : qsp_options.T1_min;
+      break;
+    case QSP_rho:
+      qsp_options.T1_max =
+          qsp_options.T1_max == double_max ? glob_rho_max : qsp_options.T1_max;
+      qsp_options.T1_min =
+          qsp_options.T1_min == -double_max ? glob_rho_min : qsp_options.T1_min;
+      break;
+    default:
+      qsp_options.T1_max = qsp_options.T1_max == double_max
+                               ? psmax(max(*T1_ptr))
+                               : qsp_options.T1_max;
+      qsp_options.T1_min = qsp_options.T1_min == -double_max
+                               ? psmin(min(*T1_ptr))
+                               : qsp_options.T1_min;
+      break;
+    }
+    switch (S1_type) {
+    case QSP_ke:
+      qsp_options.S1_max =
+          qsp_options.S1_max == double_max ? glob_ke_max : qsp_options.S1_max;
+      qsp_options.S1_min =
+          qsp_options.S1_min == -double_max ? glob_ke_min : qsp_options.S1_min;
+      break;
+    case QSP_rho:
+      qsp_options.S1_max =
+          qsp_options.S1_max == double_max ? glob_rho_max : qsp_options.S1_max;
+      qsp_options.S1_min =
+          qsp_options.S1_min == -double_max ? glob_rho_min : qsp_options.S1_min;
+      break;
+    default:
+      qsp_options.S1_max = qsp_options.S1_max == double_max
+                               ? psmax(max(*S1_ptr))
+                               : qsp_options.S1_max;
+      qsp_options.S1_min = qsp_options.S1_min == -double_max
+                               ? psmin(min(*S1_ptr))
+                               : qsp_options.S1_min;
+      break;
+    }
+  } else { // !(cond1 || cond2) == !cond1 && !cond2
+    qsp_options.S1_max = psmax(max(*S1_ptr));
+    qsp_options.S1_min = psmin(min(*S1_ptr));
+    qsp_options.T1_max = psmax(max(*T1_ptr));
+    qsp_options.T1_min = psmin(min(*T1_ptr));
+  }
+}
 
-      double glob_ke_max, glob_ke_min, glob_rho_max, glob_rho_min;
-      MPI_Allreduce(&ke_max, &glob_ke_max, 1, MPI_DOUBLE, MPI_MAX,
-                    MPI_COMM_WORLD);
-      MPI_Allreduce(&ke_min, &glob_ke_min, 1, MPI_DOUBLE, MPI_MIN,
-                    MPI_COMM_WORLD);
-      MPI_Allreduce(&rho_max, &glob_rho_max, 1, MPI_DOUBLE, MPI_MAX,
-                    MPI_COMM_WORLD);
-      MPI_Allreduce(&rho_min, &glob_rho_min, 1, MPI_DOUBLE, MPI_MIN,
-                    MPI_COMM_WORLD);
+void QSPCount(QSPOptions qsp_options, QSPData qsp_data) {
 
-      switch (T1_name) {
-      case 'k':
-        T1_max = glob_ke_max;
-        T1_min = glob_ke_min;
-        break;
-      case 't':
-        T1_max = glob_rho_max;
-        T1_min = glob_rho_min;
-        break;
-      default:
-        T1_max = psmax(max(*T1_ptr));
-        T1_min = psmin(min(*T1_ptr));
-        break;
-      }
+  int local_rank;
+  MPI_Comm_rank(MPI_COMM_WORLD, &local_rank);
 
-      switch (S1_name) {
-      case 'k':
-        S1_max = glob_ke_max;
-        S1_min = glob_ke_min;
-        break;
-      case 't':
-        S1_max = glob_rho_max;
-        S1_min = glob_rho_min;
-        break;
-      default:
-        S1_max = psmax(max(*S1_ptr));
-        S1_min = psmin(min(*S1_ptr));
-        break;
-      }
-    } else { // !(cond1 || cond2) == !cond1 && !cond2
-      S1_max = psmax(max(*S1_ptr));
-      S1_min = psmin(min(*S1_ptr));
-      T1_max = psmax(max(*T1_ptr));
-      T1_min = psmin(min(*T1_ptr));
+  // Find out what
+  QSPType S1_type = QSPConvert(qsp_options.S1_name);
+  QSPType T1_type = QSPConvert(qsp_options.T1_name);
+  TArrayn::DTArray *S1_ptr = QSPPtr(qsp_data, S1_type);
+  TArrayn::DTArray *T1_ptr = QSPPtr(qsp_data, T1_type);
+
+  if (T1_type == QSP_custom) {
+    T1_ptr = qsp_data.custom_T1;
+  }
+
+  if (S1_type == QSP_custom) {
+    S1_ptr = qsp_data.custom_S1;
+  }
+
+  if ((!S1_ptr && S1_type != QSP_ke) || (!T1_ptr && T1_type != QSP_ke)) {
+    std::cout << "Not enough data was provided for the requested tracer. "
+                 "Aborting...\n";
+    return;
+  }
+
+  int i_low, j_low, k_low, i_high, j_high, k_high;
+
+  {
+    TArrayn::DTArray *temp_ptr;
+    if (S1_ptr) { // If S1 is not ke
+      temp_ptr = S1_ptr;
+    } else { // If S1 is ke we know u must exist
+      temp_ptr = qsp_data.u;
     }
+    i_low = temp_ptr->lbound(blitz::firstDim);
+    j_low = temp_ptr->lbound(blitz::secondDim);
+    k_low = temp_ptr->lbound(blitz::thirdDim);
+    i_high = temp_ptr->ubound(blitz::firstDim);
+    j_high = temp_ptr->ubound(blitz::secondDim);
+    k_high = temp_ptr->ubound(blitz::thirdDim);
+  }
+
+  double double_max = std::numeric_limits<double>::max();
+  if (qsp_options.T1_max == double_max || qsp_options.S1_max == double_max ||
+      qsp_options.T1_min == -double_max || qsp_options.S1_min == -double_max) {
+    QSPMaxMin(T1_type, S1_type, T1_ptr, S1_ptr, qsp_options, qsp_data, i_low,
+              j_low, k_low, i_high, j_high, k_high);
   }
 
-  double hS = (S1_max - S1_min) / (double)NS;
-  double hT = (T1_max - T1_min) / (double)NT;
+  double hS =
+      (qsp_options.S1_max - qsp_options.S1_min) / (double)qsp_options.NS;
+  double hT =
+      (qsp_options.T1_max - qsp_options.T1_min) / (double)qsp_options.NT;
   double hS_inv = 1 / hS;
   double hT_inv = 1 / hT;
 
-  QSPVector local_hist(NS, NT);
-  QSPVector global_z_max(Nx, Ny);
-  QSPVector global_z_min(Nx, Ny);
+  QSPVector local_hist(qsp_options.NS, qsp_options.NT);
+  QSPVector global_z_max(qsp_data.Nx, qsp_data.Ny);
+  QSPVector global_z_min(qsp_data.Nx, qsp_data.Ny);
   // Find the range of Lz values per 2D-slice
-  if (mapped) {
-    QSPVector local_z_max(Nx, Ny);
-    QSPVector local_z_min(Nx, Ny);
+  if (qsp_data.mapped) {
+    QSPVector local_z_max(qsp_data.Nx, qsp_data.Ny);
+    QSPVector local_z_min(qsp_data.Nx, qsp_data.Ny);
     //  We are slicing as if we are doing zgrid[i, j, :]
     for (int ii = i_low; ii <= i_high; ii++) {
       for (int jj = j_low; jj <= j_high; jj++) {
@@ -236,7 +343,7 @@ void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
         double tmp_z_max = -tmp_z_min;
         double tmp;
         for (int kk = k_low; kk <= k_high; kk++) {
-          tmp = (*zgrid)(ii, jj, kk);
+          tmp = (*qsp_data.zgrid)(ii, jj, kk);
           if (tmp > tmp_z_max) {
             tmp_z_max = tmp;
           } else if (tmp < tmp_z_min) {
@@ -247,10 +354,12 @@ void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
         local_z_min(ii, jj) = tmp_z_min;
       }
     }
-    MPI_Allreduce(local_z_min.raw(), global_z_min.raw(), Nx * Ny, MPI_DOUBLE,
-                  MPI_MIN, MPI_COMM_WORLD);
-    MPI_Allreduce(local_z_max.raw(), global_z_max.raw(), Nx * Ny, MPI_DOUBLE,
-                  MPI_MAX, MPI_COMM_WORLD);
+    MPI_Allreduce(local_z_min.raw(), global_z_min.raw(),
+                  qsp_data.Nx * qsp_data.Ny, MPI_DOUBLE, MPI_MIN,
+                  MPI_COMM_WORLD);
+    MPI_Allreduce(local_z_max.raw(), global_z_max.raw(),
+                  qsp_data.Nx * qsp_data.Ny, MPI_DOUBLE, MPI_MAX,
+                  MPI_COMM_WORLD);
   }
 
   // Main loop for QSP
@@ -259,85 +368,78 @@ void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
     for (int j = j_low; j <= j_high; j++) {
       for (int k = k_low; k <= k_high; k++) {
 
-        if (T1_name == 'k') {
+        switch (T1_type) {
+        case QSP_ke:
           Tval = 0;
-          if (Nx > 1) {
-            tmp = u(i, j, k);
-            Tval += tmp * tmp;
-          }
-          if (Ny > 1) {
-            tmp = v(i, j, k);
-            Tval += tmp * tmp;
-          }
-          if (Nz > 1) {
-            tmp = w(i, j, k);
+          tmp = (*qsp_data.u)(i, j, k);
+          Tval += tmp * tmp;
+          tmp = (*qsp_data.w)(i, j, k);
+          Tval += tmp * tmp;
+          if (qsp_data.Ny > 1) {
+            tmp = (*qsp_data.v)(i, j, k);
             Tval += tmp * tmp;
           }
           Tval = 0.5 * Tval;
-        } else if (T1_name == 't') {
+          break;
+        case QSP_rho:
           tmp = (*T1_ptr)(i, j, k);
           Tval = eqn_of_state_t(tmp);
-        } else {
+          break;
+        default:
           Tval = (*T1_ptr)(i, j, k);
-        }
-        int idxT = floor((Tval - T1_min) * hT_inv);
-        if (idxT < 0) {
-          idxT = 0;
-        } else if (idxT >= NT) {
-          idxT = 0;
+          break;
         }
 
-        if (S1_name == 'k') {
+        switch (S1_type) {
+        case QSP_ke:
           Sval = 0;
-          if (Nx > 1) {
-            tmp = u(i, j, k);
-            Sval += tmp * tmp;
-          }
-          if (Ny > 1) {
-            tmp = v(i, j, k);
-            Sval += tmp * tmp;
-          }
-          if (Nz > 1) {
-            tmp = w(i, j, k);
+          tmp = (*qsp_data.u)(i, j, k);
+          Sval += tmp * tmp;
+          tmp = (*qsp_data.w)(i, j, k);
+          Sval += tmp * tmp;
+          if (qsp_data.Ny > 1) {
+            tmp = (*qsp_data.v)(i, j, k);
             Sval += tmp * tmp;
           }
           Sval = 0.5 * Sval;
-        } else if (S1_name == 't') {
+          break;
+        case QSP_rho:
           tmp = (*S1_ptr)(i, j, k);
           Sval = eqn_of_state_t(tmp);
-        } else {
+          break;
+        default:
           Sval = (*S1_ptr)(i, j, k);
+          break;
         }
-        int idxS = floor((Sval - S1_min) * hS_inv);
-        if (idxS < 0) {
-          idxS = 0;
-        } else if (idxS >= NS) {
-          idxS = 0;
-        }
+
+        int idxS = floor((Sval - qsp_options.S1_min) * hS_inv);
+        int idxT = floor((Tval - qsp_options.T1_min) * hT_inv);
+        idxS = std::max(std::min(idxS, qsp_options.NS - 1), 0);
+        idxT = std::max(std::min(idxT, qsp_options.NT - 1), 0);
 
         double volume_weight;
-        if (mapped) {
+        if (qsp_data.mapped) {
           // Calculate the Lz range
           double Lzmax_now = global_z_max(i, j);
           double Lzmin_now = global_z_min(i, j);
 
           // Calculate the arc length
           double arc, z_high, z_low, cos_high, cos_low;
-          if (k > 0 && k < Nz - 1) {
+          if (k > 0 && k < qsp_data.Nz - 1) {
             z_high = (double)(k + 1);
             z_low = (double)(k - 1);
           } else if (k == 0) {
             z_high = (double)(k + 1);
             z_low = (double)(k);
-          } else if (k == Nz - 1) {
+          } else if (k == qsp_data.Nz - 1) {
             z_high = (double)(k);
             z_low = (double)(k - 1);
           } else { // Failure
             std::cout << "k was out of bounds somehow. Failing...\n";
             return;
           }
-          cos_high = std::cos(M_PI * z_high / (double)Nz);
-          cos_low = std::cos(M_PI * z_low / (double)Nz);
+          cos_high = std::cos(M_PI * z_high / (double)qsp_data.Nz);
+          cos_low = std::cos(M_PI * z_low / (double)qsp_data.Nz);
           arc = 0.5 * (cos_low - cos_high);
 
           // Calculate the volume weight
@@ -346,40 +448,11 @@ void QSPCount(const TArrayn::DTArray &t, const TArrayn::DTArray &u,
           volume_weight = 1.0;
         }
 
-        // local_hist[index(idxS, idxT, NS, NT)] += volume_weight;
         local_hist(idxS, idxT) += volume_weight;
       }
     }
   }
 
   MPI_Barrier(MPI_COMM_WORLD); // Wait for everyone to finish
-  if (local_rank == 0) {
-    QSPVector glob_hist(NS, NT);
-    MPI_Reduce(local_hist.raw(), glob_hist.raw(), // send and receive buffers
-               NS * NT, MPI_DOUBLE,               // count and datatype
-               MPI_SUM, 0,      // Reduction operator and root process #
-               MPI_COMM_WORLD); // Communicator
-    filename = filename + "." + boost::lexical_cast<string>(plotnum) + ".csv";
-    std::fstream outfile;
-    outfile.open(filename.c_str(), std::ios_base::out);
-    if (outfile.is_open()) {
-      outfile << T1_max << ',' << T1_min << ',' << S1_max << ',' << S1_min;
-      for (int i = 4; i < NT; i++) {
-        outfile << ',' << 0;
-      }
-      outfile << std::endl;
-      for (int ii = 0; ii < NS; ii++) {
-        outfile << glob_hist[index(ii, 0, NS, NT)];
-        for (int jj = 1; jj < NT; jj++) {
-          outfile << ',' << glob_hist[index(ii, jj, NS, NT)];
-        }
-        outfile << std::endl;
-      }
-    }
-  } else {
-    MPI_Reduce(local_hist.raw(), NULL, // send and receive buffers
-               NS * NT, MPI_DOUBLE,    // count and datatype
-               MPI_SUM, 0,             // Reduction operator and root process #
-               MPI_COMM_WORLD);        // Communicator
-  }
+  QSP_write(local_rank, local_hist, qsp_options, qsp_data);
 }
diff --git a/src/cases/derivatives/derivatives.cpp b/src/cases/derivatives/derivatives.cpp
index e4a962af4207f110fa56fa18f339170dd57869c3..9fd3b0db1004198ee3f364d79674f5f6e57c2254 100644
--- a/src/cases/derivatives/derivatives.cpp
+++ b/src/cases/derivatives/derivatives.cpp
@@ -36,7 +36,7 @@ const int z_ind = 2;
 
 // QSP variables
 double T1_max, S1_max, T1_min, S1_min;
-char T1_name, S1_name;
+string T1_name, S1_name;
 int NT, NS;
 string QSP_filename;
 bool use_salinity;
@@ -152,7 +152,7 @@ class userControl : public BaseCase {
                   zgrid = NULL;
                 }
 
-                if ( do_enst_stretch ) temp3 = alloc_array(Nx,Ny,Nz);
+                if ( do_enst_stretch or do_hist ) temp3 = alloc_array(Nx,Ny,Nz);
             }
 
             if ( do_barvor ) {
@@ -430,13 +430,6 @@ class userControl : public BaseCase {
                 // nor to make deep copies of them
                 if ( do_hist ){
 
-                    if (use_salinity) {
-                      init_tracer_restart("s", *temp1);
-                    } else if (T1_name == 't' || S1_name == 't' || T1_name == 'T' ||
-                        S1_name == 'T') {
-                        init_tracer_restart("t", *temp1);
-                    }
-
                     // If user asked to grab the grids, we populate the grids
                     // with the correct data from disk
                     if (mapped) {
@@ -449,9 +442,49 @@ class userControl : public BaseCase {
                       assert(zgrid == NULL);
                     }
 
-                    QSPCount(*temp1, u, v, w, T1_name, S1_name, NS, NT, T1_max,
-                             S1_max, T1_min, S1_min, Nx, Ny, Nz, QSP_filename,
-                             plotnum, mapped, xgrid, ygrid, zgrid);
+                    QSPOptions qsp_opts;
+                    qsp_opts.S1_name = S1_name;
+                    qsp_opts.T1_name = T1_name;
+                    qsp_opts.filename = QSP_filename;
+                    qsp_opts.NS = NS;
+                    qsp_opts.NT = NT;
+                    qsp_opts.S1_max = S1_max;
+                    qsp_opts.S1_min = S1_min;
+                    qsp_opts.T1_max = T1_max;
+                    qsp_opts.T1_min = T1_min;
+
+                    QSPData qsp_data;
+                    qsp_data.mapped = mapped;
+                    qsp_data.Nx = Nx;
+                    qsp_data.Ny = Ny;
+                    qsp_data.Nz = Nz;
+                    qsp_data.plotnum = plotnum;
+                    qsp_data.u = &u;
+                    qsp_data.v = &v;
+                    qsp_data.w = &w;
+                    qsp_data.xgrid = xgrid;
+                    qsp_data.ygrid = ygrid;
+                    qsp_data.zgrid = zgrid;
+
+                    qsp_data.temp = NULL;
+                    qsp_data.rho = NULL;
+                    qsp_data.salinity = NULL;
+
+                    if (use_salinity) {
+                      init_tracer_restart("s", *temp1);
+                      qsp_data.salinity = temp1;
+                    }
+                    if (T1_name.compare("temp") == 0 || S1_name.compare("temp") == 0) {
+                      init_tracer_restart("t", *temp2);
+                      qsp_data.temp = temp2;
+                    }
+                    if (T1_name.compare("rho") == 0 || S1_name.compare("rho") == 0) {
+                      init_tracer_restart("rho", *temp3);
+                      qsp_data.rho = temp3;
+                    }
+
+                    QSPCount(qsp_opts, qsp_data);
+
                     if (master()) {
                       fprintf(stdout, "Completed the write for QSP.%d\n", plotnum);
                     }
@@ -530,13 +563,13 @@ int main(int argc, char ** argv) {
     add_option("do_Q_and_R",&do_Q_and_R,false,"Calculate Q and R?");
     add_option("do_lambda2",&do_lambda2,false,"Calculate Lambda2?");
     add_option("do_hist",&do_hist,false,"Create QSP Data?");
-    add_option("T1",&T1_name,'t', "Name of tracer 1 for QSP.  Valid values are t (for rho),u,v,w,T (for temp) or k for K.E.");
-    add_option("S1",&S1_name,'w', "Name of tracer 2 for QSP.  Valid values are t (for rho),u,v,w,T (for temp) or k for K.E.");
+    add_option("T1",&T1_name,"u", "Name of tracer 1 for QSP. Valid values are rho,u,v,w,temp or ke");
+    add_option("S1",&S1_name,"w", "Name of tracer 2 for QSP. Valid values are rho,u,v,w,temp or ke");
     add_option("T1_max",&T1_max,std::numeric_limits<double>::max(), "Maximum explicit bin for T1 in QSP.");
     add_option("T1_min",&T1_min,std::numeric_limits<double>::min(), "Minimum explicit bin for T1 in QSP.");
     add_option("S1_max",&S1_max,std::numeric_limits<double>::max(), "Maximum explicit bin for S1 in QSP.");
     add_option("S1_min",&S1_min,std::numeric_limits<double>::min(), "Minimum explicit bin for S1 in QSP.");
-    add_option("QSP_salinity",&use_salinity, false, "Should salinity be read in from filename s?.");
+    add_option("salinity",&use_salinity, false, "Should salinity be read in from filename s?.");
     add_option("QSP_filename",&QSP_filename,"QSP_default", "Filename to save data to. Don't include file extension.");
     add_option("NS",&NS,10,"Number of bins for tracer S");
     add_option("NT",&NT,10,"Number of bins for tracer T");
diff --git a/src/cases/derivatives/spins.conf b/src/cases/derivatives/spins.conf
index 9e9304c8f827a3a96740c154769aa2182d8c29a5..3d05b5ba8bed408714ff6da43f738ac245dee918 100644
--- a/src/cases/derivatives/spins.conf
+++ b/src/cases/derivatives/spins.conf
@@ -42,11 +42,11 @@ do_Q = false
 do_R = false
 do_Q_and_R = false
 do_lambda2 = false
-do_hist = false
 
 # QSP Parameters
-T1 = t
-S1 = k
+do_hist = false
+T1 = temp
+S1 = ke
 QSP_filename = QSP
 NT = 10
 NS = 10
diff --git a/src/cases/qsp/qsp.cpp b/src/cases/qsp/qsp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3dca0407e408fa5b77c63b6296929c857f70efbc
--- /dev/null
+++ b/src/cases/qsp/qsp.cpp
@@ -0,0 +1,305 @@
+/* Derivative case file for computing derivatives of existing fields */
+// only able to return first and second derivatives
+
+/* ------------------ Top matter --------------------- */
+
+// Required headers
+#include "../../BaseCase.hpp" // contains default class
+#include "../../Options.hpp"  // config-file parser
+#include "../../Science.hpp"  // Science content
+
+#include <cassert>
+#include <limits>
+#include <string>
+
+// Tensor variables for indexing
+blitz::firstIndex ii;
+blitz::secondIndex jj;
+blitz::thirdIndex kk;
+
+/* ------------------ Define parameters --------------------- */
+
+// Grid scales
+double Lx, Ly, Lz; // Grid lengths (m)
+int Nx, Ny, Nz;    // Number of points in x, y, z
+// Mapped grid?
+bool mapped;
+// Grid types
+DIMTYPE intype_x, intype_y, intype_z;
+string grid_type[3];
+// Expansion types and indices
+S_EXP expan[3];
+const int x_ind = 0;
+const int y_ind = 1;
+const int z_ind = 2;
+
+// QSP variables
+double T1_max, S1_max, T1_min, S1_min;
+std::string T1_name, S1_name;
+int NT, NS;
+std::string QSP_filename;
+bool use_salinity, read_rho;
+std::string salinity_filename, temp_filename, rho_filename;
+std::string custom_T1_filename, custom_S1_filename;
+const double double_max = std::numeric_limits<double>::max();
+
+// current output number
+int plotnum;
+
+// Derivative options
+int start_sequence; // output number to start taking derivatives at
+int final_sequence; // output number to stop  taking derivatives at
+int step_sequence;  // step between outputs to take derivatives
+                    // streching/tilting?
+bool v_exist;       // Does the v field exist?
+
+/* ------------------ Adjust the class --------------------- */
+
+class userControl : public BaseCase {
+public:
+  /* Initialize things */
+  DTArray *xgrid, *ygrid, *zgrid; // Arrays for storing grid data
+
+  /* Size of domain */
+  double length_x() const { return Lx; }
+  double length_y() const { return Ly; }
+  double length_z() const { return Lz; }
+
+  /* Resolution in X, Y, and Z */
+  int size_x() const { return Nx; }
+  int size_y() const { return Ny; }
+  int size_z() const { return Nz; }
+
+  /* Set expansions (FREE_SLIP, NO_SLIP (in vertical) or PERIODIC) */
+  DIMTYPE type_x() const { return intype_x; }
+  DIMTYPE type_y() const { return intype_y; }
+  DIMTYPE type_z() const { return intype_z; }
+
+  /* Set other things */
+  int get_restart_sequence() const { return plotnum; }
+
+  /* Read grid (if mapped) */
+  bool is_mapped() const { return mapped; }
+  void do_mapping(DTArray &xg, DTArray &yg, DTArray &zg) {
+    init_grid_restart("x", "xgrid", xg);
+    if (Ny > 1)
+      init_grid_restart("y", "ygrid", yg);
+    init_grid_restart("z", "zgrid", zg);
+  }
+
+  /* Read fields and do derivatives */
+  void init_vels(DTArray &u, DTArray &v, DTArray &w) {
+    // If user asks to grab the grids, we allocate arrays to store
+    // them in memory
+    if (mapped) {
+      xgrid = alloc_array(Nx, Ny, Nz);
+      if (Ny > 1) {
+        ygrid = alloc_array(Nx, Ny, Nz);
+      }
+      zgrid = alloc_array(Nx, Ny, Nz);
+      do_mapping(*xgrid, *ygrid, *zgrid);
+    } else {
+      // If user doesn't want to grab grids, we make sure not to
+      // allocate arrays for them and to set the pointers to NULL.
+      xgrid = NULL;
+      ygrid = NULL;
+      zgrid = NULL;
+    }
+    QSPOptions qsp_opts;
+    qsp_opts.S1_name = S1_name;
+    qsp_opts.T1_name = T1_name;
+    qsp_opts.filename = QSP_filename;
+    qsp_opts.NS = NS;
+    qsp_opts.NT = NT;
+    qsp_opts.S1_max = S1_max;
+    qsp_opts.S1_min = S1_min;
+    qsp_opts.T1_max = T1_max;
+    qsp_opts.T1_min = T1_min;
+
+    QSPData qsp_data;
+    qsp_data.mapped = mapped;
+    qsp_data.Nx = Nx;
+    qsp_data.Ny = Ny;
+    qsp_data.Nz = Nz;
+    qsp_data.xgrid = xgrid;
+    qsp_data.ygrid = ygrid;
+    qsp_data.zgrid = zgrid;
+
+    // Compute derivatives at each requested output
+    for (plotnum = start_sequence; plotnum <= final_sequence;
+         plotnum = plotnum + step_sequence) {
+
+      // read in fields (if not already stored in memory)
+      // Compute QSP data. The code promises to not mutate the arrays,
+      // nor to make deep copies of them
+
+      // u
+      init_tracer_restart("u", u);
+      // v
+      if (v_exist) {
+        init_tracer_restart("v", v);
+      } else {
+        if (master())
+          fprintf(stdout, "No v field, setting v=0\n");
+        v = 0;
+      }
+      // w
+      init_tracer_restart("w", w);
+
+      qsp_data.plotnum = plotnum;
+      qsp_data.u = &u;
+      qsp_data.v = &v;
+      qsp_data.w = &w;
+      qsp_data.temp = NULL;
+      qsp_data.rho = NULL;
+      qsp_data.salinity = NULL;
+      qsp_data.custom_T1 = NULL;
+      qsp_data.custom_S1 = NULL;
+
+      DTArray *arr_salinity, *arr_temperature, *arr_rho, *custom_T1, *custom_S1;
+
+      if (use_salinity) {
+        arr_salinity = alloc_array(Nx, Ny, Nz);
+        init_tracer_restart(salinity_filename, *arr_salinity);
+        qsp_data.salinity = arr_salinity;
+      }
+
+      if (read_rho) {
+        arr_rho = alloc_array(Nx, Ny, Nz);
+        init_tracer_restart(rho_filename, *arr_rho);
+        qsp_data.rho = arr_rho;
+      }
+
+      if (T1_name.compare("temp") == 0 || S1_name.compare("temp") == 0) {
+        arr_temperature = alloc_array(Nx, Ny, Nz);
+        init_tracer_restart(temp_filename, *arr_temperature);
+        qsp_data.temp = arr_temperature;
+      }
+
+      if (T1_name.compare("custom") == 0) {
+        custom_T1 = alloc_array(Nx, Ny, Nz);
+        init_tracer_restart(custom_T1_filename, *custom_T1);
+        qsp_data.custom_T1 = custom_T1;
+      }
+
+      if (S1_name.compare("custom") == 0) {
+        custom_S1 = alloc_array(Nx, Ny, Nz);
+        init_tracer_restart(custom_S1_filename, *custom_S1);
+        qsp_data.custom_S1 = custom_S1;
+      }
+
+      QSPCount(qsp_opts, qsp_data);
+
+      if (master()) {
+        fprintf(stdout, "Completed QSP calculation for plotnum %d\n", plotnum);
+      }
+    }
+  }
+
+  // Constructor: Initialize local variables
+  userControl() {
+    compute_quadweights(size_x(), size_y(), size_z(), length_x(), length_y(),
+                        length_z(), type_x(), type_y(), type_z());
+  }
+};
+
+/* The ''main'' routine */
+int main(int argc, char **argv) {
+  /* Initialize MPI.  This is required even for single-processor runs,
+     since the inner routines assume some degree of parallelization,
+     even if it is trivial. */
+  MPI_Init(&argc, &argv);
+
+  /* ------------------ Define parameters from spins.conf ---------------------
+   */
+  options_init();
+
+  option_category("Grid Options");
+  add_option("Lx", &Lx, "Length of tank");
+  add_option("Ly", &Ly, 1.0, "Width of tank");
+  add_option("Lz", &Lz, "Height of tank");
+  add_option("Nx", &Nx, "Number of points in X");
+  add_option("Ny", &Ny, 1, "Number of points in Y");
+  add_option("Nz", &Nz, "Number of points in Z");
+
+  option_category("Grid mapping options");
+  add_option("mapped_grid", &mapped, false, "Is the grid mapped?");
+
+  string xgrid_type, ygrid_type, zgrid_type;
+  add_option("type_x", &xgrid_type,
+             "Grid type in X.  Valid values are:\n"
+             "   FOURIER: Periodic\n"
+             "   FREE_SLIP: Cosine expansion\n"
+             "   NO_SLIP: Chebyhsev expansion");
+  add_option("type_y", &ygrid_type, "FOURIER", "Grid type in Y");
+  add_option("type_z", &zgrid_type, "Grid type in Z");
+
+  option_category("QSP options");
+  add_option("start_sequence", &start_sequence,
+             "Sequence number to start taking derivatives at");
+  add_option("final_sequence", &final_sequence,
+             "Sequence number to stop  taking derivatives at");
+  add_option("step_sequence", &step_sequence, 1,
+             "Step between outputs to take derivatives");
+  add_option("salinity_filename", &salinity_filename,
+             "Base Filename of Salinity data (optional).");
+  add_option("temp_filename", &temp_filename,
+             "Base Filename of temperature data (optional).");
+  add_option("T1_filename", &custom_T1_filename,
+             "Base Filename of custom tracer for T1.");
+  add_option("S1_filename", &custom_S1_filename,
+             "Base Filename of custom tracer for S1.");
+  add_option("T1", &T1_name, "u",
+             "Name of tracer 1 for QSP. Valid values are rho,u,v,w,temp, "
+             "salinity or ke");
+  add_option("S1", &S1_name, "w",
+             "Name of tracer 2 for QSP. Valid values are rho,u,v,w,temp, "
+             "salinity or ke");
+  add_option("T1_max", &T1_max, double_max,
+             "Maximum explicit bin for T1 in QSP.");
+  add_option("T1_min", &T1_min, -double_max,
+             "Minimum explicit bin for T1 in QSP.");
+  add_option("S1_max", &S1_max, double_max,
+             "Maximum explicit bin for S1 in QSP.");
+  add_option("S1_min", &S1_min, -double_max,
+             "Minimum explicit bin for S1 in QSP.");
+  add_option("salinity", &use_salinity, false,
+             "Should salinity be read in from a file?.");
+  add_option("read_rho", &read_rho, false,
+             "Should rho be read in from a file?.");
+  add_option("QSP_filename", &QSP_filename, "QSP_default",
+             "Filename to save data to. Don't include file extension.");
+  add_option("NS", &NS, 10, "Number of bins for tracer S");
+  add_option("NT", &NT, 10, "Number of bins for tracer T");
+  add_option("v_exist", &v_exist, "Does the v field exist?");
+  // Parse the options from the command line and config file
+  options_parse(argc, argv);
+
+  /* ------------------ Adjust and check parameters --------------------- */
+  /* Now, make sense of the options received.  Many of these values
+     can be directly used, but the ones of string-type need further procesing.
+   */
+
+  // parse expansion types
+  parse_boundary_conditions(xgrid_type, ygrid_type, zgrid_type, intype_x,
+                            intype_y, intype_z);
+  // vector of string types
+  grid_type[x_ind] = xgrid_type;
+  grid_type[y_ind] = ygrid_type;
+  grid_type[z_ind] = zgrid_type;
+
+  // adjust Ly for 2D
+  if (Ny == 1 and Ly != 1.0) {
+    Ly = 1.0;
+    if (master())
+      fprintf(stdout, "Simulation is 2 dimensional, "
+                      "Ly has been changed to 1.0 for normalization.\n");
+  }
+
+  /* ------------------ Do stuff --------------------- */
+  userControl mycode; // Create an instantiated object of the above class
+  FluidEvolve<userControl> kevin_kh(&mycode);
+  kevin_kh.initialize();
+  MPI_Finalize();
+  return 0;
+}
diff --git a/src/cases/qsp/spins.conf b/src/cases/qsp/spins.conf
new file mode 100644
index 0000000000000000000000000000000000000000..dd65a3bd8b7742946d05410f553b805950966b6c
--- /dev/null
+++ b/src/cases/qsp/spins.conf
@@ -0,0 +1,69 @@
+## QSP configuration file
+
+# Spatial Parameters
+Lx = 6.4
+Ly = 1.0
+Lz = 0.3
+Nx = 4096
+Ny = 1
+Nz = 384
+
+# Expansion types
+type_x = FREE_SLIP
+type_y = FOURIER
+type_z = NO_SLIP
+mapped_grid = false
+v_exist = false
+
+# If salinity is needed (directly or indirectly) set this to true
+salinity = false
+salinity_filename = s
+
+# If you already pre-computed rho (the equation of state) and saved it to a
+# file, then set this to true. If this is false, and QSP can find the
+# temperature data but not rho, it will use the equation of state function from
+# Science.hpp, and if it also finds salinity it will add that to the equation
+# of state calculation too.
+read_rho = false
+rho_filename = rho
+
+# Sequence Options
+start_sequence = 0
+final_sequence = 0
+step_sequence =  1
+
+# Choose what tracers you want to use. T1 and S1 have no distinguishable
+# meaning, just stay consistent with NT and NS. Do note that if you pick ke
+# then QSP will compute it in-place. If you already pre-computed ke and have a
+# saved file for it, use the custom property explained below instead.
+# Tracer options: rho, temp, salinity, u, v, w, ke
+#
+# Note: If you need to use a custom tracer (i.e. not one of the above), set the
+# tracer name to "custom." If you do this however, it is your responsability to
+# ensure that:
+# a) you provide a valid custom filename too and
+# b) all prepocessing is already completed and the data in the file is exactly
+#    what you want to use
+T1 = temp
+S1 = ke
+
+# Tracer base filenames. These are only relevent parameters IFF you set the
+# according tracer name to "custom" above. Otherwise, these are ignored.
+T1_filename = vorticity
+S1_filename = vorticity
+
+# Set the min/max values of the first/last bin in each dimension respectively.
+# These are optional parameters. If you remove these lines from the config QSP
+# will find the global min/max values for each timestep and use that instead.
+# So if you need fixed values for these make sure to include them!
+T1_min = 1
+T1_max = 1
+S1_min = 1
+S1_max = 1
+
+# Number of bins for T1 and S1 chosen above.
+NT = 10
+NS = 10
+
+# Base filename to save results
+QSP_filename = QSP