about summary refs log tree commit diff
path: root/wqflask
diff options
context:
space:
mode:
Diffstat (limited to 'wqflask')
-rw-r--r--wqflask/.DS_Storebin6148 -> 0 bytes
-rw-r--r--wqflask/base/data_set.py21
-rw-r--r--wqflask/base/trait.py8
-rw-r--r--wqflask/runserver.py3
-rw-r--r--wqflask/utility/Plot.py4
-rw-r--r--wqflask/utility/startup_config.py6
-rw-r--r--wqflask/wqflask/correlation/correlation_gn3_api.py38
-rw-r--r--wqflask/wqflask/correlation/show_corr_results.py5
-rw-r--r--wqflask/wqflask/marker_regression/display_mapping_results.py2
-rw-r--r--wqflask/wqflask/marker_regression/gemma_mapping.py35
-rw-r--r--wqflask/wqflask/marker_regression/rqtl_mapping.py5
-rw-r--r--wqflask/wqflask/marker_regression/run_mapping.py48
-rw-r--r--wqflask/wqflask/show_trait/SampleList.py51
-rw-r--r--wqflask/wqflask/show_trait/show_trait.py53
-rw-r--r--wqflask/wqflask/static/new/css/bootstrap-custom.css4
-rw-r--r--wqflask/wqflask/static/new/css/show_trait.css30
-rw-r--r--wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js62
-rw-r--r--wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js6
-rw-r--r--wqflask/wqflask/static/new/javascript/show_trait.js95
-rw-r--r--wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js32
-rw-r--r--wqflask/wqflask/templates/collections/add.html29
-rw-r--r--wqflask/wqflask/templates/display_files_admin.html32
-rw-r--r--wqflask/wqflask/templates/display_files_user.html31
-rw-r--r--wqflask/wqflask/templates/edit_phenotype.html28
-rw-r--r--wqflask/wqflask/templates/loading.html34
-rw-r--r--wqflask/wqflask/templates/mapping_results.html7
-rw-r--r--wqflask/wqflask/templates/pair_scan_results.html170
-rw-r--r--wqflask/wqflask/templates/show_trait_details.html2
-rwxr-xr-xwqflask/wqflask/templates/show_trait_mapping_tools.html131
-rw-r--r--wqflask/wqflask/templates/show_trait_transform_and_filter.html29
-rw-r--r--wqflask/wqflask/templates/test_correlation_page.html2
-rw-r--r--wqflask/wqflask/views.py260
32 files changed, 961 insertions, 302 deletions
diff --git a/wqflask/.DS_Store b/wqflask/.DS_Store
deleted file mode 100644
index d992942f..00000000
--- a/wqflask/.DS_Store
+++ /dev/null
Binary files differdiff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py
index 4cb82665..8906ab69 100644
--- a/wqflask/base/data_set.py
+++ b/wqflask/base/data_set.py
@@ -277,7 +277,6 @@ class Markers:
             filtered_markers = []
             for marker in self.markers:
                 if marker['name'] in p_values:
-                    # logger.debug("marker {} IS in p_values".format(i))
                     marker['p_value'] = p_values[marker['name']]
                     if math.isnan(marker['p_value']) or (marker['p_value'] <= 0):
                         marker['lod_score'] = 0
@@ -298,7 +297,6 @@ class HumanMarkers(Markers):
         self.markers = []
         for line in marker_data_fh:
             splat = line.strip().split()
-            # logger.debug("splat:", splat)
             if len(specified_markers) > 0:
                 if splat[1] in specified_markers:
                     marker = {}
@@ -398,6 +396,15 @@ class DatasetGroup:
         if maternal and paternal:
             self.parlist = [maternal, paternal]
 
+    def get_study_samplelists(self):
+        study_sample_file = locate_ignore_error(self.name + ".json", 'study_sample_lists')
+        try:
+            f = open(study_sample_file)
+        except:
+            return []
+        study_samples = json.load(f)
+        return study_samples
+
     def get_genofiles(self):
         jsonfile = "%s/%s.json" % (webqtlConfig.GENODIR, self.name)
         try:
@@ -737,7 +744,6 @@ class DataSet:
             and Strain.SpeciesId=Species.Id
             and Species.name = '{}'
             """.format(create_in_clause(self.samplelist), *mescape(self.group.species))
-        logger.sql(query)
         results = dict(g.db.execute(query).fetchall())
         sample_ids = [results[item] for item in self.samplelist]
 
@@ -908,7 +914,6 @@ class PhenotypeDataSet(DataSet):
                         Geno.Name = '%s' and
                         Geno.SpeciesId = Species.Id
                 """ % (species, this_trait.locus)
-                logger.sql(query)
                 result = g.db.execute(query).fetchone()
 
                 if result:
@@ -938,7 +943,6 @@ class PhenotypeDataSet(DataSet):
                     Order BY
                             Strain.Name
                     """
-        logger.sql(query)
         results = g.db.execute(query, (trait, self.id)).fetchall()
         return results
 
@@ -1005,7 +1009,6 @@ class GenotypeDataSet(DataSet):
                     Order BY
                             Strain.Name
                     """
-        logger.sql(query)
         results = g.db.execute(query,
                                (webqtlDatabaseFunction.retrieve_species_id(self.group.name),
                                 trait, self.name)).fetchall()
@@ -1126,8 +1129,6 @@ class MrnaAssayDataSet(DataSet):
                 ProbeSet.Name = '%s'
             """ % (escape(str(this_trait.dataset.id)),
                    escape(this_trait.name)))
-
-            logger.sql(query)
             result = g.db.execute(query).fetchone()
 
             mean = result[0] if result else 0
@@ -1147,7 +1148,6 @@ class MrnaAssayDataSet(DataSet):
                         Geno.Name = '{}' and
                         Geno.SpeciesId = Species.Id
                 """.format(species, this_trait.locus)
-                logger.sql(query)
                 result = g.db.execute(query).fetchone()
 
                 if result:
@@ -1179,7 +1179,6 @@ class MrnaAssayDataSet(DataSet):
                     Order BY
                             Strain.Name
                     """ % (escape(trait), escape(self.name))
-        logger.sql(query)
         results = g.db.execute(query).fetchall()
         return results
 
@@ -1190,7 +1189,6 @@ class MrnaAssayDataSet(DataSet):
                     where ProbeSetXRef.ProbeSetFreezeId = %s and
                     ProbeSetXRef.ProbeSetId=ProbeSet.Id;
                 """ % (column_name, escape(str(self.id)))
-        logger.sql(query)
         results = g.db.execute(query).fetchall()
 
         return dict(results)
@@ -1224,7 +1222,6 @@ def geno_mrna_confidentiality(ob):
 
     query = '''SELECT Id, Name, FullName, confidentiality,
                         AuthorisedUsers FROM %s WHERE Name = "%s"''' % (dataset_table, ob.name)
-    logger.sql(query)
     result = g.db.execute(query)
 
     (dataset_id,
diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py
index 10851e00..96a09302 100644
--- a/wqflask/base/trait.py
+++ b/wqflask/base/trait.py
@@ -27,11 +27,13 @@ def create_trait(**kw):
 
     assert bool(kw.get('name')), "Needs trait name"
 
-    if kw.get('dataset_name'):
+    if bool(kw.get('dataset')):
+        dataset = kw.get('dataset')
+    else:
         if kw.get('dataset_name') != "Temp":
             dataset = create_dataset(kw.get('dataset_name'))
-    else:
-        dataset = kw.get('dataset')
+        else:
+            dataset = create_dataset("Temp", group_name=kw.get('group_name'))
 
     if dataset.type == 'Publish':
         permissions = check_resource_availability(
diff --git a/wqflask/runserver.py b/wqflask/runserver.py
index df957bd9..8198b921 100644
--- a/wqflask/runserver.py
+++ b/wqflask/runserver.py
@@ -23,6 +23,9 @@ app_config()
 werkzeug_logger = logging.getLogger('werkzeug')
 
 if WEBSERVER_MODE == 'DEBUG':
+    from flask_debugtoolbar import DebugToolbarExtension
+    app.debug = True
+    toolbar = DebugToolbarExtension(app)
     app.run(host='0.0.0.0',
             port=SERVER_PORT,
             debug=True,
diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py
index 9b2c6735..d4256a46 100644
--- a/wqflask/utility/Plot.py
+++ b/wqflask/utility/Plot.py
@@ -139,7 +139,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab
     max_D = max(data)
     min_D = min(data)
     # add by NL 06-20-2011: fix the error: when max_D is infinite, log function in detScale will go wrong
-    if max_D == float('inf') or max_D > webqtlConfig.MAXLRS:
+    if (max_D == float('inf') or max_D > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS:
         max_D = webqtlConfig.MAXLRS  # maximum LRS value
 
     xLow, xTop, stepX = detScale(min_D, max_D)
@@ -156,7 +156,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab
         j += step
 
     for i, item in enumerate(data):
-        if item == float('inf') or item > webqtlConfig.MAXLRS:
+        if (item == float('inf') or item > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS:
             item = webqtlConfig.MAXLRS  # maximum LRS value
         j = int((item - xLow) / step)
         Count[j] += 1
diff --git a/wqflask/utility/startup_config.py b/wqflask/utility/startup_config.py
index 56d0af6f..778fb64d 100644
--- a/wqflask/utility/startup_config.py
+++ b/wqflask/utility/startup_config.py
@@ -20,8 +20,12 @@ def app_config():
         import os
         app.config['SECRET_KEY'] = str(os.urandom(24))
     mode = WEBSERVER_MODE
-    if mode == "DEV" or mode == "DEBUG":
+    if mode in ["DEV", "DEBUG"]:
         app.config['TEMPLATES_AUTO_RELOAD'] = True
+        if mode == "DEBUG":
+            from flask_debugtoolbar import DebugToolbarExtension
+            app.debug = True
+            toolbar = DebugToolbarExtension(app)
 
     print("==========================================")
 
diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py
index aea91220..d0d4bcba 100644
--- a/wqflask/wqflask/correlation/correlation_gn3_api.py
+++ b/wqflask/wqflask/correlation/correlation_gn3_api.py
@@ -18,7 +18,10 @@ from gn3.db_utils import database_connector
 def create_target_this_trait(start_vars):
     """this function creates the required trait and target dataset for correlation"""
 
-    this_dataset = data_set.create_dataset(dataset_name=start_vars['dataset'])
+    if start_vars['dataset'] == "Temp":
+        this_dataset = data_set.create_dataset(dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group'])
+    else:
+        this_dataset = data_set.create_dataset(dataset_name=start_vars['dataset'])
     target_dataset = data_set.create_dataset(
         dataset_name=start_vars['corr_dataset'])
     this_trait = create_trait(dataset=this_dataset,
@@ -145,10 +148,7 @@ def lit_for_trait_list(corr_results, this_dataset, this_trait):
 def fetch_sample_data(start_vars, this_trait, this_dataset, target_dataset):
 
     sample_data = process_samples(
-        start_vars, this_dataset.group.samplelist)
-
-    sample_data = test_process_data(this_trait, this_dataset, start_vars)
-
+        start_vars, this_dataset.group.all_samples_ordered())
 
     if target_dataset.type == "ProbeSet":
         target_dataset.get_probeset_data(list(sample_data.keys()))
@@ -202,17 +202,22 @@ def compute_correlation(start_vars, method="pearson", compute_all=False):
         if tissue_input is not None:
             (primary_tissue_data, target_tissue_data) = tissue_input
 
-        corr_input_data = {
-            "primary_tissue": primary_tissue_data,
-            "target_tissues_dict": target_tissue_data
-        }
-        correlation_results = compute_tissue_correlation(
-            primary_tissue_dict=corr_input_data["primary_tissue"],
-            target_tissues_data=corr_input_data[
-                "target_tissues_dict"],
-            corr_method=method
-
-        )
+            corr_input_data = {
+                "primary_tissue": primary_tissue_data,
+                "target_tissues_dict": target_tissue_data
+            }
+            correlation_results = compute_tissue_correlation(
+                primary_tissue_dict=corr_input_data["primary_tissue"],
+                target_tissues_data=corr_input_data[
+                    "target_tissues_dict"],
+                corr_method=method
+
+            )
+        else:
+            return {"correlation_results": [],
+                    "this_trait": this_trait.name,
+                    "target_dataset": start_vars['corr_dataset'],
+                    "return_results": corr_return_results}
 
     elif corr_type == "lit":
         (this_trait_geneid, geneid_dict, species) = do_lit_correlation(
@@ -303,4 +308,3 @@ def get_tissue_correlation_input(this_trait, trait_symbol_dict):
             "symbol_tissue_vals_dict": corr_result_tissue_vals_dict
         }
         return (primary_tissue_data, target_tissue_data)
-    return None
diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py
index e2daa991..d73965da 100644
--- a/wqflask/wqflask/correlation/show_corr_results.py
+++ b/wqflask/wqflask/correlation/show_corr_results.py
@@ -30,7 +30,10 @@ def set_template_vars(start_vars, correlation_data):
     corr_type = start_vars['corr_type']
     corr_method = start_vars['corr_sample_method']
 
-    this_dataset_ob = create_dataset(dataset_name=start_vars['dataset'])
+    if start_vars['dataset'] == "Temp":
+        this_dataset_ob = create_dataset(dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group'])
+    else:
+        this_dataset_ob = create_dataset(dataset_name=start_vars['dataset'])
     this_trait = create_trait(dataset=this_dataset_ob,
                               name=start_vars['trait_id'])
 
diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py
index f941267e..3986c441 100644
--- a/wqflask/wqflask/marker_regression/display_mapping_results.py
+++ b/wqflask/wqflask/marker_regression/display_mapping_results.py
@@ -2113,7 +2113,7 @@ class DisplayMappingResults:
                                 thisChr.append(
                                     [_locus.name, _locus.cM - Locus0CM])
                     else:
-                        for j in (0, nLoci / 4, nLoci / 2, nLoci * 3 / 4, -1):
+                        for j in (0, round(nLoci / 4), round(nLoci / 2), round(nLoci * 3 / 4), -1):
                             while _chr[j].name == ' - ':
                                 j += 1
                             if _chr[j].cM != preLpos:
diff --git a/wqflask/wqflask/marker_regression/gemma_mapping.py b/wqflask/wqflask/marker_regression/gemma_mapping.py
index f88c5ac8..623ab87f 100644
--- a/wqflask/wqflask/marker_regression/gemma_mapping.py
+++ b/wqflask/wqflask/marker_regression/gemma_mapping.py
@@ -11,6 +11,7 @@ from utility.tools import flat_files
 from utility.tools import GEMMA_WRAPPER_COMMAND
 from utility.tools import TEMPDIR
 from utility.tools import WEBSERVER_MODE
+from gn3.computations.gemma import generate_hash_of_string
 
 import utility.logger
 logger = utility.logger.getLogger(__name__)
@@ -34,10 +35,7 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
         genofile_name = this_dataset.group.name
 
     if first_run:
-        trait_filename = (f"{str(this_dataset.group.name)}_"
-                          f"{str(this_trait.name)}_"
-                          f"{generate_random_n_string(6)}")
-        gen_pheno_txt_file(this_dataset, genofile_name, vals, trait_filename)
+        pheno_filename = gen_pheno_txt_file(this_dataset, genofile_name, vals)
 
         if not os.path.isfile(f"{webqtlConfig.GENERATED_IMAGE_DIR}"
                               f"{genofile_name}_output.assoc.txt"):
@@ -56,13 +54,13 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
 
         chr_list_string = ",".join(this_chromosomes_name)
         if covariates != "":
-            gen_covariates_file(this_dataset, covariates, samples)
+            covar_filename = gen_covariates_file(this_dataset, covariates, samples)
         if use_loco == "True":
             generate_k_command = (f"{GEMMA_WRAPPER_COMMAND} --json --loco "
                                   f"{chr_list_string} -- {GEMMAOPTS} "
                                   f"-g {flat_files('genotype/bimbam')}/"
                                   f"{genofile_name}_geno.txt -p "
-                                  f"{TEMPDIR}/gn2/{trait_filename}.txt -a "
+                                  f"{TEMPDIR}/gn2/{pheno_filename}.txt -a "
                                   f"{flat_files('genotype/bimbam')}/"
                                   f"{genofile_name}_snps.txt -gk > "
                                   f"{TEMPDIR}/gn2/{k_output_filename}.json")
@@ -73,10 +71,10 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
                              f"-- {GEMMAOPTS} "
                              f"-g {flat_files('genotype/bimbam')}/"
                              f"{genofile_name}_geno.txt "
-                             f"-p {TEMPDIR}/gn2/{trait_filename}.txt ")
+                             f"-p {TEMPDIR}/gn2/{pheno_filename}.txt ")
             if covariates != "":
                 gemma_command += (f"-c {flat_files('mapping')}/"
-                                  f"{this_dataset.group.name}_covariates.txt "
+                                  f"{covar_filename}.txt "
                                   f"-a {flat_files('genotype/bimbam')}/"
                                   f"{genofile_name}_snps.txt "
                                   f"-lmm 9 -maf {maf} > {TEMPDIR}/gn2/"
@@ -92,7 +90,7 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
                                   f"{GEMMAOPTS} "
                                   f" -g {flat_files('genotype/bimbam')}/"
                                   f"{genofile_name}_geno.txt -p "
-                                  f"{TEMPDIR}/gn2/{trait_filename}.txt -a "
+                                  f"{TEMPDIR}/gn2/{pheno_filename}.txt -a "
                                   f"{flat_files('genotype/bimbam')}/"
                                   f"{genofile_name}_snps.txt -gk > "
                                   f"{TEMPDIR}/gn2/{k_output_filename}.json")
@@ -106,12 +104,11 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
                              f"{genofile_name}_snps.txt "
                              f"-lmm 9 -g {flat_files('genotype/bimbam')}/"
                              f"{genofile_name}_geno.txt -p "
-                             f"{TEMPDIR}/gn2/{trait_filename}.txt ")
+                             f"{TEMPDIR}/gn2/{pheno_filename}.txt ")
 
             if covariates != "":
                 gemma_command += (f" -c {flat_files('mapping')}/"
-                                  f"{this_dataset.group.name}"
-                                  f"_covariates.txt > "
+                                  f"{covar_filename}.txt > "
                                   f"{TEMPDIR}/gn2/{gwa_output_filename}.json")
             else:
                 gemma_command += f" > {TEMPDIR}/gn2/{gwa_output_filename}.json"
@@ -129,16 +126,20 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco,
         return marker_obs, gwa_output_filename
 
 
-def gen_pheno_txt_file(this_dataset, genofile_name, vals, trait_filename):
+def gen_pheno_txt_file(this_dataset, genofile_name, vals):
     """Generates phenotype file for GEMMA"""
 
-    with open(f"{TEMPDIR}/gn2/{trait_filename}.txt", "w") as outfile:
+    filename = "PHENO_" + generate_hash_of_string(this_dataset.name + str(vals)).replace("/", "_")
+
+    with open(f"{TEMPDIR}/gn2/{filename}.txt", "w") as outfile:
         for value in vals:
             if value == "x":
                 outfile.write("NA\n")
             else:
                 outfile.write(value + "\n")
 
+    return filename
+
 
 def gen_covariates_file(this_dataset, covariates, samples):
     covariate_list = covariates.split(",")
@@ -168,14 +169,18 @@ def gen_covariates_file(this_dataset, covariates, samples):
                     this_covariate_data.append("-9")
         covariate_data_object.append(this_covariate_data)
 
+    filename = "COVAR_" + generate_hash_of_string(this_dataset.name + str(covariate_data_object)).replace("/", "_")
+
     with open((f"{flat_files('mapping')}/"
-               f"{this_dataset.group.name}_covariates.txt"),
+               f"{filename}.txt"),
               "w") as outfile:
         for i in range(len(covariate_data_object[0])):
             for this_covariate in covariate_data_object:
                 outfile.write(str(this_covariate[i]) + "\t")
             outfile.write("\n")
 
+    return filename
+
 
 def parse_loco_output(this_dataset, gwa_output_filename, loco="True"):
 
diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py
index 09afb8d1..6e816b47 100644
--- a/wqflask/wqflask/marker_regression/rqtl_mapping.py
+++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py
@@ -20,7 +20,7 @@ logger = utility.logger.getLogger(__name__)
 GN3_RQTL_URL = "http://localhost:8086/api/rqtl/compute"
 GN3_TMP_PATH = "/export/local/home/zas1024/genenetwork3/tmp"
 
-def run_rqtl(trait_name, vals, samples, dataset, mapping_scale, model, method, num_perm, perm_strata_list, do_control, control_marker, manhattan_plot, cofactors):
+def run_rqtl(trait_name, vals, samples, dataset, pair_scan, mapping_scale, model, method, num_perm, perm_strata_list, do_control, control_marker, manhattan_plot, cofactors):
     """Run R/qtl by making a request to the GN3 endpoint and reading in the output file(s)"""
 
     pheno_file = write_phenotype_file(trait_name, samples, vals, dataset, cofactors, perm_strata_list)
@@ -38,6 +38,9 @@ def run_rqtl(trait_name, vals, samples, dataset, mapping_scale, model, method, n
         "scale": mapping_scale
     }
 
+    if pair_scan:
+        post_data["pairscan"] = True
+
     if do_control == "true" and control_marker:
         post_data["control_marker"] = control_marker
 
diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py
index fcb98db9..7a6636c5 100644
--- a/wqflask/wqflask/marker_regression/run_mapping.py
+++ b/wqflask/wqflask/marker_regression/run_mapping.py
@@ -109,6 +109,7 @@ class RunMapping:
             self.mapping_results_path = "{}{}.csv".format(
                 webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename)
 
+        self.pair_scan = False
         self.manhattan_plot = False
         if 'manhattan_plot' in start_vars:
             if start_vars['manhattan_plot'].lower() != "false":
@@ -218,7 +219,7 @@ class RunMapping:
         elif self.mapping_method == "rqtl_plink":
             results = self.run_rqtl_plink()
         elif self.mapping_method == "rqtl_geno":
-            perm_strata = []
+            self.perm_strata = []
             if "perm_strata" in start_vars and "categorical_vars" in start_vars:
                 self.categorical_vars = start_vars["categorical_vars"].split(
                     ",")
@@ -227,13 +228,13 @@ class RunMapping:
                                                  sample_names=self.samples,
                                                  this_trait=self.this_trait)
 
-                    perm_strata = get_perm_strata(
+                    self.perm_strata = get_perm_strata(
                         self.this_trait, primary_samples, self.categorical_vars, self.samples)
             self.score_type = "LOD"
             self.control_marker = start_vars['control_marker']
             self.do_control = start_vars['do_control']
-            if 'mapmethod_rqtl_geno' in start_vars:
-                self.method = start_vars['mapmethod_rqtl_geno']
+            if 'mapmethod_rqtl' in start_vars:
+                self.method = start_vars['mapmethod_rqtl']
             else:
                 self.method = "em"
             self.model = start_vars['mapmodel_rqtl_geno']
@@ -242,10 +243,10 @@ class RunMapping:
                self.pair_scan = True
             if self.permCheck and self.num_perm > 0:
                 self.perm_output, self.suggestive, self.significant, results = rqtl_mapping.run_rqtl(
-                    self.this_trait.name, self.vals, self.samples, self.dataset, self.mapping_scale, self.model, self.method, self.num_perm, perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates)
+                    self.this_trait.name, self.vals, self.samples, self.dataset, self.pair_scan, self.mapping_scale, self.model, self.method, self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates)
             else:
-                results = rqtl_mapping.run_rqtl(self.this_trait.name, self.vals, self.samples, self.dataset, self.mapping_scale, self.model, self.method,
-                                                     self.num_perm, perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates)
+                results = rqtl_mapping.run_rqtl(self.this_trait.name, self.vals, self.samples, self.dataset, self.pair_scan, self.mapping_scale, self.model, self.method,
+                                                     self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates)
         elif self.mapping_method == "reaper":
             if "startMb" in start_vars:  # ZS: Check if first time page loaded, so it can default to ON
                 if "additiveCheck" in start_vars:
@@ -326,33 +327,8 @@ class RunMapping:
             self.no_results = True
         else:
             if self.pair_scan == True:
-                self.qtl_results = []
-                highest_chr = 1  # This is needed in order to convert the highest chr to X/Y
-                for marker in results:
-                    if marker['chr1'] > 0 or marker['chr1'] == "X" or marker['chr1'] == "X/Y":
-                        if marker['chr1'] > highest_chr or marker['chr1'] == "X" or marker['chr1'] == "X/Y":
-                            highest_chr = marker['chr1']
-                        if 'lod_score' in list(marker.keys()):
-                            self.qtl_results.append(marker)
-
-                self.trimmed_markers = results
-
-                for qtl in enumerate(self.qtl_results):
-                    self.json_data['chr1'].append(str(qtl['chr1']))
-                    self.json_data['chr2'].append(str(qtl['chr2']))
-                    self.json_data['Mb'].append(qtl['Mb'])
-                    self.json_data['markernames'].append(qtl['name'])
-
-                self.js_data = dict(
-                    json_data=self.json_data,
-                    this_trait=self.this_trait.name,
-                    data_set=self.dataset.name,
-                    maf=self.maf,
-                    manhattan_plot=self.manhattan_plot,
-                    mapping_scale=self.mapping_scale,
-                    qtl_results=self.qtl_results
-                )
-
+                self.figure_data = results[0]
+                self.table_data = results[1]
             else:
                 self.qtl_results = []
                 self.results_for_browser = []
@@ -764,9 +740,9 @@ def get_perm_strata(this_trait, sample_list, categorical_vars, used_samples):
         if sample in list(sample_list.sample_attribute_values.keys()):
             combined_string = ""
             for var in categorical_vars:
-                if var.lower() in sample_list.sample_attribute_values[sample]:
+                if var in sample_list.sample_attribute_values[sample]:
                     combined_string += str(
-                        sample_list.sample_attribute_values[sample][var.lower()])
+                        sample_list.sample_attribute_values[sample][var])
                 else:
                     combined_string += "NA"
         else:
diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py
index 92cea550..ae30aa59 100644
--- a/wqflask/wqflask/show_trait/SampleList.py
+++ b/wqflask/wqflask/show_trait/SampleList.py
@@ -32,7 +32,7 @@ class SampleList:
         for counter, sample_name in enumerate(sample_names, 1):
             sample_name = sample_name.replace("_2nd_", "")
 
-            # ZS: self.this_trait will be a list if it is a Temp trait
+            # self.this_trait will be a list if it is a Temp trait
             if isinstance(self.this_trait, list):
                 sample = webqtlCaseData.webqtlCaseData(name=sample_name)
                 if counter <= len(self.this_trait):
@@ -47,7 +47,7 @@ class SampleList:
                                 name=sample_name,
                                 value=float(self.this_trait[counter - 1]))
             else:
-                # ZS - If there's no value for the sample/strain,
+                # If there's no value for the sample/strain,
                 # create the sample object (so samples with no value
                 # are still displayed in the table)
                 try:
@@ -63,29 +63,29 @@ class SampleList:
 
             sample.this_id = str(counter)
 
-            # ZS: For extra attribute columns; currently only used by
+            # For extra attribute columns; currently only used by
             # several datasets
             if self.sample_attribute_values:
                 sample.extra_attributes = self.sample_attribute_values.get(
                     sample_name, {})
 
-                # ZS: Add a url so RRID case attributes can be displayed as links
-                if 'rrid' in sample.extra_attributes:
+                # Add a url so RRID case attributes can be displayed as links
+                if '36' in sample.extra_attributes:
                     if self.dataset.group.species == "mouse":
-                        if len(sample.extra_attributes['rrid'].split(":")) > 1:
-                            the_rrid = sample.extra_attributes['rrid'].split(":")[
+                        if len(sample.extra_attributes['36'].split(":")) > 1:
+                            the_rrid = sample.extra_attributes['36'].split(":")[
                                 1]
-                            sample.extra_attributes['rrid'] = [
-                                sample.extra_attributes['rrid']]
-                            sample.extra_attributes['rrid'].append(
+                            sample.extra_attributes['36'] = [
+                                sample.extra_attributes['36']]
+                            sample.extra_attributes['36'].append(
                                 webqtlConfig.RRID_MOUSE_URL % the_rrid)
                     elif self.dataset.group.species == "rat":
-                        if len(str(sample.extra_attributes['rrid'])):
-                            the_rrid = sample.extra_attributes['rrid'].split("_")[
+                        if len(str(sample.extra_attributes['36'])):
+                            the_rrid = sample.extra_attributes['36'].split("_")[
                                 1]
-                            sample.extra_attributes['rrid'] = [
-                                sample.extra_attributes['rrid']]
-                            sample.extra_attributes['rrid'].append(
+                            sample.extra_attributes['36'] = [
+                                sample.extra_attributes['36']]
+                            sample.extra_attributes['36'].append(
                                 webqtlConfig.RRID_RAT_URL % the_rrid)
 
             self.sample_list.append(sample)
@@ -124,17 +124,19 @@ class SampleList:
 
         # Get attribute names and distinct values for each attribute
         results = g.db.execute('''
-                        SELECT DISTINCT CaseAttribute.Id, CaseAttribute.Name, CaseAttributeXRefNew.Value
+                        SELECT DISTINCT CaseAttribute.Id, CaseAttribute.Name, CaseAttribute.Description, CaseAttributeXRefNew.Value
                         FROM CaseAttribute, CaseAttributeXRefNew
                         WHERE CaseAttributeXRefNew.CaseAttributeId = CaseAttribute.Id
                         AND CaseAttributeXRefNew.InbredSetId = %s
-                        ORDER BY lower(CaseAttribute.Name)''', (str(self.dataset.group.id),))
+                        ORDER BY CaseAttribute.Id''', (str(self.dataset.group.id),))
 
         self.attributes = {}
-        for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name)):
-            key, name = attr
+        for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name, row.Description)):
+            key, name, description = attr
             self.attributes[key] = Bunch()
+            self.attributes[key].id = key
             self.attributes[key].name = name
+            self.attributes[key].description = description
             self.attributes[key].distinct_values = [
                 item.Value for item in values]
             self.attributes[key].distinct_values = natural_sort(
@@ -168,10 +170,13 @@ class SampleList:
 
             for sample_name, items in itertools.groupby(results.fetchall(), lambda row: row.SampleName):
                 attribute_values = {}
+                # Make a list of attr IDs without values (that have values for other samples)
+                valueless_attr_ids = [self.attributes[key].id for key in self.attributes.keys()]
                 for item in items:
+                    valueless_attr_ids.remove(item.Id)
                     attribute_value = item.Value
 
-                    # ZS: If it's an int, turn it into one for sorting
+                    # If it's an int, turn it into one for sorting
                     # (for example, 101 would be lower than 80 if
                     # they're strings instead of ints)
                     try:
@@ -179,8 +184,10 @@ class SampleList:
                     except ValueError:
                         pass
 
-                    attribute_values[self.attributes[item.Id].name.lower(
-                    )] = attribute_value
+                    attribute_values[str(item.Id)] = attribute_value
+                for attr_id in valueless_attr_ids:
+                    attribute_values[str(attr_id)] = ""
+
                 self.sample_attribute_values[sample_name] = attribute_values
 
     def get_first_attr_col(self):
diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py
index c07430dd..c947a3b4 100644
--- a/wqflask/wqflask/show_trait/show_trait.py
+++ b/wqflask/wqflask/show_trait/show_trait.py
@@ -1,3 +1,5 @@
+from typing import Dict
+
 import string
 import datetime
 import uuid
@@ -192,6 +194,8 @@ class ShowTrait:
                 [self.dataset.species.chromosomes.chromosomes[this_chr].name, i])
 
         self.genofiles = self.dataset.group.get_genofiles()
+        study_samplelist_json = self.dataset.group.get_study_samplelists()
+        self.study_samplelists = [study["title"] for study in study_samplelist_json]
 
         # ZS: No need to grab scales from .geno file unless it's using
         # a mapping method that reads .geno files
@@ -277,9 +281,13 @@ class ShowTrait:
             hddn['species'] = self.dataset.group.species
         hddn['use_outliers'] = False
         hddn['method'] = "gemma"
+        hddn['mapmethod_rqtl'] = "hk"
+        hddn['mapmodel_rqtl'] = "normal"
+        hddn['pair_scan'] = ""
         hddn['selected_chr'] = -1
         hddn['mapping_display_all'] = True
         hddn['suggestive'] = 0
+        hddn['study_samplelists'] = json.dumps(study_samplelist_json)
         hddn['num_perm'] = 0
         hddn['categorical_vars'] = ""
         if categorical_var_list:
@@ -295,7 +303,7 @@ class ShowTrait:
         hddn['compare_traits'] = []
         hddn['export_data'] = ""
         hddn['export_format'] = "excel"
-        if len(self.scales_in_geno) < 2:
+        if len(self.scales_in_geno) < 2 and bool(self.scales_in_geno):
             hddn['mapping_scale'] = self.scales_in_geno[list(
                 self.scales_in_geno.keys())[0]][0][0]
 
@@ -520,6 +528,9 @@ class ShowTrait:
                                          sample_group_type='primary',
                                          header="%s Only" % (self.dataset.group.name))
             self.sample_groups = (primary_samples,)
+            print("\nttttttttttttttttttttttttttttttttttttttttttttt\n")
+            print(self.sample_groups)
+            print("\nttttttttttttttttttttttttttttttttttttttttttttt\n")
 
         self.primary_sample_names = primary_sample_names
         self.dataset.group.allsamples = all_samples_ordered
@@ -693,7 +704,7 @@ def get_categorical_variables(this_trait, sample_list) -> list:
     if len(sample_list.attributes) > 0:
         for attribute in sample_list.attributes:
             if len(sample_list.attributes[attribute].distinct_values) < 10:
-                categorical_var_list.append(sample_list.attributes[attribute].name)
+                categorical_var_list.append(str(sample_list.attributes[attribute].id))
 
     return categorical_var_list
 
@@ -799,3 +810,41 @@ def get_scales_from_genofile(file_location):
         return [["physic", "Mb"], ["morgan", "cM"]]
     else:
         return [["physic", "Mb"]]
+
+
+
+def get_diff_of_vals(new_vals: Dict, trait_id: str) -> Dict:
+    """ Get the diff between current sample values and the values in the DB
+
+    Given a dict of the changed values and the trait/dataset ID, return a Dict
+    with keys corresponding to each sample with a changed value and a value
+    that is a dict with keys for the old_value and new_value
+
+    """
+
+    trait_name = trait_id.split(":")[0]
+    dataset_name = trait_id.split(":")[1]
+    trait_ob = create_trait(name=trait_name, dataset_name=dataset_name)
+
+    old_vals = {sample : trait_ob.data[sample].value for sample in trait_ob.data}
+
+    shared_samples = set.union(set(new_vals.keys()), set(old_vals.keys()))
+
+    diff_dict = {}
+    for sample in shared_samples:
+        try:
+            new_val = round(float(new_vals[sample]), 3)
+        except:
+            new_val = "x"
+        try:
+            old_val = round(float(old_vals[sample]), 3)
+        except:
+            old_val = "x"
+
+        if new_val != old_val:
+            diff_dict[sample] = {
+                "new_val": new_val,
+                "old_val": old_val
+            }
+
+    return diff_dict
diff --git a/wqflask/wqflask/static/new/css/bootstrap-custom.css b/wqflask/wqflask/static/new/css/bootstrap-custom.css
index 7c8549e1..a0d3ff6a 100644
--- a/wqflask/wqflask/static/new/css/bootstrap-custom.css
+++ b/wqflask/wqflask/static/new/css/bootstrap-custom.css
@@ -327,7 +327,7 @@ th {
     font-family: 'Glyphicons Halflings';
 
     src: url('../fonts/glyphicons-halflings-regular.eot');
-    src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+    src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
 }
 
 .glyphicon {
@@ -7554,5 +7554,3 @@ button.close {
         display: none !important;
     }
 }
-
-/*# sourceMappingURL=bootstrap.css.map */
\ No newline at end of file
diff --git a/wqflask/wqflask/static/new/css/show_trait.css b/wqflask/wqflask/static/new/css/show_trait.css
index 782dabc2..b0514e01 100644
--- a/wqflask/wqflask/static/new/css/show_trait.css
+++ b/wqflask/wqflask/static/new/css/show_trait.css
@@ -260,3 +260,33 @@ input.trait-value-input {
 div.inline-div {
   display: inline;
 }
+
+/* div.colorbox_border {
+  border: 1px solid grey;
+} */
+div#cboxContent {
+  /* box-shadow:
+  0 2.8px 2.2px rgba(0, 0, 0, 0.034),
+  0 6.7px 5.3px rgba(0, 0, 0, 0.048),
+  0 12.5px 10px rgba(0, 0, 0, 0.06),
+  0 22.3px 17.9px rgba(0, 0, 0, 0.072),
+  0 41.8px 33.4px rgba(0, 0, 0, 0.086),
+  0 100px 80px rgba(0, 0, 0, 0.12) */
+
+  padding: 10px 10px 5px 10px;
+
+  -moz-box-shadow: 3px 3px 5px #535353;
+  -webkit-box-shadow: 3px 3px 5px #535353;
+  box-shadow: 3px 3px 5px #535353;
+
+  -moz-border-radius: 6px 6px 6px 6px;
+  -webkit-border-radius: 6px;
+  border-radius: 6px 6px 6px 6px;
+
+  /* border: 2px solid grey; */
+}
+
+#cboxClose {
+  margin-right: 5px;
+  margin-bottom: 2px;
+}
diff --git a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js
index 3e414034..00025a32 100644
--- a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js
+++ b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js
@@ -65,10 +65,8 @@ if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) {
 
 collection_click = function() {
   var this_collection_url;
-  console.log("Clicking on:", $(this));
   this_collection_url = $(this).find('.collection_name').prop("href");
   this_collection_url += "&json";
-  console.log("this_collection_url", this_collection_url);
   collection_list = $("#collections_holder").html();
   return $.ajax({
     dataType: "json",
@@ -79,32 +77,57 @@ collection_click = function() {
 
 submit_click = function() {
   var covariates_string = "";
-  var covariates_display_string = "";
+  var covariates_as_set = new Set();
+  $(".selected-covariates:first option").each(function() {
+    if ($(this).val() != ""){
+      covariates_as_set.add($(this).val() + "," + $(this).text());
+    }
+  });
   $('#collections_holder').find('input[type=checkbox]:checked').each(function() {
     var this_dataset, this_trait;
     this_trait = $(this).parents('tr').find('.trait').text();
     this_trait_display = $(this).parents('tr').find('.trait').data("display_name");
     this_description = $(this).parents('tr').find('.description').text();
-    console.log("this_trait is:", this_trait_display);
     this_dataset = $(this).parents('tr').find('.dataset').data("dataset");
-    console.log("this_dataset is:", this_dataset);
-    covariates_string += this_trait + ":" + this_dataset + ","
-    //this_covariate_display_string = this_trait + ": " + this_description
     this_covariate_display_string = this_trait_display
     if (this_covariate_display_string.length > 50) {
       this_covariate_display_string = this_covariate_display_string.substring(0, 45) + "..."
     }
-    covariates_display_string += this_covariate_display_string + "\n"
+    covariates_as_set.add(this_trait + ":" + this_dataset + "," + this_covariate_display_string)
+  });
+
+  covariates_as_list = Array.from(covariates_as_set)
+
+  // Removed the starting "No covariates selected" option before adding options for each covariate
+  if (covariates_as_list.length > 0){
+    $(".selected-covariates option[value='']").each(function() {
+      $(this).remove();
+    });
+  }
+
+  $(".selected-covariates option").each(function() {
+    $(this).remove();
   });
-  // Trim the last newline from display_string
-  covariates_display_string = covariates_display_string.replace(/\n$/, "")
 
-  // Trim the last comma
-  covariates_string = covariates_string.substring(0, covariates_string.length - 1)
-  //covariates_display_string = covariates_display_string.substring(0, covariates_display_string.length - 2)
+  covariate_list_for_form = []
+  $.each(covariates_as_list, function (index, value) {
+    option_value = value.split(",")[0]
+    option_text = value.split(",")[1]
+    $(".selected-covariates").append($("<option/>", {
+      value: option_value,
+      text: option_text
+    }))
+    covariate_list_for_form.push(option_value)
+  });
 
-  $("input[name=covariates]").val(covariates_string)
-  $(".selected-covariates").val(covariates_display_string)
+  $("input[name=covariates]").val(covariate_list_for_form.join(","));
+
+  cofactor_count = $(".selected-covariates:first option").length;
+  if (cofactor_count > 10){
+    $(".selected-covariates").attr("size", 10);
+  } else {
+    $(".selected-covariates").attr("size", cofactor_count);
+  }
 
   return $.colorbox.close();
 };
@@ -186,9 +209,8 @@ color_by_trait = function(trait_sample_data, textStatus, jqXHR) {
 process_traits = function(trait_data, textStatus, jqXHR) {
   var the_html, trait, _i, _len;
   console.log('in process_traits with trait_data:', trait_data);
-  the_html = "<button id='back_to_collections' class='btn btn-inverse btn-small'>";
-  the_html += "<i class='icon-white icon-arrow-left'></i> Back </button>";
-  the_html += "    <button id='submit' class='btn btn-primary btn-small'> Submit </button>";
+  the_html = "<button class='btn btn-success btn-small submit'> Submit </button>";
+  the_html += "<button id='back_to_collections' class='btn btn-inverse btn-small' style='float: right;'>Back</button>";
   the_html += "<table id='collection_table' style='padding-top: 10px;' class='table table-hover'>";
   the_html += "<thead><tr><th></th><th>Record</th><th>Data Set</th><th>Description</th></tr></thead>";
   the_html += "<tbody>";
@@ -221,6 +243,6 @@ back_to_collections = function() {
 };
 
 $(".collection_line").on("click", collection_click);
-$("#submit").on("click", submit_click);
+$(".submit").on("click", submit_click);
 $(".trait").on("click", trait_click);
-$("#back_to_collections").on("click", back_to_collections);
\ No newline at end of file
+$("#back_to_collections").on("click", back_to_collections);
diff --git a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js
index 6ca92fb6..4de1b0ac 100644
--- a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js
+++ b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js
@@ -93,15 +93,15 @@ build_columns = function() {
     );
   }
 
-  attr_keys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].name.toLowerCase() > js_data.attributes[b].name.toLowerCase()) ? 1 : -1)
+  attr_keys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].id > js_data.attributes[b].id) ? 1 : -1)
   for (i = 0; i < attr_keys.length; i++){
     column_list.push(
       {
-        'title': "<div style='text-align: " + js_data.attributes[attr_keys[i]].alignment + "'>" + js_data.attributes[attr_keys[i]].name + "</div>",
+        'title': "<div title='" + js_data.attributes[attr_keys[i]].description + "' style='text-align: " + js_data.attributes[attr_keys[i]].alignment + "'>" + js_data.attributes[attr_keys[i]].name + "</div>",
         'type': "natural",
         'data': null,
         'render': function(data, type, row, meta) {
-          attr_name = Object.keys(data.extra_attributes).sort()[meta.col - data.first_attr_col]
+          attr_name = Object.keys(data.extra_attributes).sort((a, b) => (parseInt(a) > parseInt(b)) ? 1 : -1)[meta.col - data.first_attr_col]
 
           if (attr_name != null && attr_name != undefined){
             if (Array.isArray(data.extra_attributes[attr_name])){
diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js
index 77ef1720..f050d4ae 100644
--- a/wqflask/wqflask/static/new/javascript/show_trait.js
+++ b/wqflask/wqflask/static/new/javascript/show_trait.js
@@ -98,11 +98,54 @@ sample_group_types = js_data.sample_group_types;
 $(".select_covariates").click(function () {
   open_covariate_selection();
 });
+
 $(".remove_covariates").click(function () {
-  $("input[name=covariates]").val("")
-  $(".selected-covariates").val("")
+  $(".selected-covariates option:selected").each(function() {
+    this_val = $(this).val();
+    $(".selected-covariates option").each(function(){
+      if ($(this).val() == this_val){
+        $(this).remove();
+      }
+    })
+    cofactor_count = $(".selected-covariates:first option").length
+    if (cofactor_count > 2 && cofactor_count < 11){
+      $(".selected-covariates").each(function() {
+        $(this).attr("size", $(".selected-covariates:first option").length)
+      });
+    } else if (cofactor_count > 10) {
+      $(".selected-covariates").each(function() {
+        $(this).attr("size", 10)
+      });
+    } else {
+      $(".selected-covariates").each(function() {
+        $(this).attr("size", 2)
+      });
+    }
+    if (cofactor_count == 0){
+      $(".selected-covariates").each(function() {
+        $(this).append($("<option/>", {
+          value: "",
+          text: "No covariates selected"
+        }))
+      })
+    }
+  });
+
+  covariates_list = [];
+  $(".selected-covariates:first option").each(function() {
+    covariates_list.push($(this).val());
+  })
+  $("input[name=covariates]").val(covariates_list.join(","))
 });
 
+$(".remove_all_covariates").click(function() {
+  $(".selected-covariates option").each(function() {
+    $(this).remove();
+  });
+  $(".selected-covariates").attr("size", 2)
+  $("input[name=covariates]").val("");
+})
+
 open_trait_selection = function() {
   return $('#collections_holder').load('/collections/list?color_by_trait #collections_list', (function(_this) {
     return function() {
@@ -608,13 +651,14 @@ $(".corr_compute").on("click", (function(_this) {
 create_value_dropdown = function(value) {
   return "<option val=" + value + ">" + value + "</option>";
 };
+
 populate_sample_attributes_values_dropdown = function() {
   var attribute_info, key, sample_attributes, selected_attribute, value, _i, _len, _ref, _ref1, _results;
   $('#attribute_values').empty();
   sample_attributes = [];
 
   var attributes_as_list = Object.keys(js_data.attributes).map(function(key) {
-    return [key, js_data.attributes[key].name.toLowerCase()];
+    return [key, js_data.attributes[key].id];
   });
 
   attributes_as_list.sort(function(first, second) {
@@ -628,7 +672,7 @@ populate_sample_attributes_values_dropdown = function() {
   });
 
   for (i=0; i < attributes_as_list.length; i++) {
-    attribute_info = js_data.attributes[attributes_as_list[i][0]]
+    attribute_info = js_data.attributes[attributes_as_list[i][1]]
     sample_attributes.push(attribute_info.distinct_values);
   }
 
@@ -667,11 +711,13 @@ block_by_attribute_value = function() {
   let exclude_val_nodes = table_api.column(attribute_start_pos + parseInt(exclude_column)).nodes().to$();
 
   for (i = 0; i < exclude_val_nodes.length; i++) {
-    let this_col_value = exclude_val_nodes[i].childNodes[0].data;
-    let this_val_node = val_nodes[i].childNodes[0];
+    if (exclude_val_nodes[i].hasChildNodes()) {
+      let this_col_value = exclude_val_nodes[i].childNodes[0].data;
+      let this_val_node = val_nodes[i].childNodes[0];
 
-    if (this_col_value == exclude_by_value){
-      this_val_node.value = "x";
+      if (this_col_value == exclude_by_value){
+        this_val_node.value = "x";
+      }
     }
   }
 
@@ -713,10 +759,34 @@ block_by_index = function() {
   for (_k = 0, _len1 = index_list.length; _k < _len1; _k++) {
     index = index_list[_k];
     val_nodes[index - 1].childNodes[0].value = "x";
-
   }
 };
 
+filter_by_study = function() {
+  let this_study = $('#filter_study').val();
+
+  let study_sample_data = JSON.parse($('input[name=study_samplelists]').val())
+  let filter_samples = study_sample_data[parseInt(this_study)]['samples']
+
+  if ($('#filter_study_group').length){
+    let block_group = $('#filter_study_group').val();
+    if (block_group === "other") {
+      table_api = $('#samples_other').DataTable();
+    } else {
+      table_api = $('#samples_primary').DataTable();
+    }
+  }
+
+  let sample_nodes = table_api.column(2).nodes().to$();
+  let val_nodes = table_api.column(3).nodes().to$();
+  for (i = 0; i < sample_nodes.length; i++) {
+    this_sample = sample_nodes[i].childNodes[0].innerText;
+    if (!filter_samples.includes(this_sample)){
+      val_nodes[i].childNodes[0].value = "x";
+    }
+  }
+}
+
 filter_by_value = function() {
   let filter_logic = $('#filter_logic').val();
   let filter_column = $('#filter_column').val();
@@ -748,7 +818,7 @@ filter_by_value = function() {
       var this_col_value = filter_val_nodes[i].childNodes[0].value;
     } else {
       if (filter_val_nodes[i].childNodes[0] !== undefined){
-        var this_col_value = filter_val_nodes[i].childNodes[0].data;
+        var this_col_value = filter_val_nodes[i].innerText;
       } else {
         continue
       }
@@ -1690,6 +1760,11 @@ $('#block_by_index').click(function(){
   edit_data_change();
 });
 
+$('#filter_by_study').click(function(){
+  filter_by_study();
+  edit_data_change();
+})
+
 $('#filter_by_value').click(function(){
   filter_by_value();
   edit_data_change();
diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js
index e457fa4a..4f994eae 100644
--- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js
+++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js
@@ -145,7 +145,7 @@ var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form
                           'score_type', 'suggestive', 'significant', 'num_perm', 'permCheck', 'perm_output', 'perm_strata', 'categorical_vars', 'num_bootstrap', 'bootCheck', 'bootstrap_results',
                           'LRSCheck', 'covariates', 'maf', 'use_loco', 'manhattan_plot', 'control_marker', 'do_control', 'genofile',
                           'pair_scan', 'startMb', 'endMb', 'graphWidth', 'lrsMax', 'additiveCheck', 'showSNP', 'showGenes', 'viewLegend', 'haplotypeAnalystCheck', 
-                          'mapmethod_rqtl_geno', 'mapmodel_rqtl_geno', 'temp_trait', 'group', 'species', 'reaper_version', 'primary_samples']
+                          'mapmethod_rqtl', 'mapmodel_rqtl', 'temp_trait', 'group', 'species', 'reaper_version', 'primary_samples']
 
 $(".rqtl-geno-tab, #rqtl_geno_compute").on("click", (function(_this) {
   return function() {
@@ -156,6 +156,8 @@ $(".rqtl-geno-tab, #rqtl_geno_compute").on("click", (function(_this) {
       $('input[name=selected_chr]').val($('#chr_rqtl_geno').val());
       $('input[name=mapping_scale]').val($('#scale_rqtl_geno').val());
       $('input[name=genofile]').val($('#genofile_rqtl_geno').val());
+      $('input[name=mapmodel_rqtl]').val($('#mapmodel_rqtl_geno').val());
+      $('input[name=mapmethod_rqtl]').val($('#mapmethod_rqtl_geno').val());
       $('input[name=num_perm]').val($('input[name=num_perm_rqtl_geno]').val());
       $('input[name=categorical_vars]').val(js_data.categorical_vars)
       $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_rqtl]:checked').val());
@@ -177,12 +179,14 @@ $(".rqtl-pair-tab, #rqtl_pair_compute").on("click", (function(_this) {
       var form_data, url;
       url = "/loading";
       $('input[name=method]').val("rqtl_geno");
-      $('input[name=pair_scan]').val("");
-      $('input[name=genofile]').val($('#genofile_rqtl_geno').val());
-      $('input[name=num_perm]').val($('input[name=num_perm_rqtl_geno]').val());
+      $('input[name=pair_scan]').val("true");
+      $('input[name=genofile]').val($('#genofile_rqtl_pair').val());
+      $('input[name=mapmodel_rqtl]').val($('#mapmodel_rqtl_pair').val());
+      $('input[name=mapmethod_rqtl]').val($('#mapmethod_rqtl_pair').val());
+      $('input[name=num_perm]').val($('input[name=num_perm_rqtl_pair]').val());
       $('input[name=categorical_vars]').val(js_data.categorical_vars)
-      $('input[name=control_marker]').val($('input[name=control_rqtl_geno]').val());
-      $('input[name=do_control]').val($('input[name=do_control_rqtl]:checked').val());
+      $('input[name=control_marker]').val($('input[name=control_rqtl_pair]').val());
+      $('input[name=do_control]').val($('input[name=do_control_rqtl_pair]:checked').val());
       $('input[name=tool_used]').val("Mapping");
       $('input[name=form_url]').val("/run_mapping");
       $('input[name=wanted_inputs]').val(mapping_input_list.join(","));
@@ -259,25 +263,25 @@ $("#use_composite_choice").change(composite_mapping_fields);
 
 $("#mapping_method_choice").change(mapping_method_fields);
 
-$("#mapmodel_rqtl_geno").change(function() {
+$("#mapmodel_rqtl_geno,#mapmodel_rqtl_pair").change(function() {
   if ($(this).val() == "np"){
     $("#mapmethod_rqtl_geno").attr('disabled', 'disabled');
     $("#mapmethod_rqtl_geno").css('background-color', '#CCC');
-    $("#missing_geno").attr('disabled', 'disabled');
-    $("#missing_geno").css('background-color', '#CCC');
+    $("#missing_geno,#missing_geno_pair").attr('disabled', 'disabled');
+    $("#missing_geno,#missing_geno_pair").css('background-color', '#CCC');
   } else {
     $("#mapmethod_rqtl_geno").removeAttr('disabled');
     $("#mapmethod_rqtl_geno").css('background-color', '#FFF');
-    $("#missing_geno").removeAttr('disabled');
-    $("#missing_geno").css('background-color', '#FFF');
+    $("#missing_geno,#missing_geno_pair").removeAttr('disabled');
+    $("#missing_geno,#missing_geno_pair").css('background-color', '#FFF');
   }
 });
 
-$("#mapmethod_rqtl_geno").change(function() {
+$("#mapmethod_rqtl_geno,#mapmethod_rqtl_pair").change(function() {
   if ($(this).val() == "mr"){
-    $("#missing_geno_div").css('display', 'block');
+    $("#missing_geno_div,#missing_geno_pair_div").css('display', 'block');
   } else {
-    $("#missing_geno_div").css('display', 'none');
+    $("#missing_geno_div,#missing_geno_pair_div").css('display', 'none');
   }
 });
 
diff --git a/wqflask/wqflask/templates/collections/add.html b/wqflask/wqflask/templates/collections/add.html
index 0398c6e4..8640fdb8 100644
--- a/wqflask/wqflask/templates/collections/add.html
+++ b/wqflask/wqflask/templates/collections/add.html
@@ -5,7 +5,7 @@
         or add to an existing collection.</p>
     </div>
     <div class="modal-body" style="margin-left: 20px;">
-        <form action="/collections/new" target="_blank" data-validate="parsley" id="add_form">
+        <form action="/collections/new" target="_blank" data-validate="parsley" id="add_form" class="form-inline">
             {% if traits is defined %}
             <input type="hidden" name="traits" value="{{ traits }}" />
             {% else %}
@@ -14,10 +14,8 @@
             {% if collections|length > 0 %}
             <fieldset>
               <legend>1. Add to an existing collection</legend>
-              <div style="margin-left: 20px;">  
-                <!--<label>Existing collection name:</label>-->
-                <select name="existing_collection" class="form-control">
-                    <!--<option selected disabled>Select Collection</option>-->
+              <div style="margin-left: 20px;">
+                <select name="existing_collection" class="form-control" style="width: 80%;">
                 {% for col in collections %}
                     {% if loop.index == 1 %}
                     <option value="{{ col.id }}:{{ col.name }}" selected>{{ col.name }}</option>
@@ -26,8 +24,9 @@
                     {% endif %}
                 {% endfor %}
                 </select>
-                <br />
-                <button type="submit" name="add_to_existing" class="btn btn-primary">Add to existing collection</button>
+                <input type="button" style="display: inline;" id="make_default" value="Make Default">
+              <br><br>
+              <button type="submit" name="add_to_existing" class="btn btn-primary">Add</button>
               </div>
             </fieldset>
             {% endif %}
@@ -35,7 +34,6 @@
             <fieldset>
               <legend>{% if collections|length > 0 %}2. {% else %}{% endif %}Create a new collection</legend>
               <div style="margin-left: 20px;">
-                <!--<label>Collection name:</label>-->
                 <input type="text" name="new_collection" placeholder=" Name of new collection..."
                     data-trigger="change" data-minlength="5" data-maxlength="50" style="width: 100%">
                 <button type="submit" name="create_new" class="btn btn-primary" style="margin-top: 20px;">Create collection</button>
@@ -54,6 +52,21 @@
     parent.jQuery.colorbox.close();
   });
 
+  make_default = function() {
+    alert("The current collection is now your default collection.")
+    let uc_id = $('[name=existing_collection] option:selected').val().split(":")[0]
+    $.cookie('default_collection', uc_id, {
+        expires: 365,
+        path: '/'
+    });
+
+    let default_collection_id = $.cookie('default_collection');
+  };
+
+  $("#make_default").on("click", function(){
+    make_default();
+  });
+
   apply_default = function() {
     let default_collection_id = $.cookie('default_collection');
     if (default_collection_id) {
diff --git a/wqflask/wqflask/templates/display_files_admin.html b/wqflask/wqflask/templates/display_files_admin.html
new file mode 100644
index 00000000..4b4babc4
--- /dev/null
+++ b/wqflask/wqflask/templates/display_files_admin.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% block title %}Trait Submission{% endblock %}
+{% block content %}
+<!-- Start of body -->
+{% with messages = get_flashed_messages(with_categories=true) %}
+{% if messages %}
+{% for category, message in messages %}
+<div class="container-fluid bg-{{ category }}">
+    <p>{{ message }}</p>
+</div>
+{% endfor %}
+{% endif %}
+{% endwith %}
+Show files for approval
+
+<div>
+    <ul>
+        {% for file in files %}
+        <li><a href="/display-file/{{ file }}" target="_blank">{{ file }}</a><br/>
+            <button><a href="/data-samples/approve/{{ file }}">Approve</a></button>
+            <button><a href="/data-samples/reject/{{ file }}">Reject</a></button></li>
+        {% endfor %}
+    </ul>
+</div>
+{%endblock%}
+
+{% block js %}
+<script>
+ gn_server_url = "{{ gn_server_url }}";
+
+</script>
+{% endblock %}
diff --git a/wqflask/wqflask/templates/display_files_user.html b/wqflask/wqflask/templates/display_files_user.html
new file mode 100644
index 00000000..b6bab709
--- /dev/null
+++ b/wqflask/wqflask/templates/display_files_user.html
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+{% block title %}Trait Submission{% endblock %}
+{% block content %}
+<!-- Start of body -->
+{% with messages = get_flashed_messages(with_categories=true) %}
+{% if messages %}
+{% for category, message in messages %}
+<div class="container-fluid bg-{{ category }}">
+    <p>{{ message }}</p>
+</div>
+{% endfor %}
+{% endif %}
+{% endwith %}
+Show files for approval
+
+<div>
+    <ul>
+        {% for file in files %}
+        <li><a href="/display-file/{{ file }}" target="_blank">{{ file }}</a><br/>
+            <button><a href="/data-samples/reject/{{ file }}">Reject</a></button></li>
+        {% endfor %}
+    </ul>
+</div>
+{%endblock%}
+
+{% block js %}
+<script>
+ gn_server_url = "{{ gn_server_url }}";
+
+</script>
+{% endblock %}
diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html
index 7d4c65f8..7a841793 100644
--- a/wqflask/wqflask/templates/edit_phenotype.html
+++ b/wqflask/wqflask/templates/edit_phenotype.html
@@ -2,8 +2,18 @@
 {% block title %}Trait Submission{% endblock %}
 {% block content %}
 <!-- Start of body -->
-Edit Trait for Published Database
-Submit Trait | Reset
+{% with messages = get_flashed_messages(with_categories=true) %}
+{% if messages %}
+{% for category, message in messages %}
+<div class="container-fluid bg-{{ category }}">
+    <p>{{ message }}</p>
+</div>
+{% endfor %}
+{% endif %}
+{% endwith %}
+<div class="page-header text-center">
+    <h1>Edit Trait for Published Database</h1>
+</div>
 
 {% if diff %}
 
@@ -53,7 +63,7 @@ Submit Trait | Reset
 
 {% endif %}
 
-<form id="edit-form" class="form-horizontal" method="post" action="/trait/update">
+<form id="edit-form" class="form-horizontal" method="post" action="/trait/update" enctype=multipart/form-data>
     <h2 class="text-center">Trait Information:</h2>
     <div class="form-group">
         <label for="pubmed-id" class="col-sm-2 control-label">Pubmed ID:</label>
@@ -207,10 +217,18 @@ Submit Trait | Reset
             <input name="old_sequence" class="changed" type="hidden" value="{{ publication.sequence |default('', true) }}"/>
         </div>
     </div>
-    <div class="controls" style="display:block; margin-left: 40%; margin-right: 20%;">
+    <div style="margin-left: 13%;">
+        <a href="/trait/{{ publish_xref.id_ }}/sampledata/{{ publish_xref.phenotype_id }}" class="btn btn-link btn-sm">
+            Sample Data(CSV Download)
+        </a>
+    </div>
+    <div class="form-group">
+        <input type = "file" class="col-sm-4 control-label" name = "file" />
+    </div>
+    <div class="controls center-block" style="width: max-content;">
         <input name="dataset-name" class="changed" type="hidden" value="{{ publish_xref.id_ }}"/>
-        <input name="phenotype-id" class="changed" type="hidden" value="{{ publish_xref.phenotype_id }}"/>
         <input name="inbred-set-id" class="changed" type="hidden" value="{{ publish_xref.inbred_set_id }}"/>
+        <input name="phenotype-id" class="changed" type="hidden" value="{{ publish_xref.phenotype_id }}"/>
         <input name="comments" class="changed" type="hidden" value="{{ publish_xref.comments }}"/>
         <input type="submit" style="width: 125px; margin-right: 25px;" class="btn btn-primary form-control col-xs-2 changed" value="Submit Change">
         <input type="reset" style="width: 110px;" class="btn btn-primary form-control col-xs-2 changed" onClick="window.location.reload();" value="Reset">
diff --git a/wqflask/wqflask/templates/loading.html b/wqflask/wqflask/templates/loading.html
index 6d6136ac..1edde31e 100644
--- a/wqflask/wqflask/templates/loading.html
+++ b/wqflask/wqflask/templates/loading.html
@@ -25,6 +25,8 @@
           <br>
           transformation = <b><i>{{ start_vars.transform }}</i></b>
           {% endif %}
+          <br>
+          hash of sample values = <b><i>{{ start_vars.vals_hash }}</i></b>
           <br><br>
           <b>Mapping Metadata</b>
           <br>
@@ -68,6 +70,29 @@
           <div style="text-align: center;">
             <img align="center" src="/static/gif/89.gif">
           </div>
+          {% if start_vars.vals_diff|length != 0 and start_vars.transform == "" %}
+          <br><br>
+          <button id="show_full_diff">Show Full Diff</button>
+          <br>
+          <div id="diff_table_container" style="display: none; height:200px; overflow:auto;">
+          <table class="table table-hover">
+            <thead>
+              <th>Sample</th>
+              <th>New Value</th>
+              <th>Old Value</th>
+            </thead>
+            <tbody>
+              {% for sample in start_vars.vals_diff %}
+              <tr>
+                <td>{{ sample }}</td>
+                <td>{{ start_vars.vals_diff[sample].new_val }}</td>
+                <td>{{ start_vars.vals_diff[sample].old_val }}</td>
+              </tr>
+              {% endfor %}
+            </tbody>
+          </table>
+          </div>
+          {% endif %}
         </div>
       </div>
     </div>
@@ -76,7 +101,14 @@
 <script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script>
 <script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
 <script  type="text/javascript">
-
 $("#loading_form").attr("action", "{{ start_vars.form_url }}");
 setTimeout(function(){ $("#loading_form").submit()}, 350);
+
+$('#show_full_diff').click(function() {
+  if ($('#diff_table_container').is(':visible')){
+    $('#diff_table_container').hide();
+  } else {
+    $('#diff_table_container').show();
+  }
+})
 </script>
diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html
index 35d8a157..81eb1ba1 100644
--- a/wqflask/wqflask/templates/mapping_results.html
+++ b/wqflask/wqflask/templates/mapping_results.html
@@ -44,7 +44,12 @@
         {% endif %}
         <input type="hidden" name="num_perm" value="{{ nperm }}">
         <input type="hidden" name="perm_info" value="">
-        <input type="hidden" name="perm_strata" value="{{ perm_strata }}">
+        {% if categorical_vars is defined %}
+        <input type="hidden" name="categorical_vars" value="{{ categorical_vars|join(',') }}">
+        {% endif %}
+        {% if perm_strata is defined %}
+        <input type="hidden" name="perm_strata" value="True">
+        {% endif %}
         <input type="hidden" name="num_bootstrap" value="{{ nboot }}">
         <input type="hidden" name="do_control" value="{{ doControl }}">
         <input type="hidden" name="control_marker" value="{{ controlLocus }}">
diff --git a/wqflask/wqflask/templates/pair_scan_results.html b/wqflask/wqflask/templates/pair_scan_results.html
index fb825b90..43c753e2 100644
--- a/wqflask/wqflask/templates/pair_scan_results.html
+++ b/wqflask/wqflask/templates/pair_scan_results.html
@@ -1,70 +1,128 @@
 {% extends "base.html" %}
 {% block title %}Pair Scan{% endblock %}
 {% block css %}
-    <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />
-     <link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" />
-    <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" />
+<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='DataTables/css/jquery.dataTables.css') }}" />
+<link rel="stylesheet" type="text/css" href="{{ url_for('css', filename='d3-tip/d3-tip.css') }}" />
+<link rel="stylesheet" type="text/css" href="/static/new/css/pair_scan.css" />
+<link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" />
+<link rel="stylesheet" type="text/css" href="/static/new/css/d3panels.css" />
 {% endblock %}
 
 {% block content %} <!-- Start of body -->
 
-    {{ header("Mapping",
-        '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }}
-
-    <div class="container">
-        <div>
-            <h2>
-                Pair Scan
-            </h2>
-        </div>
-        <div id="chart_container">
-            <div class="pair_scan_figure" id="pair_scan_figure">
-                <a href="/tmp/{{ pair_scan_filename }}">
-                    <img alt="Embedded Image" src="data:image/png;base64,
-                    {% for elem in pair_scan_array -%}
-                    {% print("%c"|format(elem)) %}
-                    {%- endfor %}
-                    " /></a>
-            </div>
-        </div>
-        <div>
-            <h2>
-                Results
-            </h2>
-            <table cellpadding="0" cellspacing="0" border="0" id="pair_scan_results" class="table table-hover table-striped table-bordered">
-                <thead>
-                    <tr>
-                        <td>Index</td>
-                        <td>Locus</td>
-                        <td>Chr 1</td>
-                        <td>Mb</td>
-                        <td>Chr 2</td>
-                   </tr>
-                </thead>
-                <tbody>
-                    {% for marker in trimmed_markers %}
-                        <tr>
-                            <td>{{loop.index}}</td>
-                            <td>{{marker.name}}</td>
-                            <td>{{marker.chr1}}</td>
-                            <td>{{marker.Mb}}</td>
-                            <td>{{marker.chr2}}</td>
-                        </tr>
-                    {% endfor %}
-                </tbody>
-            </table>
-        </div>
+{{ header("Mapping",
+    '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }}
+
+<div id="main_div" class="container">
+    <div>
+        <h2>
+            Pair Scan
+        </h2>
+    </div>
+    <div class="qtlcharts" id="chart_container">
+        <div id="pairscan_chart"></div>
     </div>
+    <div style="width: 1100px;">
+        <h2>
+            Results
+        </h2>
+        <table cellpadding="0" cellspacing="0" border="0" id="pair_scan_results" class="table table-hover table-striped table-bordered">
+            <thead>
+                <tr>
+                    <th colspan="3">Interval 1</th>
+                    <th rowspan="3">LOD</th>
+                    <th colspan="3">Interval 2</th>
+                </tr>
+                <tr>
+                    <th rowspan="2">Position</th>
+                    <th colspan="2">Flanking Markers</th>
+                    <th rowspan="2">Position</th>
+                    <th colspan="2">Flanking Markers</th>
+                </tr>
+                <tr>
+                    <th>Proximal</th>
+                    <th>Distal</th>
+                    <th>Proximal</th>
+                    <th>Distal</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for row in table_data %}
+                <tr>
+                    <td>{{ row.pos1 }}</td>
+                    <td>{{ row.proximal1 }}</td>
+                    <td>{{ row.distal1 }}</td>
+                    <td>{{ row.lod }}</td>
+                    <td>{{ row.pos2 }}</td>
+                    <td>{{ row.proximal2 }}</td>
+                    <td>{{ row.distal2 }}</td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+</div>
 
 {% endblock %}
 
 {% block js %}  
 
-    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script>
-    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3-tip/d3-tip.js') }}"></script>
-     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script>
-    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>
-    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/scientific.js') }}"></script>
-    <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='js_alt/underscore.min.js') }}"></script>
+<script>
+    var figure_data = {{ figure_data | safe }}
+</script>
+
+<script src="https://d3js.org/d3.v7.min.js"></script>
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.js') }}"></script>
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTables/js/jquery.dataTables.min.js') }}"></script>
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/plugins/sorting/scientific.js') }}"></script>
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='DataTablesExtensions/scroller/js/dataTables.scroller.min.js') }}"></script>
+<script language="javascript" type="text/javascript" src="/static/new/javascript/d3panels.js"></script>
+
+<script type="text/javascript">
+
+var data, mychart;
+
+// d3.json("data.json").then(function(data) {
+//     var mychart;
+//     mychart = d3panels.lod2dheatmap({
+//       altrectcolor: "",
+//       chrlinecolor: "black",
+//       equalCells: true
+//     });
+//     return mychart(d3.select("div#pairscan_chart"), data);
+//   });
 
-{% endblock %}
\ No newline at end of file
+// d3.json("data.json").then(function(data) {
+//     var mychart;
+//     mychart = d3panels.lod2dheatmap({
+//         oneAtTop: true,
+//         altrectcolor: "",
+//         chrlinecolor: "black",
+//         colors: ["crimson", "white", "slateblue"],
+//         equalCells: true
+//     });
+//     data.poslabel = data.marker;
+//     return mychart(d3.select("div#chart2"), data);
+// });
+
+mychart = d3panels.lod2dheatmap({
+    equalCells: true
+});
+
+mychart(d3.select('div#pairscan_chart'), figure_data);
+
+table_conf = {
+                "sDom": "itir",
+                "autoWidth": true,
+                "bSortClasses": false,
+                "order": [[3, "desc" ]],
+                "scrollY": "100vh",
+                "scroller":  true,
+                "scrollCollapse": true
+            }
+
+trait_table = $('#pair_scan_results').DataTable(table_conf);
+
+</script>
+
+{% endblock %}
diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html
index bb30c54c..53e16aa0 100644
--- a/wqflask/wqflask/templates/show_trait_details.html
+++ b/wqflask/wqflask/templates/show_trait_details.html
@@ -236,7 +236,7 @@
         <button type="button" id="view_in_gn1" class="btn btn-primary" title="View Trait in GN1" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?cmd=show&db={{ this_trait.dataset.name }}&probeset={{ this_trait.name }}', '_blank')">Go to GN1</button>
         {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %}
         {% if this_trait.dataset.type == 'Publish' %}
-        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/trait/{{ this_trait.name }}/edit/{{ this_trait.dataset.id }}', '_blank')">Edit</button>
+        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/trait/{{ this_trait.name }}/edit/inbredset-id/{{ this_trait.dataset.id }}', '_blank')">Edit</button>
         {% endif %}
 
         {% if this_trait.dataset.type == 'ProbeSet' %}
diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html
index a32c45fb..80bc6509 100755
--- a/wqflask/wqflask/templates/show_trait_mapping_tools.html
+++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html
@@ -77,17 +77,20 @@
                               No collections available. Please add traits to a collection to use them as covariates.
                               {% else %}
                               <div class="select-covar-div">
-                                <button type="button" class="btn btn-default select-covar-button select_covariates">Select</button>
+                                <button type="button" class="btn btn-success select-covar-button select_covariates">Select</button>
                                 <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button>
+                                <button type="button" class="btn btn-danger select-covar-button remove_all_covariates">Clear</button>
                               </div>
-                              <textarea rows="3" cols="50" readonly placeholder="No covariates selected" class="selected-covariates"></textarea>
+                              <select size="2" name="selected_covariates_gemma" class="form-control selected-covariates" multiple>
+                                  <option value="">No covariates selected</option>
+                              </select>
                               {% endif %}
                             </div>
                         </div>
                         <div class="mapping_method_fields form-group">
                           <label class="col-xs-3 control-label"></label>
                           <div class="col-xs-6">
-                            <button id="gemma_compute" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Marker Regression" value="Compute">Compute</button>
+                            <button id="gemma_compute" type="button" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Marker Regression" value="Compute">Compute</button>
                           </div>
                         </div>
                     </div>
@@ -190,7 +193,7 @@
                         <div class="mapping_method_fields form-group">
                             <label class="col-xs-3 control-label"></label>
                             <div class="col-xs-6">
-                              <button id="interval_mapping_compute" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Interval Mapping" value="Compute">Compute</button>
+                              <button id="interval_mapping_compute" type="button" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Interval Mapping" value="Compute">Compute</button>
                             </div>
                         </div>
                     </div>
@@ -229,6 +232,17 @@
                                 </select>
                             </div>
                         </div>
+                        {% else %}
+                        <div class="mapping_method_fields form-group">
+                            <label for="scale_select" class="col-xs-3 control-label">Map Scale</label>
+                            <div class="col-xs-2 controls">
+                                <select id="scale_rqtl_geno" class="form-control scale-select">
+                                    {% for item in scales_in_geno[dataset.group.name + ".geno"] %}
+                                    <option value="{{ item[0] }}">{{ item[1] }}</option>
+                                    {% endfor %}
+                                </select>
+                            </div>
+                        </div>
                         {% endif %}
                         <div class="mapping_method_fields form-group">
                             <label for="mapping_permutations" class="col-xs-3 control-label">Permutations</label>
@@ -320,6 +334,113 @@
                             No collections available. Please add traits to a collection to use them as covariates.
                             {% else %}
                             <div class="select-covar-div">
+                              <button type="button" class="btn btn-success select-covar-button select_covariates">Select</button>
+                              <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button>
+                              <button type="button" class="btn btn-danger select-covar-button remove_all_covariates">Clear</button>
+                            </div>
+                            <select size="2" name="selected_covariates_rqtl" class="form-control selected-covariates" multiple>
+                                <option value="">No covariates selected</option>
+                            </select>
+                            {% endif %}
+                          </div>
+                        </div>
+                        <div class="mapping_method_fields form-group">
+                            <label class="col-xs-3 control-label"></label>
+                            <div class="col-xs-6 controls">
+                              <button id="rqtl_geno_compute" type="button" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Marker Regression" value="Compute">Compute</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="tab-pane {% if dataset.group.mapping_id == '3' %}active{% endif %}" id="rqtl_pair">
+                    <div class="form-horizontal section-form-div">
+                        {% if genofiles and genofiles|length > 0 %}
+                        <div class="mapping_method_fields form-group">
+                            <label for="genofiles" class="col-xs-3 control-label">Genotypes</label>
+                            <div class="col-xs-6 controls">
+                                <select id="genofile_rqtl_pair" class="form-control">
+                                    {% for item in genofiles %}
+                                    <option value="{{item['location']}}:{{item['title']}}">{{item['title']}}</option>
+                                    {% endfor %}
+                                </select>
+                            </div>
+                        </div>
+                        {% endif %}
+                        <div class="mapping_method_fields form-group">
+                            <label for="mapping_permutations" class="col-xs-3 control-label">Permutations</label>
+                            <div class="col-xs-4 controls">
+                                <input name="num_perm_rqtl_pair" value="200" type="text" class="form-control">
+                            </div>
+                        </div>
+                        {% if sample_groups[0].attributes|length > 0 %}
+                        <div class="mapping_method_fields form-group">
+                            <label class="col-xs-3 control-label">Stratified</label>
+                            <div class="col-xs-6 controls">
+                                <label class="radio-inline">
+                                    <input type="radio" name="perm_strata" value="True" checked=""> 
+                                    Yes
+                                </label>
+                                <label class="radio-inline">
+                                    <input type="radio" name="perm_strata" value="False" >
+                                    No
+                               </label>
+                            </div>
+                        </div>
+                        {% endif %}
+                        <div class="mapping_method_fields form-group">
+                            <label for="control_for" class="col-xs-3 control-label">Control&nbsp;for</label>
+                            <div class="col-xs-6 controls">
+                              <input name="control_rqtl_pair" value="{% if dataset.type == 'ProbeSet' and this_trait.locus_chr != '' %}{{ nearest_marker }}{% endif %}" type="text" class="form-control cofactor-input" />
+                              <label class="radio-inline">
+                                  <input type="radio" name="do_control_rqtl" value="true">
+                                  Yes
+                              </label>
+                              <label class="radio-inline">
+                                  <input type="radio" name="do_control_rqtl" value="false" checked="">
+                                  No
+                              </label>
+                            </div>
+                        </div>
+                        <div class="mapping_method_fields form-group">
+                            <label for="mapmodel_rqtl_pair" class="col-xs-3 control-label">Model</label>
+                            <div class="col-xs-4 controls">
+                              <select id="mapmodel_rqtl_pair" name="mapmodel_rqtl_pair" class="form-control">
+                                <option value="normal">Normal</option>
+                                {% if binary == "true" %}<option value="binary">Binary</option>{% endif %}
+                                <!--<option value="2part">2-part</option>-->
+                                <option value="np">Non-parametric</option>
+                              </select>
+                            </div>
+                        </div>
+                        <div class="mapping_method_fields form-group">
+                            <label for="mapmethod_rqtl_pair" class="col-xs-3 control-label">Method</label>
+                            <div class="col-xs-6 controls">
+                              <select id="mapmethod_rqtl_pair" name="mapmethod_rqtl_pair" class="form-control">
+                                <option value="hk" selected>Haley-Knott</option>
+                                <option value="ehk">Extended Haley-Knott</option>
+                                <option value="mr">Marker Regression</option>
+                                <option value="em">Expectation-Maximization</option>
+                                <option value="imp">Imputation</option>
+                              </select>
+                            </div>
+                        </div>
+                        <div id="missing_geno_pair_div" class="mapping_method_fields form-group" style="display: none;">
+                            <label for="missing_genotypes_pair" class="col-xs-3 control-label"></label>
+                            <div class="col-xs-6 controls">
+                              <select id="missing_genotype_pair" name="missing_genotypes" class="form-control">
+                                <option value="mr">Remove Samples w/o Genotypes</option>
+                                <option value="mr-imp">Single Imputation</option>
+                                <option value="mr-argmax">Imputation w/ Viterbi Algorithm</option>
+                              </select>
+                            </div>
+                        </div>
+                        <div class="mapping_method_fields form-group">
+                          <label class="col-xs-3 control-label">Covariates<br><span class="covar-text">Select covariate(s) from a collection</span></label>
+                          <div class="col-xs-8 covar-options">
+                            {% if g.user_session.num_collections < 1 %}
+                            No collections available. Please add traits to a collection to use them as covariates.
+                            {% else %}
+                            <div class="select-covar-div">
                               <button type="button" class="btn btn-default select-covar-button select_covariates">Select</button>
                               <button type="button" class="btn btn-default select-covar-button remove_covariates">Remove</button>
                             </div>
@@ -330,7 +451,7 @@
                         <div class="mapping_method_fields form-group">
                             <label class="col-xs-3 control-label"></label>
                             <div class="col-xs-6 controls">
-                              <button id="rqtl_geno_compute" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Marker Regression" value="Compute">Compute</button>
+                              <button id="rqtl_pair_compute" type="button" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Pair Scan" value="Compute">Compute</button>
                             </div>
                         </div>
                     </div>
diff --git a/wqflask/wqflask/templates/show_trait_transform_and_filter.html b/wqflask/wqflask/templates/show_trait_transform_and_filter.html
index 20f78b48..5e6ed2cf 100644
--- a/wqflask/wqflask/templates/show_trait_transform_and_filter.html
+++ b/wqflask/wqflask/templates/show_trait_transform_and_filter.html
@@ -25,7 +25,7 @@
         <label for="exclude_column">Block samples by group:</label>
         <select id="exclude_column" size=1>
           {% for attribute in sample_groups[0].attributes %}
-          {% if sample_groups[0].attributes[attribute].distinct_values|length <= 10 %}
+          {% if sample_groups[0].attributes[attribute].distinct_values|length <= 10 and sample_groups[0].attributes[attribute].distinct_values|length > 1 %}
           <option value="{{ loop.index }}">
               {{ sample_groups[0].attributes[attribute].name }}
           </option>
@@ -45,6 +45,27 @@
         <input type="button" id="exclude_by_attr" class="btn btn-danger" value="Block">
     </div>
     {% endif %}
+    {% if study_samplelists|length > 0 %}
+    <div id="filterMenuSpan" class="input-append block-div-2">
+      <label for="filter_study_select">Filter samples by study: </label>
+      <select id="filter_study">
+        {% for study in study_samplelists %}
+        <option value="{{ loop.index - 1 }}">{{ study }}</option>
+        {% endfor %}
+      </select>
+      {% if sample_groups|length != 1 %}
+      <select id="filter_study_group" size="1">
+        <option value="primary">
+          {{ sample_group_types['samples_primary'] }}
+        </option>
+        <option value="other">
+          {{ sample_group_types['samples_other'] }}
+        </option>
+      </select>
+      {% endif %}
+      <input type="button" id="filter_by_study" class="btn btn-danger" value="Filter">
+    </div>
+    {% endif %}
     <div id="filterMenuSpan" class="input-append block-div-2">
       <label for="filter_samples_field">Filter samples by {% if (numerical_var_list|length == 0) and (not js_data.se_exists) %}value{% endif %} </label>
       {% if (numerical_var_list|length > 0) or js_data.se_exists %}
@@ -53,10 +74,12 @@
         {% if js_data.se_exists %}
         <option value="stderr">SE</option>
         {% endif %}
-        {% for attribute in numerical_var_list %}
+        {% for attribute in sample_groups[0].attributes %}
+        {% if sample_groups[0].attributes[attribute].name in numerical_var_list %}
         <option value="{{ loop.index }}">
-          {{ attribute }}
+          {{ sample_groups[0].attributes[attribute].name }}
         </option>
+        {% endif %}
         {% endfor %}
       </select>
       {% endif %}
diff --git a/wqflask/wqflask/templates/test_correlation_page.html b/wqflask/wqflask/templates/test_correlation_page.html
index 0809b65e..991773a2 100644
--- a/wqflask/wqflask/templates/test_correlation_page.html
+++ b/wqflask/wqflask/templates/test_correlation_page.html
@@ -113,7 +113,7 @@ console.log(correlationResults)
             {"data":corr_type=="sample"?null:"fd","width":"25px"},
             { "data": "index","width":"120px","title":"Index" },
             { "data": "trait_name","title":"TraitName"},
-            { "data": "corr_coeffient","defaultContent": "--"},
+            { "data": "corr_coefficient","defaultContent": "--"},
             { "data": "p_value","defaultContent":"--"},
             { "data": "num_overlap","defaultContent":"--"},
             {"data":"tissue_corr","defaultContent":"--","title":"Tissue r"},
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index 731ca291..000d71d9 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -27,6 +27,8 @@ from zipfile import ZIP_DEFLATED
 
 from wqflask import app
 
+from gn3.commands import run_cmd
+from gn3.computations.gemma import generate_hash_of_string
 from gn3.db import diff_from_dict
 from gn3.db import fetchall
 from gn3.db import fetchone
@@ -38,10 +40,13 @@ from gn3.db.phenotypes import Probeset
 from gn3.db.phenotypes import Publication
 from gn3.db.phenotypes import PublishXRef
 from gn3.db.phenotypes import probeset_mapping
+from gn3.db.traits import get_trait_csv_sample_data
+from gn3.db.traits import update_sample_data
 
 
 from flask import current_app
 from flask import g
+from flask import flash
 from flask import Response
 from flask import request
 from flask import make_response
@@ -59,6 +64,7 @@ from wqflask import server_side
 from base.data_set import create_dataset  # Used by YAML in marker_regression
 from wqflask.show_trait import show_trait
 from wqflask.show_trait import export_trait_data
+from wqflask.show_trait.show_trait import get_diff_of_vals
 from wqflask.heatmap import heatmap
 from wqflask.external_tools import send_to_bnw
 from wqflask.external_tools import send_to_webgestalt
@@ -302,6 +308,7 @@ def gsearchact():
     elif type == "phenotype":
         return render_template("gsearch_pheno.html", **result)
 
+
 @app.route("/gsearch_table", methods=('GET',))
 def gsearchtable():
     logger.info(request.url)
@@ -316,6 +323,7 @@ def gsearchtable():
 
     return flask.jsonify(current_page)
 
+
 @app.route("/gsearch_updating", methods=('POST',))
 def gsearch_updating():
     logger.info("REQUEST ARGS:", request.values)
@@ -359,20 +367,6 @@ def wcgna_setup():
     return render_template("wgcna_setup.html", **request.form)
 
 
-# @app.route("/wgcna_results", methods=('POST',))
-# def wcgna_results():
-#     logger.info("In wgcna, request.form is:", request.form)
-#     logger.info(request.url)
-#     # Start R, load the package and pointers and create the analysis
-#     wgcna = wgcna_analysis.WGCNA()
-#     # Start the analysis, a wgcnaA object should be a separate long running thread
-#     wgcnaA = wgcna.run_analysis(request.form)
-#     # After the analysis is finished store the result
-#     result = wgcna.process_results(wgcnaA)
-#     # Display them using the template
-#     return render_template("wgcna_results.html", **result)
-
-
 @app.route("/ctl_setup", methods=('POST',))
 def ctl_setup():
     # We are going to get additional user input for the analysis
@@ -382,20 +376,6 @@ def ctl_setup():
     return render_template("ctl_setup.html", **request.form)
 
 
-# @app.route("/ctl_results", methods=('POST',))
-# def ctl_results():
-#     logger.info("In ctl, request.form is:", request.form)
-#     logger.info(request.url)
-#     # Start R, load the package and pointers and create the analysis
-#     ctl = ctl_analysis.CTL()
-#     # Start the analysis, a ctlA object should be a separate long running thread
-#     ctlA = ctl.run_analysis(request.form)
-#     # After the analysis is finished store the result
-#     result = ctl.process_results(ctlA)
-#     # Display them using the template
-#     return render_template("ctl_results.html", **result)
-
-
 @app.route("/intro")
 def intro():
     doc = Docs("intro", request.args)
@@ -430,9 +410,9 @@ def submit_trait_form():
         version=GN_VERSION)
 
 
-@app.route("/trait/<name>/edit/inbredset-id/<inbred_set_id>")
+@app.route("/trait/<name>/edit/inbredset-id/<inbredset_id>")
 @admin_login_required
-def edit_phenotype(name, inbred_set_id):
+def edit_phenotype(name, inbredset_id):
     conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
                            user=current_app.config.get("DB_USER"),
                            passwd=current_app.config.get("DB_PASS"),
@@ -441,7 +421,7 @@ def edit_phenotype(name, inbred_set_id):
         conn=conn,
         table="PublishXRef",
         where=PublishXRef(id_=name,
-                          inbred_set_id=inbred_set_id))
+                          inbred_set_id=inbredset_id))
     phenotype_ = fetchone(
         conn=conn,
         table="Phenotype",
@@ -488,7 +468,7 @@ def edit_phenotype(name, inbred_set_id):
 
 
 @app.route("/trait/edit/probeset-name/<dataset_name>")
-# @admin_login_required
+@admin_login_required
 def edit_probeset(dataset_name):
     conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
                            user=current_app.config.get("DB_USER"),
@@ -538,6 +518,68 @@ def update_phenotype():
                            passwd=current_app.config.get("DB_PASS"),
                            host=current_app.config.get("DB_HOST"))
     data_ = request.form.to_dict()
+    TMPDIR = current_app.config.get("TMPDIR")
+    author = g.user_session.record.get(b'user_name')
+    if 'file' not in request.files:
+        flash("No sample-data has been uploaded", "warning")
+    else:
+        file_ = request.files['file']
+        trait_name = str(data_.get('dataset-name'))
+        phenotype_id = str(data_.get('phenotype-id', 35))
+        SAMPLE_DATADIR = os.path.join(TMPDIR, "sample-data")
+        if not os.path.exists(SAMPLE_DATADIR):
+            os.makedirs(SAMPLE_DATADIR)
+        if not os.path.exists(os.path.join(SAMPLE_DATADIR,
+                                           "diffs")):
+            os.makedirs(os.path.join(SAMPLE_DATADIR,
+                                     "diffs"))
+        if not os.path.exists(os.path.join(SAMPLE_DATADIR,
+                                           "updated")):
+            os.makedirs(os.path.join(SAMPLE_DATADIR,
+                                     "updated"))
+        current_time = str(datetime.datetime.now().isoformat())
+        new_file_name = (os.path.join(TMPDIR,
+                                      "sample-data/updated/",
+                                      (f"{author.decode('utf-8')}."
+                                       f"{trait_name}.{phenotype_id}."
+                                       f"{current_time}.csv")))
+        uploaded_file_name = (os.path.join(
+            TMPDIR,
+            "sample-data/updated/",
+            (f"updated.{author.decode('utf-8')}."
+             f"{trait_name}.{phenotype_id}."
+             f"{current_time}.csv")))
+        file_.save(new_file_name)
+        publishdata_id = ""
+        lines = []
+        with open(new_file_name, "r") as f:
+            lines = f.read()
+            first_line = lines.split('\n', 1)[0]
+            publishdata_id = first_line.split("Id:")[-1].strip()
+        with open(new_file_name, "w") as f:
+            f.write(lines.split("\n\n")[-1])
+        csv_ = get_trait_csv_sample_data(conn=conn,
+                                         trait_name=str(trait_name),
+                                         phenotype_id=str(phenotype_id))
+        with open(uploaded_file_name, "w") as f_:
+            f_.write(csv_.split("\n\n")[-1])
+        r = run_cmd(cmd=("csvdiff "
+                         f"'{uploaded_file_name}' '{new_file_name}' "
+                         "--format json"))
+        diff_output = (f"{TMPDIR}/sample-data/diffs/"
+                       f"{trait_name}.{author.decode('utf-8')}."
+                       f"{phenotype_id}.{current_time}.json")
+        with open(diff_output, "w") as f:
+            dict_ = json.loads(r.get("output"))
+            dict_.update({
+                "author": author.decode('utf-8'),
+                "publishdata_id": publishdata_id,
+                "dataset_id": data_.get("dataset-name"),
+                "timestamp": datetime.datetime.now().strftime(
+                    "%Y-%m-%d %H:%M:%S")
+            })
+            f.write(json.dumps(dict_))
+        flash("Sample-data has been successfully uploaded", "success")
     # Run updates:
     phenotype_ = {
         "pre_pub_description": data_.get("pre-pub-desc"),
@@ -579,7 +621,6 @@ def update_phenotype():
         diff_data.update({"Publication": diff_from_dict(old={
             k: data_.get(f"old_{k}") for k, v in publication_.items()
             if v is not None}, new=publication_)})
-    author = g.user_session.record.get(b'user_name')
     if diff_data:
         diff_data.update({"dataset_id": data_.get("dataset-name")})
         diff_data.update({"author": author.decode('utf-8')})
@@ -590,8 +631,9 @@ def update_phenotype():
                data=MetadataAudit(dataset_id=data_.get("dataset-name"),
                                   editor=author.decode("utf-8"),
                                   json_data=json.dumps(diff_data)))
+        flash(f"Diff-data: \n{diff_data}\nhas been uploaded", "success")
     return redirect(f"/trait/{data_.get('dataset-name')}"
-                    f"/edit/inbredset-id/{data_.get('inbred-set-id')}")
+                    f"/edit/phenotype-id/{data_.get('phenotype-id')}")
 
 
 @app.route("/probeset/update", methods=["POST"])
@@ -956,16 +998,16 @@ def loading_page():
             if key in wanted:
                 start_vars[key] = value
 
+        sample_vals_dict = json.loads(start_vars['sample_vals'])
         if 'n_samples' in start_vars:
             n_samples = int(start_vars['n_samples'])
         else:
-            sample_vals_dict = json.loads(start_vars['sample_vals'])
             if 'group' in start_vars:
                 dataset = create_dataset(
                     start_vars['dataset'], group_name=start_vars['group'])
             else:
                 dataset = create_dataset(start_vars['dataset'])
-            samples = start_vars['primary_samples'].split(",")
+            samples = dataset.group.samplelist
             if 'genofile' in start_vars:
                 if start_vars['genofile'] != "":
                     genofile_string = start_vars['genofile']
@@ -981,6 +1023,10 @@ def loading_page():
                         n_samples += 1
 
         start_vars['n_samples'] = n_samples
+        start_vars['vals_hash'] = generate_hash_of_string(str(sample_vals_dict))
+        if start_vars['dataset'] != "Temp": # Currently can't get diff for temp traits
+            start_vars['vals_diff'] = get_diff_of_vals(sample_vals_dict, str(start_vars['trait_id'] + ":" + str(start_vars['dataset'])))
+
         start_vars['wanted_inputs'] = initial_start_vars['wanted_inputs']
 
         start_vars_container['start_vars'] = start_vars
@@ -1021,7 +1067,6 @@ def mapping_results_page():
         'num_perm',
         'permCheck',
         'perm_strata',
-        'strat_var',
         'categorical_vars',
         'perm_output',
         'num_bootstrap',
@@ -1048,8 +1093,8 @@ def mapping_results_page():
         'showGenes',
         'viewLegend',
         'haplotypeAnalystCheck',
-        'mapmethod_rqtl_geno',
-        'mapmodel_rqtl_geno',
+        'mapmethod_rqtl',
+        'mapmodel_rqtl',
         'temp_trait',
         'reaper_version',
         'n_samples',
@@ -1087,32 +1132,23 @@ def mapping_results_page():
                 rendered_template = render_template("mapping_error.html")
                 return rendered_template
 
-            template_vars.js_data = json.dumps(template_vars.js_data,
-                                               default=json_default_handler,
-                                               indent="   ")
+            if not template_vars.pair_scan:
+                template_vars.js_data = json.dumps(template_vars.js_data,
+                                                default=json_default_handler,
+                                                indent="   ")
 
             result = template_vars.__dict__
 
             if result['pair_scan']:
                 with Bench("Rendering template"):
-                    img_path = result['pair_scan_filename']
-                    logger.info("img_path:", img_path)
-                    initial_start_vars = request.form
-                    logger.info("initial_start_vars:", initial_start_vars)
-                    imgfile = open(TEMPDIR + img_path, 'rb')
-                    imgdata = imgfile.read()
-                    imgB64 = base64.b64encode(imgdata)
-                    bytesarray = array.array('B', imgB64)
-                    result['pair_scan_array'] = bytesarray
                     rendered_template = render_template(
                         "pair_scan_results.html", **result)
             else:
                 gn1_template_vars = display_mapping_results.DisplayMappingResults(
                     result).__dict__
 
-                with Bench("Rendering template"):
-                    rendered_template = render_template(
-                        "mapping_results.html", **gn1_template_vars)
+                rendered_template = render_template(
+                    "mapping_results.html", **gn1_template_vars)
 
     return rendered_template
 
@@ -1193,9 +1229,10 @@ def corr_compute_page():
 
 @app.route("/test_corr_compute", methods=["POST"])
 def test_corr_compute_page():
-    correlation_data = compute_correlation(request.form)
+    correlation_data = compute_correlation(request.form, compute_all=True)
     return render_template("test_correlation_page.html", **correlation_data)
-    
+
+
 @app.route("/corr_matrix", methods=('POST',))
 def corr_matrix_page():
     logger.info("In corr_matrix, request.form is:", pf(request.form))
@@ -1293,8 +1330,6 @@ def browser_inputs():
 
     return flask.jsonify(file_contents)
 
-##########################################################################
-
 
 def json_default_handler(obj):
     """Based on http://stackoverflow.com/a/2680060/1175849"""
@@ -1310,3 +1345,112 @@ def json_default_handler(obj):
     else:
         raise TypeError('Object of type %s with value of %s is not JSON serializable' % (
             type(obj), repr(obj)))
+
+
+@app.route("/trait/<trait_name>/sampledata/<phenotype_id>")
+def get_sample_data_as_csv(trait_name: int, phenotype_id: int):
+    conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
+                           user=current_app.config.get("DB_USER"),
+                           passwd=current_app.config.get("DB_PASS"),
+                           host=current_app.config.get("DB_HOST"))
+    csv_ = get_trait_csv_sample_data(conn, str(trait_name),
+                                     str(phenotype_id))
+    return Response(
+        csv_,
+        mimetype="text/csv",
+        headers={"Content-disposition":
+                 "attachment; filename=myplot.csv"}
+    )
+
+
+@app.route("/admin/data-sample/diffs/")
+@admin_login_required
+def display_diffs_admin():
+    TMPDIR = current_app.config.get("TMPDIR")
+    DIFF_DIR = f"{TMPDIR}/sample-data/diffs"
+    files = []
+    if os.path.exists(DIFF_DIR):
+        files = os.listdir(DIFF_DIR)
+        files = filter(lambda x: not(x.endswith((".approved", ".rejected"))),
+                       files)
+    return render_template("display_files_admin.html",
+                           files=files)
+
+
+@app.route("/user/data-sample/diffs/")
+def display_diffs_users():
+    TMPDIR = current_app.config.get("TMPDIR")
+    DIFF_DIR = f"{TMPDIR}/sample-data/diffs"
+    files = []
+    author = g.user_session.record.get(b'user_name').decode("utf-8")
+    if os.path.exists(DIFF_DIR):
+        files = os.listdir(DIFF_DIR)
+        files = filter(lambda x: not(x.endswith((".approved", ".rejected"))) \
+                       and author in x,
+                       files)
+    return render_template("display_files_user.html",
+                           files=files)
+
+
+@app.route("/data-samples/approve/<name>")
+def approve_data(name):
+    sample_data = {}
+    conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
+                           user=current_app.config.get("DB_USER"),
+                           passwd=current_app.config.get("DB_PASS"),
+                           host=current_app.config.get("DB_HOST"))
+    TMPDIR = current_app.config.get("TMPDIR")
+    with open(os.path.join(f"{TMPDIR}/sample-data/diffs",
+                           name), 'r') as myfile:
+        sample_data = json.load(myfile)
+    PUBLISH_ID = sample_data.get("publishdata_id")
+    modifications = [d for d in sample_data.get("Modifications")]
+    row_counts = len(modifications)
+    for modification in modifications:
+        if modification.get("Current"):
+            (strain_id,
+             strain_name,
+             value, se, count) = modification.get("Current").split(",")
+            update_sample_data(
+                conn=conn,
+                strain_name=strain_name,
+                strain_id=int(strain_id),
+                publish_data_id=int(PUBLISH_ID),
+                value=value,
+                error=se,
+                count=count
+            )
+            insert(conn,
+                   table="metadata_audit",
+                   data=MetadataAudit(
+                       dataset_id=name.split(".")[0],  # use the dataset name
+                       editor=sample_data.get("author"),
+                       json_data=json.dumps(sample_data)))
+    if modifications:
+        # Once data is approved, rename it!
+        os.rename(os.path.join(f"{TMPDIR}/sample-data/diffs", name),
+                  os.path.join(f"{TMPDIR}/sample-data/diffs",
+                               f"{name}.approved"))
+        flash((f"Just updated data from: {name}; {row_counts} "
+               "row(s) modified!"),
+              "success")
+    return redirect("/admin/data-sample/diffs/")
+
+
+@app.route("/data-samples/reject/<name>")
+def reject_data(name):
+    TMPDIR = current_app.config.get("TMPDIR")
+    os.rename(os.path.join(f"{TMPDIR}/sample-data/diffs", name),
+              os.path.join(f"{TMPDIR}/sample-data/diffs",
+                           f"{name}.rejected"))
+    flash(f"{name} has been rejected!", "success")
+    return redirect("/admin/data-sample/diffs/")
+
+
+@app.route("/display-file/<name>")
+def display_file(name):
+    TMPDIR = current_app.config.get("TMPDIR")
+    with open(os.path.join(f"{TMPDIR}/sample-data/diffs",
+                           name), 'r') as myfile:
+        content = myfile.read()
+    return Response(content, mimetype='text/json')