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/tests/unit/wqflask/marker_regression/test_gemma_mapping.py12
-rw-r--r--wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py43
-rw-r--r--wqflask/utility/Plot.py4
-rw-r--r--wqflask/utility/redis_tools.py20
-rw-r--r--wqflask/utility/startup_config.py6
-rw-r--r--wqflask/wqflask/correlation/correlation_gn3_api.py9
-rw-r--r--wqflask/wqflask/decorators.py28
-rw-r--r--wqflask/wqflask/marker_regression/display_mapping_results.py83
-rw-r--r--wqflask/wqflask/marker_regression/qtlreaper_mapping.py95
-rw-r--r--wqflask/wqflask/marker_regression/rqtl_mapping.py7
-rw-r--r--wqflask/wqflask/marker_regression/run_mapping.py100
-rw-r--r--wqflask/wqflask/resource_manager.py2
-rw-r--r--wqflask/wqflask/show_trait/SampleList.py51
-rw-r--r--wqflask/wqflask/show_trait/show_trait.py60
-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.js8
-rw-r--r--wqflask/wqflask/static/new/javascript/show_trait.js95
-rw-r--r--wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js10
-rw-r--r--wqflask/wqflask/templates/admin/group_manager.html2
-rw-r--r--wqflask/wqflask/templates/base.html1
-rw-r--r--wqflask/wqflask/templates/collections/add.html29
-rw-r--r--wqflask/wqflask/templates/collections/view.html2
-rw-r--r--wqflask/wqflask/templates/loading.html51
-rw-r--r--wqflask/wqflask/templates/mapping_results.html19
-rw-r--r--wqflask/wqflask/templates/new_security/_scripts.html1
-rw-r--r--wqflask/wqflask/templates/new_security/forgot_password.html1
-rw-r--r--wqflask/wqflask/templates/new_security/forgot_password_step2.html1
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html26
-rw-r--r--wqflask/wqflask/templates/new_security/password_reset.html1
-rw-r--r--wqflask/wqflask/templates/new_security/register_user.html1
-rw-r--r--wqflask/wqflask/templates/new_security/registered.html1
-rw-r--r--wqflask/wqflask/templates/new_security/thank_you.html1
-rw-r--r--wqflask/wqflask/templates/new_security/verification_still_needed.html1
-rw-r--r--wqflask/wqflask/templates/search_result_page.html8
-rw-r--r--wqflask/wqflask/templates/show_trait.html2
-rw-r--r--wqflask/wqflask/templates/show_trait_details.html5
-rwxr-xr-xwqflask/wqflask/templates/show_trait_mapping_tools.html55
-rw-r--r--wqflask/wqflask/templates/show_trait_transform_and_filter.html29
-rw-r--r--wqflask/wqflask/views.py83
44 files changed, 623 insertions, 454 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/tests/unit/wqflask/marker_regression/test_gemma_mapping.py b/wqflask/tests/unit/wqflask/marker_regression/test_gemma_mapping.py
index 4003d68f..58a44b2a 100644
--- a/wqflask/tests/unit/wqflask/marker_regression/test_gemma_mapping.py
+++ b/wqflask/tests/unit/wqflask/marker_regression/test_gemma_mapping.py
@@ -81,10 +81,12 @@ class TestGemmaMapping(unittest.TestCase):
     def test_gen_pheno_txt_file(self):
         """add tests for generating pheno txt file"""
         with mock.patch("builtins.open", mock.mock_open())as mock_open:
-            gen_pheno_txt_file(this_dataset={}, genofile_name="", vals=[
-                               "x", "w", "q", "we", "R"], trait_filename="fitr.re")
+            gen_pheno_txt_file(
+                this_dataset=AttributeSetter({"name": "A"}),
+                genofile_name="", vals=[
+                    "x", "w", "q", "we", "R"])
             mock_open.assert_called_once_with(
-                '/home/user/data/gn2/fitr.re.txt', 'w')
+                '/home/user/data/gn2/PHENO_KiAEKlCvM6iGTM9Kh_TAlQ.txt', 'w')
             filehandler = mock_open()
             values = ["x", "w", "q", "we", "R"]
             write_calls = [mock.call('NA\n'), mock.call('w\n'), mock.call(
@@ -112,7 +114,7 @@ class TestGemmaMapping(unittest.TestCase):
         create_trait.side_effect = create_trait_side_effect
 
         group = MockGroup({"name": "group_X", "samplelist": samplelist})
-        this_dataset = AttributeSetter({"group": group})
+        this_dataset = AttributeSetter({"group": group, "name": "A"})
         flat_files.return_value = "Home/Genenetwork"
 
         with mock.patch("builtins.open", mock.mock_open())as mock_open:
@@ -132,7 +134,7 @@ class TestGemmaMapping(unittest.TestCase):
 
             flat_files.assert_called_once_with('mapping')
             mock_open.assert_called_once_with(
-                'Home/Genenetwork/group_X_covariates.txt', 'w')
+                'Home/Genenetwork/COVAR_anFZ_LfZYV0Ulywo+7tRCw.txt', 'w')
             filehandler = mock_open()
             filehandler.write.assert_has_calls([mock.call(
                 '-9\t'), mock.call('-9\t'), mock.call('-9\t'), mock.call('-9\t'), mock.call('\n')])
diff --git a/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py b/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py
index c220a072..868b0b0b 100644
--- a/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py
+++ b/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py
@@ -43,11 +43,11 @@ class TestRunMapping(unittest.TestCase):
             })
         }
         self.dataset = AttributeSetter(
-            {"fullname": "dataser_1", "group": self.group, "type": "ProbeSet"})
+            {"fullname": "dataset_1", "group": self.group, "type": "ProbeSet"})
 
         self.chromosomes = AttributeSetter({"chromosomes": chromosomes})
         self.trait = AttributeSetter(
-            {"symbol": "IGFI", "chr": "X1", "mb": 123313})
+            {"symbol": "IGFI", "chr": "X1", "mb": 123313, "display_name": "Test Name"})
 
     def tearDown(self):
         self.dataset = AttributeSetter(
@@ -180,34 +180,36 @@ class TestRunMapping(unittest.TestCase):
 
             with mock.patch("wqflask.marker_regression.run_mapping.datetime.datetime", new=datetime_mock):
                 export_mapping_results(dataset=self.dataset, trait=self.trait, markers=markers,
-                                       results_path="~/results", mapping_scale="physic", score_type="-log(p)",
-                                       transform="qnorm", covariates="Dataset1:Trait1,Dataset2:Trait2", n_samples="100")
+                                       results_path="~/results", mapping_method="gemma", mapping_scale="physic",
+                                       score_type="-logP", transform="qnorm",
+                                       covariates="Dataset1:Trait1,Dataset2:Trait2",
+                                       n_samples="100", vals_hash="")
 
                 write_calls = [
                     mock.call('Time/Date: 09/01/19 / 10:12:12\n'),
                     mock.call('Population: Human GP1_\n'), mock.call(
-                        'Data Set: dataser_1\n'),
-                    mock.call('N Samples: 100\n'), mock.call(
-                        'Transform - Quantile Normalized\n'),
+                        'Data Set: dataset_1\n'),
+                    mock.call('Trait: Test Name\n'),
+                    mock.call('Trait Hash: \n'),
+                    mock.call('N Samples: 100\n'),
+                    mock.call('Mapping Tool: gemma\n'),
+                    mock.call('Transform - Quantile Normalized\n'),
                     mock.call('Gene Symbol: IGFI\n'), mock.call(
                         'Location: X1 @ 123313 Mb\n'),
                     mock.call('Cofactors (dataset - trait):\n'),
                     mock.call('Trait1 - Dataset1\n'),
                     mock.call('Trait2 - Dataset2\n'),
                     mock.call('\n'), mock.call('Name,Chr,'),
-                    mock.call('Mb,-log(p)'), mock.call('Cm,-log(p)'),
+                    mock.call('Mb,-logP'),
                     mock.call(',Additive'), mock.call(',Dominance'),
                     mock.call('\n'), mock.call('MK1,C1,'),
-                    mock.call('12000,'), mock.call('1,'),
-                    mock.call('3'), mock.call(',VA'),
-                    mock.call(',TT'), mock.call('\n'),
-                    mock.call('MK2,C2,'), mock.call('10000,'),
-                    mock.call('15,'), mock.call('7'),
+                    mock.call('12000,'), mock.call('3'),
+                    mock.call(',VA'), mock.call(',TT'),
+                    mock.call('\n'), mock.call('MK2,C2,'),
+                    mock.call('10000,'), mock.call('7'),
                     mock.call('\n'), mock.call('MK1,C3,'),
-                    mock.call('1,'), mock.call('45,'),
-                    mock.call('7'), mock.call(',VE'),
-                    mock.call(',Tt')
-
+                    mock.call('1,'), mock.call('7'),
+                    mock.call(',VE'), mock.call(',Tt')
                 ]
                 mock_open.assert_called_once_with("~/results", "w+")
                 filehandler = mock_open()
@@ -232,25 +234,20 @@ class TestRunMapping(unittest.TestCase):
                 "c1": "c1_value",
                 "c2": "c2_value",
                 "w1": "w1_value"
-
             },
             "S2": {
                 "w1": "w2_value",
                 "w2": "w2_value"
-
             },
             "S3": {
 
                 "c1": "c1_value",
                 "c2": "c2_value"
-
             },
-
         }})
-
         results = get_perm_strata(this_trait={}, sample_list=sample_list,
                                   categorical_vars=categorical_vars, used_samples=used_samples)
-        self.assertEqual(results, [2, 1])
+        self.assertEqual(results, [1, 1])
 
     def test_get_chr_length(self):
         """test for getting chromosome length"""
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/redis_tools.py b/wqflask/utility/redis_tools.py
index ff125bd2..de9dde46 100644
--- a/wqflask/utility/redis_tools.py
+++ b/wqflask/utility/redis_tools.py
@@ -127,22 +127,20 @@ def check_verification_code(code):
 
 
 def get_user_groups(user_id):
-    # ZS: Get the groups where a user is an admin or a member and
+    # Get the groups where a user is an admin or a member and
     # return lists corresponding to those two sets of groups
-    admin_group_ids = []  # ZS: Group IDs where user is an admin
-    user_group_ids = []  # ZS: Group IDs where user is a regular user
+    admin_group_ids = []  # Group IDs where user is an admin
+    user_group_ids = []  # Group IDs where user is a regular user
     groups_list = Redis.hgetall("groups")
-    for key in groups_list:
+    for group_id, group_details in groups_list.items():
         try:
-            group_ob = json.loads(groups_list[key])
-            group_admins = set([this_admin.encode(
-                'utf-8') if this_admin else None for this_admin in group_ob['admins']])
-            group_members = set([this_member.encode(
-                'utf-8') if this_member else None for this_member in group_ob['members']])
+            _details = json.loads(group_details)
+            group_admins = set([this_admin if this_admin else None for this_admin in _details['admins']])
+            group_members = set([this_member if this_member else None for this_member in _details['members']])
             if user_id in group_admins:
-                admin_group_ids.append(group_ob['id'])
+                admin_group_ids.append(group_id)
             elif user_id in group_members:
-                user_group_ids.append(group_ob['id'])
+                user_group_ids.append(group_id)
             else:
                 continue
         except:
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 f7c06a46..a18bceaf 100644
--- a/wqflask/wqflask/correlation/correlation_gn3_api.py
+++ b/wqflask/wqflask/correlation/correlation_gn3_api.py
@@ -148,14 +148,9 @@ 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)
+        start_vars, this_dataset.group.all_samples_ordered())
 
-    sample_data = test_process_data(this_trait, this_dataset, start_vars)
-
-    if target_dataset.type == "ProbeSet":
-        target_dataset.get_probeset_data(list(sample_data.keys()))
-    else:
-        target_dataset.get_trait_data(list(sample_data.keys()))
+    target_dataset.get_trait_data(list(sample_data.keys()))
     this_trait = retrieve_sample_data(this_trait, this_dataset)
     this_trait_data = {
         "trait_sample_data": sample_data,
diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py
index f0978fd3..54aa6795 100644
--- a/wqflask/wqflask/decorators.py
+++ b/wqflask/wqflask/decorators.py
@@ -1,14 +1,36 @@
 """This module contains gn2 decorators"""
 from flask import g
+from typing import Dict
 from functools import wraps
+from utility.hmac import hmac_creation
 
+import json
+import requests
 
-def admin_login_required(f):
+
+def edit_access_required(f):
     """Use this for endpoints where admins are required"""
     @wraps(f)
     def wrap(*args, **kwargs):
-        if g.user_session.record.get(b"user_email_address") not in [
-                b"labwilliams@gmail.com"]:
+        resource_id: str = ""
+        if kwargs.get("inbredset_id"):  # data type: dataset-publish
+            resource_id = hmac_creation("dataset-publish:"
+                                        f"{kwargs.get('inbredset_id')}:"
+                                        f"{kwargs.get('name')}")
+        if kwargs.get("dataset_name"):  # data type: dataset-probe
+            resource_id = hmac_creation("dataset-probeset:"
+                                        f"{kwargs.get('dataset_name')}")
+        response: Dict = {}
+        try:
+            _user_id = g.user_session.record.get(b"user_id",
+                                                 "").decode("utf-8")
+            response = json.loads(
+                requests.get("http://localhost:8080/"
+                             "available?resource="
+                             f"{resource_id}&user={_user_id}").content)
+        except:
+            response = {}
+        if "edit" not in response.get("data", []):
             return "You need to be admin", 401
         return f(*args, **kwargs)
     return wrap
diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py
index f941267e..6254b9b9 100644
--- a/wqflask/wqflask/marker_regression/display_mapping_results.py
+++ b/wqflask/wqflask/marker_regression/display_mapping_results.py
@@ -24,6 +24,7 @@
 #
 # Last updated by Zach 12/14/2010
 
+import datetime
 import string
 from math import *
 from PIL import Image
@@ -271,6 +272,7 @@ class DisplayMappingResults:
         # Needing for form submission when doing single chr
         # mapping or remapping after changing options
         self.sample_vals = start_vars['sample_vals']
+        self.vals_hash= start_vars['vals_hash']
         self.sample_vals_dict = json.loads(self.sample_vals)
 
         self.transform = start_vars['transform']
@@ -355,8 +357,7 @@ class DisplayMappingResults:
         if 'use_loco' in list(start_vars.keys()) and self.mapping_method == "gemma":
             self.use_loco = start_vars['use_loco']
 
-        if 'reaper_version' in list(start_vars.keys()) and self.mapping_method == "reaper":
-            self.reaper_version = start_vars['reaper_version']
+        if self.mapping_method == "reaper":
             if 'output_files' in start_vars:
                 self.output_files = ",".join(
                     [(the_file if the_file is not None else "") for the_file in start_vars['output_files']])
@@ -651,7 +652,7 @@ class DisplayMappingResults:
             btminfo.append(
                 'Mapping using genotype data as a trait will result in infinity LRS at one locus. In order to display the result properly, all LRSs higher than 100 are capped at 100.')
 
-    def plotIntMapping(self, canvas, offset=(80, 120, 90, 100), zoom=1, startMb=None, endMb=None, showLocusForm=""):
+    def plotIntMapping(self, canvas, offset=(80, 120, 110, 100), zoom=1, startMb=None, endMb=None, showLocusForm=""):
         im_drawer = ImageDraw.Draw(canvas)
         # calculating margins
         xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset
@@ -661,7 +662,7 @@ class DisplayMappingResults:
             if self.legendChecked:
                 yTopOffset += 10
                 if self.covariates != "" and self.controlLocus and self.doControl != "false":
-                    yTopOffset += 20
+                    yTopOffset += 25
                 if len(self.transform) > 0:
                     yTopOffset += 5
             else:
@@ -1195,43 +1196,47 @@ class DisplayMappingResults:
             dataset_label = "%s - %s" % (self.dataset.group.name,
                                          self.dataset.fullname)
 
-        string1 = 'Dataset: %s' % (dataset_label)
+
+        self.current_datetime = datetime.datetime.now().strftime("%b %d %Y %H:%M:%S")
+        string1 = 'UTC Timestamp: %s' % (self.current_datetime)
+        string2 = 'Dataset: %s' % (dataset_label)
+        string3 = 'Trait Hash: %s' % (self.vals_hash)
 
         if self.genofile_string == "":
-            string2 = 'Genotype File: %s.geno' % self.dataset.group.name
+            string4 = 'Genotype File: %s.geno' % self.dataset.group.name
         else:
-            string2 = 'Genotype File: %s' % self.genofile_string
+            string4 = 'Genotype File: %s' % self.genofile_string.split(":")[1]
 
-        string4 = ''
+        string6 = ''
         if self.mapping_method == "gemma" or self.mapping_method == "gemma_bimbam":
             if self.use_loco == "True":
-                string3 = 'Using GEMMA mapping method with LOCO and '
+                string5 = 'Using GEMMA mapping method with LOCO and '
             else:
-                string3 = 'Using GEMMA mapping method with '
+                string5 = 'Using GEMMA mapping method with '
             if self.covariates != "":
-                string3 += 'the cofactors below:'
+                string5 += 'the cofactors below:'
                 cofactor_names = ", ".join(
                     [covar.split(":")[0] for covar in self.covariates.split(",")])
-                string4 = cofactor_names
+                string6 = cofactor_names
             else:
-                string3 += 'no cofactors'
+                string5 += 'no cofactors'
         elif self.mapping_method == "rqtl_plink" or self.mapping_method == "rqtl_geno":
-            string3 = 'Using R/qtl mapping method with '
+            string5 = 'Using R/qtl mapping method with '
             if self.covariates != "":
-                string3 += 'the cofactors below:'
+                string5 += 'the cofactors below:'
                 cofactor_names = ", ".join(
                     [covar.split(":")[0] for covar in self.covariates.split(",")])
-                string4 = cofactor_names
+                string6 = cofactor_names
             elif self.controlLocus and self.doControl != "false":
-                string3 += '%s as control' % self.controlLocus
+                string5 += '%s as control' % self.controlLocus
             else:
-                string3 += 'no cofactors'
+                string5 += 'no cofactors'
         else:
-            string3 = 'Using Haldane mapping function with '
+            string5 = 'Using Haldane mapping function with '
             if self.controlLocus and self.doControl != "false":
-                string3 += '%s as control' % self.controlLocus
+                string5 += '%s as control' % self.controlLocus
             else:
-                string3 += 'no control for other QTLs'
+                string5 += 'no control for other QTLs'
 
         y_constant = 10
         if self.this_trait.name:
@@ -1243,24 +1248,26 @@ class DisplayMappingResults:
 
             if self.this_trait.symbol:
                 identification += "Trait: %s - %s" % (
-                    self.this_trait.name, self.this_trait.symbol)
+                    self.this_trait.display_name, self.this_trait.symbol)
             elif self.dataset.type == "Publish":
                 if self.this_trait.post_publication_abbreviation:
                     identification += "Trait: %s - %s" % (
-                        self.this_trait.name, self.this_trait.post_publication_abbreviation)
+                        self.this_trait.display_name, self.this_trait.post_publication_abbreviation)
                 elif self.this_trait.pre_publication_abbreviation:
                     identification += "Trait: %s - %s" % (
-                        self.this_trait.name, self.this_trait.pre_publication_abbreviation)
+                        self.this_trait.display_name, self.this_trait.pre_publication_abbreviation)
                 else:
-                    identification += "Trait: %s" % (self.this_trait.name)
+                    identification += "Trait: %s" % (self.this_trait.display_name)
             else:
-                identification += "Trait: %s" % (self.this_trait.name)
+                identification += "Trait: %s" % (self.this_trait.display_name)
             identification += " with %s samples" % (self.n_samples)
 
             d = 4 + max(
                 im_drawer.textsize(identification, font=labelFont)[0],
                 im_drawer.textsize(string1, font=labelFont)[0],
-                im_drawer.textsize(string2, font=labelFont)[0])
+                im_drawer.textsize(string2, font=labelFont)[0],
+                im_drawer.textsize(string3, font=labelFont)[0],
+                im_drawer.textsize(string4, font=labelFont)[0])
             im_drawer.text(
                 text=identification,
                 xy=(xLeftOffset, y_constant * fontZoom), font=labelFont,
@@ -1269,7 +1276,9 @@ class DisplayMappingResults:
         else:
             d = 4 + max(
                 im_drawer.textsize(string1, font=labelFont)[0],
-                im_drawer.textsize(string2, font=labelFont)[0])
+                im_drawer.textsize(string2, font=labelFont)[0],
+                im_drawer.textsize(string3, font=labelFont)[0],
+                im_drawer.textsize(string4, font=labelFont)[0])
 
         if len(self.transform) > 0:
             transform_text = "Transform - "
@@ -1296,14 +1305,22 @@ class DisplayMappingResults:
             text=string2, xy=(xLeftOffset, y_constant * fontZoom),
             font=labelFont, fill=labelColor)
         y_constant += 15
-        if string3 != '':
+        im_drawer.text(
+            text=string3, xy=(xLeftOffset, y_constant * fontZoom),
+            font=labelFont, fill=labelColor)
+        y_constant += 15
+        im_drawer.text(
+            text=string4, xy=(xLeftOffset, y_constant * fontZoom),
+            font=labelFont, fill=labelColor)
+        y_constant += 15
+        if string4 != '':
             im_drawer.text(
-                text=string3, xy=(xLeftOffset, y_constant * fontZoom),
+                text=string5, xy=(xLeftOffset, y_constant * fontZoom),
                 font=labelFont, fill=labelColor)
             y_constant += 15
-            if string4 != '':
+            if string5 != '':
                 im_drawer.text(
-                    text=string4, xy=(xLeftOffset, y_constant * fontZoom),
+                    text=string6, xy=(xLeftOffset, y_constant * fontZoom),
                     font=labelFont, fill=labelColor)
 
     def drawGeneBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None):
@@ -2113,7 +2130,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/qtlreaper_mapping.py b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py
index 4d6715ba..801674e1 100644
--- a/wqflask/wqflask/marker_regression/qtlreaper_mapping.py
+++ b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py
@@ -178,101 +178,6 @@ def parse_reaper_output(gwa_filename, permu_filename, bootstrap_filename):
     return marker_obs, permu_vals, bootstrap_vals
 
 
-def run_original_reaper(this_trait, dataset, samples_before, trait_vals, json_data, num_perm, bootCheck, num_bootstrap, do_control, control_marker, manhattan_plot):
-    genotype = dataset.group.read_genotype_file(use_reaper=True)
-
-    if manhattan_plot != True:
-        genotype = genotype.addinterval()
-
-    trimmed_samples = []
-    trimmed_values = []
-    for i in range(0, len(samples_before)):
-        try:
-            trimmed_values.append(float(trait_vals[i]))
-            trimmed_samples.append(str(samples_before[i]))
-        except:
-            pass
-
-    perm_output = []
-    bootstrap_results = []
-
-    if num_perm < 100:
-        suggestive = 0
-        significant = 0
-    else:
-        perm_output = genotype.permutation(
-            strains=trimmed_samples, trait=trimmed_values, nperm=num_perm)
-        suggestive = perm_output[int(num_perm * 0.37 - 1)]
-        significant = perm_output[int(num_perm * 0.95 - 1)]
-        # highly_significant = perm_output[int(num_perm*0.99-1)] #ZS: Currently not used, but leaving it here just in case
-
-    json_data['suggestive'] = suggestive
-    json_data['significant'] = significant
-
-    if control_marker != "" and do_control == "true":
-        reaper_results = genotype.regression(strains=trimmed_samples,
-                                             trait=trimmed_values,
-                                             control=str(control_marker))
-        if bootCheck:
-            control_geno = []
-            control_geno2 = []
-            _FIND = 0
-            for _chr in genotype:
-                for _locus in _chr:
-                    if _locus.name == control_marker:
-                        control_geno2 = _locus.genotype
-                        _FIND = 1
-                        break
-                if _FIND:
-                    break
-            if control_geno2:
-                _prgy = list(genotype.prgy)
-                for _strain in trimmed_samples:
-                    _idx = _prgy.index(_strain)
-                    control_geno.append(control_geno2[_idx])
-
-            bootstrap_results = genotype.bootstrap(strains=trimmed_samples,
-                                                   trait=trimmed_values,
-                                                   control=control_geno,
-                                                   nboot=num_bootstrap)
-    else:
-        reaper_results = genotype.regression(strains=trimmed_samples,
-                                             trait=trimmed_values)
-
-        if bootCheck:
-            bootstrap_results = genotype.bootstrap(strains=trimmed_samples,
-                                                   trait=trimmed_values,
-                                                   nboot=num_bootstrap)
-
-    json_data['chr'] = []
-    json_data['pos'] = []
-    json_data['lod.hk'] = []
-    json_data['markernames'] = []
-    # if self.additive:
-    #    self.json_data['additive'] = []
-
-    # Need to convert the QTL objects that qtl reaper returns into a json serializable dictionary
-    qtl_results = []
-    for qtl in reaper_results:
-        reaper_locus = qtl.locus
-        # ZS: Convert chr to int
-        converted_chr = reaper_locus.chr
-        if reaper_locus.chr != "X" and reaper_locus.chr != "X/Y":
-            converted_chr = int(reaper_locus.chr)
-        json_data['chr'].append(converted_chr)
-        json_data['pos'].append(reaper_locus.Mb)
-        json_data['lod.hk'].append(qtl.lrs)
-        json_data['markernames'].append(reaper_locus.name)
-        # if self.additive:
-        #    self.json_data['additive'].append(qtl.additive)
-        locus = {"name": reaper_locus.name, "chr": reaper_locus.chr,
-                 "cM": reaper_locus.cM, "Mb": reaper_locus.Mb}
-        qtl = {"lrs_value": qtl.lrs, "chr": converted_chr, "Mb": reaper_locus.Mb,
-               "cM": reaper_locus.cM, "name": reaper_locus.name, "additive": qtl.additive, "dominance": qtl.dominance}
-        qtl_results.append(qtl)
-    return qtl_results, json_data, perm_output, suggestive, significant, bootstrap_results
-
-
 def natural_sort(marker_list):
     """
     Function to naturally sort numbers + strings, adopted from user Mark Byers here: https://stackoverflow.com/questions/4836710/does-python-have-a-built-in-function-for-string-natural-sort
diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py
index 09afb8d1..1dca1b1b 100644
--- a/wqflask/wqflask/marker_regression/rqtl_mapping.py
+++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py
@@ -39,7 +39,7 @@ def run_rqtl(trait_name, vals, samples, dataset, mapping_scale, model, method, n
     }
 
     if do_control == "true" and control_marker:
-        post_data["control_marker"] = control_marker
+        post_data["control"] = control_marker
 
     if not manhattan_plot:
         post_data["interval"] = True
@@ -61,6 +61,7 @@ def get_hash_of_textio(the_file: TextIO) -> str:
 
     the_file.seek(0)
     hash_of_file = hashlib.md5(the_file.read().encode()).hexdigest()
+    hash_of_file = hash_of_file.replace("/", "_") # Replace / with _ to prevent issue with filenames being translated to directories
 
     return hash_of_file
 
@@ -89,7 +90,7 @@ def write_phenotype_file(trait_name: str,
     for i, sample in enumerate(samples):
         this_row = [sample]
         if vals[i] != "x":
-            this_row.append(vals[i])
+            this_row.append(str(round(float(vals[i]), 3)))
         else:
             this_row.append("NA")
         for cofactor in cofactor_data:
@@ -126,7 +127,7 @@ def cofactors_to_dict(cofactors: str, dataset_ob, samples) -> Dict:
                 sample_data = trait_ob.data
                 for index, sample in enumerate(samples):
                     if sample in sample_data:
-                        sample_value = sample_data[sample].value
+                        sample_value = str(round(float(sample_data[sample].value), 3))
                         cofactor_dict[cofactor_name].append(sample_value)
                     else:
                         cofactor_dict[cofactor_name].append("NA")
diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py
index f601201b..80094057 100644
--- a/wqflask/wqflask/marker_regression/run_mapping.py
+++ b/wqflask/wqflask/marker_regression/run_mapping.py
@@ -75,6 +75,7 @@ class RunMapping:
         self.vals = []
         self.samples = []
         self.sample_vals = start_vars['sample_vals']
+        self.vals_hash = start_vars['vals_hash']
         sample_val_dict = json.loads(self.sample_vals)
         samples = sample_val_dict.keys()
         if (len(genofile_samplelist) != 0):
@@ -103,9 +104,7 @@ class RunMapping:
         if "results_path" in start_vars:
             self.mapping_results_path = start_vars['results_path']
         else:
-            mapping_results_filename = self.dataset.group.name + "_" + \
-                ''.join(random.choice(string.ascii_uppercase + string.digits)
-                        for _ in range(6))
+            mapping_results_filename = "_".join([self.dataset.group.name, self.mapping_method, self.vals_hash]).replace("/", "_")
             self.mapping_results_path = "{}{}.csv".format(
                 webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename)
 
@@ -220,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(
                     ",")
@@ -229,7 +228,7 @@ 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']
@@ -243,10 +242,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.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)
+                                                     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:
@@ -272,47 +271,32 @@ class RunMapping:
                     self.bootCheck = False
                     self.num_bootstrap = 0
 
-            self.reaper_version = start_vars['reaper_version']
-
             self.control_marker = start_vars['control_marker']
             self.do_control = start_vars['do_control']
             logger.info("Running qtlreaper")
 
-            if self.reaper_version == "new":
-                self.first_run = True
-                self.output_files = None
-                # ZS: check if first run so existing result files can be used if it isn't (for example zooming on a chromosome, etc)
-                if 'first_run' in start_vars:
-                    self.first_run = False
-                    if 'output_files' in start_vars:
-                        self.output_files = start_vars['output_files'].split(
-                            ",")
-
-                results, self.perm_output, self.suggestive, self.significant, self.bootstrap_results, self.output_files = qtlreaper_mapping.run_reaper(self.this_trait,
-                                                                                                                                                       self.dataset,
-                                                                                                                                                       self.samples,
-                                                                                                                                                       self.vals,
-                                                                                                                                                       self.json_data,
-                                                                                                                                                       self.num_perm,
-                                                                                                                                                       self.bootCheck,
-                                                                                                                                                       self.num_bootstrap,
-                                                                                                                                                       self.do_control,
-                                                                                                                                                       self.control_marker,
-                                                                                                                                                       self.manhattan_plot,
-                                                                                                                                                       self.first_run,
-                                                                                                                                                       self.output_files)
-            else:
-                results, self.json_data, self.perm_output, self.suggestive, self.significant, self.bootstrap_results = qtlreaper_mapping.run_original_reaper(self.this_trait,
-                                                                                                                                                             self.dataset,
-                                                                                                                                                             self.samples,
-                                                                                                                                                             self.vals,
-                                                                                                                                                             self.json_data,
-                                                                                                                                                             self.num_perm,
-                                                                                                                                                             self.bootCheck,
-                                                                                                                                                             self.num_bootstrap,
-                                                                                                                                                             self.do_control,
-                                                                                                                                                             self.control_marker,
-                                                                                                                                                             self.manhattan_plot)
+            self.first_run = True
+            self.output_files = None
+            # ZS: check if first run so existing result files can be used if it isn't (for example zooming on a chromosome, etc)
+            if 'first_run' in start_vars:
+                self.first_run = False
+                if 'output_files' in start_vars:
+                    self.output_files = start_vars['output_files'].split(
+                        ",")
+
+            results, self.perm_output, self.suggestive, self.significant, self.bootstrap_results, self.output_files = qtlreaper_mapping.run_reaper(self.this_trait,
+                                                                                                                                                    self.dataset,
+                                                                                                                                                    self.samples,
+                                                                                                                                                    self.vals,
+                                                                                                                                                    self.json_data,
+                                                                                                                                                    self.num_perm,
+                                                                                                                                                    self.bootCheck,
+                                                                                                                                                    self.num_bootstrap,
+                                                                                                                                                    self.do_control,
+                                                                                                                                                    self.control_marker,
+                                                                                                                                                    self.manhattan_plot,
+                                                                                                                                                    self.first_run,
+                                                                                                                                                    self.output_files)
         elif self.mapping_method == "plink":
             self.score_type = "-logP"
             self.manhattan_plot = True
@@ -422,7 +406,8 @@ class RunMapping:
 
                 with Bench("Exporting Results"):
                     export_mapping_results(self.dataset, self.this_trait, self.qtl_results, self.mapping_results_path,
-                                           self.mapping_scale, self.score_type, self.transform, self.covariates, self.n_samples)
+                                           self.mapping_method, self.mapping_scale, self.score_type,
+                                           self.transform, self.covariates, self.n_samples, self.vals_hash)
 
                 with Bench("Trimming Markers for Figure"):
                     if len(self.qtl_results) > 30000:
@@ -540,14 +525,21 @@ class RunMapping:
         return trimmed_genotype_data
 
 
-def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, score_type, transform, covariates, n_samples):
+def export_mapping_results(dataset, trait, markers, results_path, mapping_method, mapping_scale, score_type, transform, covariates, n_samples, vals_hash):
+    if mapping_scale == "physic":
+        scale_string = "Mb"
+    else:
+        scale_string = "cM"
     with open(results_path, "w+") as output_file:
         output_file.write(
             "Time/Date: " + datetime.datetime.now().strftime("%x / %X") + "\n")
         output_file.write(
             "Population: " + dataset.group.species.title() + " " + dataset.group.name + "\n")
         output_file.write("Data Set: " + dataset.fullname + "\n")
+        output_file.write("Trait: " + trait.display_name + "\n")
+        output_file.write("Trait Hash: " + vals_hash + "\n")
         output_file.write("N Samples: " + str(n_samples) + "\n")
+        output_file.write("Mapping Tool: " + str(mapping_method) + "\n")
         if len(transform) > 0:
             transform_text = "Transform - "
             if transform == "qnorm":
@@ -577,10 +569,7 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale,
         output_file.write("Name,Chr,")
         if score_type.lower() == "-logP":
             score_type = "-logP"
-        if 'Mb' in markers[0]:
-            output_file.write("Mb," + score_type)
-        if 'cM' in markers[0]:
-            output_file.write("Cm," + score_type)
+        output_file.write(scale_string + "," + score_type)
         if "additive" in list(markers[0].keys()):
             output_file.write(",Additive")
         if "dominance" in list(markers[0].keys()):
@@ -588,11 +577,8 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale,
         output_file.write("\n")
         for i, marker in enumerate(markers):
             output_file.write(marker['name'] + "," + str(marker['chr']) + ",")
-            if 'Mb' in marker:
-                output_file.write(str(marker['Mb']) + ",")
-            if 'cM' in marker:
-                output_file.write(str(marker['cM']) + ",")
-            if "lod_score" in marker.keys():
+            output_file.write(str(marker[scale_string]) + ",")
+            if score_type == "-logP":
                 output_file.write(str(marker['lod_score']))
             else:
                 output_file.write(str(marker['lrs_value']))
@@ -765,9 +751,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/resource_manager.py b/wqflask/wqflask/resource_manager.py
index b28c1b04..c54dd0b3 100644
--- a/wqflask/wqflask/resource_manager.py
+++ b/wqflask/wqflask/resource_manager.py
@@ -8,8 +8,6 @@ from wqflask import app
 from utility.authentication_tools import check_owner_or_admin
 from utility.redis_tools import get_resource_info, get_group_info, get_groups_like_unique_column, get_user_id, get_user_by_unique_column, get_users_like_unique_column, add_access_mask, add_resource, change_resource_owner
 
-from utility.logger import getLogger
-logger = getLogger(__name__)
 
 
 @app.route("/resources/manage", methods=('GET', 'POST'))
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..c4d1ae1c 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
@@ -176,11 +178,11 @@ class ShowTrait:
             self.sample_group_types['samples_primary'] = self.dataset.group.name
         sample_lists = [group.sample_list for group in self.sample_groups]
 
-        categorical_var_list = []
+        self.categorical_var_list = []
         self.numerical_var_list = []
         if not self.temp_trait:
             # ZS: Only using first samplelist, since I think mapping only uses those samples
-            categorical_var_list = get_categorical_variables(
+            self.categorical_var_list = get_categorical_variables(
                 self.this_trait, self.sample_groups[0])
             self.numerical_var_list = get_numerical_variables(
                 self.this_trait, self.sample_groups[0])
@@ -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
@@ -280,10 +284,11 @@ class ShowTrait:
         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:
-            hddn['categorical_vars'] = ",".join(categorical_var_list)
+        if self.categorical_var_list:
+            hddn['categorical_vars'] = ",".join(self.categorical_var_list)
         hddn['manhattan_plot'] = ""
         hddn['control_marker'] = ""
         if not self.temp_trait:
@@ -295,7 +300,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]
 
@@ -318,7 +323,7 @@ class ShowTrait:
                        has_num_cases=self.has_num_cases,
                        attributes=self.sample_groups[0].attributes,
                        categorical_attr_exists=self.categorical_attr_exists,
-                       categorical_vars=",".join(categorical_var_list),
+                       categorical_vars=",".join(self.categorical_var_list),
                        num_values=self.num_values,
                        qnorm_values=self.qnorm_vals,
                        zscore_values=self.z_scores,
@@ -520,6 +525,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 +701,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 +807,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/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..0a060cdc 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])){
@@ -130,6 +130,7 @@ var primary_table = $('#samples_primary').DataTable( {
       $(row).addClass("value_se");
       if (data.outlier) {
         $(row).addClass("outlier");
+        $(row).attr("style", "background-color: orange;");
       }
       $('td', row).eq(1).addClass("column_name-Index")
       $('td', row).eq(2).addClass("column_name-Sample")
@@ -189,6 +190,7 @@ if (js_data.sample_lists.length > 1){
         $(row).addClass("value_se");
         if (data.outlier) {
           $(row).addClass("outlier");
+          $(row).attr("style", "background-color: orange;");
         }
         $('td', row).eq(1).addClass("column_name-Index")
         $('td', row).eq(2).addClass("column_name-Sample")
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 09e9d024..e42fe8c4 100644
--- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js
+++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js
@@ -141,11 +141,11 @@ $('input[name=display_all]').change((function(_this) {
 })(this));
 
 //ZS: This is a list of inputs to be passed to the loading page, since not all inputs on the trait page are relevant to mapping
-var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale', 'sample_vals',
-                          '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']
+var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale',
+                          'sample_vals', 'vals_hash', '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', 'primary_samples']
 
 $(".rqtl-geno-tab, #rqtl_geno_compute").on("click", (function(_this) {
   return function() {
diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html
index c0b99e75..692a7abc 100644
--- a/wqflask/wqflask/templates/admin/group_manager.html
+++ b/wqflask/wqflask/templates/admin/group_manager.html
@@ -81,7 +81,7 @@
                         <tr>
                             <td><input type="checkbox" name="read" value="{{ group.id }}"></td>
                             <td>{{ loop.index }}</td>
-                            <td>{{ group.name }}</td>
+                            <td><a href="/groups/view?id={{ group.id }}">{{ group.name }}</a></td>
                             <td>{{ group.admins|length + group.members|length }}</td>
                             <td>{{ group.created_timestamp }}</td>
                             <td>{{ group.changed_timestamp }}</td>
diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html
index 049ebe6d..14e6bc88 100644
--- a/wqflask/wqflask/templates/base.html
+++ b/wqflask/wqflask/templates/base.html
@@ -87,6 +87,7 @@
                                   <li><a href="https://systems-genetics.org/">Systems Genetics PheWAS</a></li>
                                   <li><a href="http://ucscbrowser.genenetwork.org/">Genome Browser</a></li>
                                   <li><a href="http://power.genenetwork.org">BXD Power Calculator</a></li>
+                                  <li><a href="http://notebook.genenetwork.org/">Jupyter Notebook Launcher</a></li>
                                   <li><a href="http://datafiles.genenetwork.org">Interplanetary File System</a></li>
                                 </ul>
                         </li>
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/collections/view.html b/wqflask/wqflask/templates/collections/view.html
index 9ec98ab1..a3090bcf 100644
--- a/wqflask/wqflask/templates/collections/view.html
+++ b/wqflask/wqflask/templates/collections/view.html
@@ -49,7 +49,7 @@
                     <input type="text" id="select_top" class="form-control" style="width: 200px; display: inline; padding-bottom: 9px;" placeholder="Select Top ...">
                     <button class="btn btn-default" id="deselect_all" type="button"><span class="glyphicon glyphicon-remove"></span> Deselect</button>
                     <button id="remove" class="btn btn-danger" data-url="/collections/remove" type="button" disabled><i class="icon-minus-sign"></i> Delete Rows</button>
-                    <button id="delete" class="btn btn-danger submit_special" data-url="/collections/delete" title="Delete this collection" > Delete Collection</button>
+                    <button id="delete" class="btn btn-danger submit_special" data-url="/collections/delete" type="button" title="Delete this collection" > Delete Collection</button>
                 </form>
             </div>
             <div style="margin-top: 10px; margin-bottom: 5px;">
diff --git a/wqflask/wqflask/templates/loading.html b/wqflask/wqflask/templates/loading.html
index 6d6136ac..ccf810b0 100644
--- a/wqflask/wqflask/templates/loading.html
+++ b/wqflask/wqflask/templates/loading.html
@@ -12,6 +12,8 @@
           {% if start_vars.tool_used == "Mapping" %}
           <h1>Computing the Maps</h1>
           <br>
+          <b>Time Elapsed:</b> <span class="timer"></span>
+          <br>
           <b>Trait Metadata</b>
           <br>
           species = <b><i>{{ start_vars.species[0] | upper }}{{ start_vars.species[1:] }}</i></b>
@@ -25,6 +27,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 +72,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,6 +103,30 @@
 <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">
+$('#show_full_diff').click(function() {
+  if ($('#diff_table_container').is(':visible')){
+    $('#diff_table_container').hide();
+  } else {
+    $('#diff_table_container').show();
+  }
+})
+
+var start = new Date;
+
+setInterval(function() {
+  minutes = Math.floor((new Date - start) / 1000 / 60)
+  seconds = Math.round(((new Date - start) / 1000) % 60)
+  if (seconds < 10 && minutes >= 1){
+    seconds_text = "0" + seconds.toString()
+  } else {
+    seconds_text = seconds.toString()
+  }
+  if (minutes < 1) {
+    $('.timer').text(seconds_text + " seconds");
+  } else {
+    $('.timer').text(minutes.toString() + ":" + seconds_text);
+  }
+}, 100);
 
 $("#loading_form").attr("action", "{{ start_vars.form_url }}");
 setTimeout(function(){ $("#loading_form").submit()}, 350);
diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html
index 35d8a157..f2d11e89 100644
--- a/wqflask/wqflask/templates/mapping_results.html
+++ b/wqflask/wqflask/templates/mapping_results.html
@@ -34,6 +34,7 @@
         <input type="hidden" name="results_path" value="{{ mapping_results_path }}">
         <input type="hidden" name="method" value="{{ mapping_method }}">
         <input type="hidden" name="sample_vals" value="{{ sample_vals }}">
+        <input type="hidden" name="vals_hash" value="{{ vals_hash }}">
         <input type="hidden" name="n_samples" value="{{ n_samples }}">
         <input type="hidden" name="maf" value="{{ maf }}">
         <input type="hidden" name="use_loco" value="{{ use_loco }}">
@@ -44,7 +45,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 }}">
@@ -62,15 +68,16 @@
               <h2>Map Viewer: Whole Genome</h2><br>
               <b>Population:</b> {{ dataset.group.species|capitalize }} {{ dataset.group.name }}<br>
               <b>Database:</b> {{ dataset.fullname }}<br>
-              {% if dataset.type == "ProbeSet" %}<b>Trait ID:</b>{% else %}<b>Record ID:</b>{% endif %} <a href="/show_trait?trait_id={{ this_trait.name }}&dataset={{ dataset.name }}">{{ this_trait.name }}</a><br>
+              {% if dataset.type == "ProbeSet" %}<b>Trait ID:</b>{% else %}<b>Record ID:</b>{% endif %} <a href="/show_trait?trait_id={{ this_trait.name }}&dataset={{ dataset.name }}">{{ this_trait.display_name }}</a><br>
+              <b>Trait Hash: </b> {{ vals_hash }}<br>
               {% if dataset.type == "ProbeSet" %}
               <b>Gene Symbol:</b> <i>{{ this_trait.symbol }}</i><br>
               <b>Location:</b> Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb<br>
               {% endif %}
-              {% if genofile_string is defined %}
-              <b>Genotypes:</b> {{ genofile_string.split(":")[1] }}
+              {% if genofile_string != "" %}
+              <b>Genotypes:</b> {{ genofile_string.split(":")[1] }}<br>
               {% endif %}
-              <br>
+              <b>Current Date/Time:</b> {{ current_datetime }}<br>
               <br>
               <a class="export_mapping_results" href="#" target="_blank" >Download Full Results</a>
           </div>
@@ -524,7 +531,7 @@
         });
         {% endif %}
 
-        {% if mapping_method != "gemma" and mapping_method != "plink" %}
+        {% if mapping_method != "gemma" and mapping_method != "plink" and nperm > 0 and permChecked == "ON" %}
         $('#download_perm').click(function(){
           perm_info_dict = {
             perm_data: js_data.perm_results,
diff --git a/wqflask/wqflask/templates/new_security/_scripts.html b/wqflask/wqflask/templates/new_security/_scripts.html
deleted file mode 100644
index 5fefe305..00000000
--- a/wqflask/wqflask/templates/new_security/_scripts.html
+++ /dev/null
@@ -1 +0,0 @@
-<!--<script type="text/javascript" src="/static/new/javascript/login.js"></script>-->
diff --git a/wqflask/wqflask/templates/new_security/forgot_password.html b/wqflask/wqflask/templates/new_security/forgot_password.html
index e5c42a45..60a221da 100644
--- a/wqflask/wqflask/templates/new_security/forgot_password.html
+++ b/wqflask/wqflask/templates/new_security/forgot_password.html
@@ -48,6 +48,5 @@
   {% endblock %}
 
 {% block js %}
-    {% include "new_security/_scripts.html" %}
 
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/forgot_password_step2.html b/wqflask/wqflask/templates/new_security/forgot_password_step2.html
index b4bf41c7..1835fd4c 100644
--- a/wqflask/wqflask/templates/new_security/forgot_password_step2.html
+++ b/wqflask/wqflask/templates/new_security/forgot_password_step2.html
@@ -20,7 +20,6 @@
 {% endblock %}
 
 {% block js %}
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html
index 095036f0..88eab6bc 100644
--- a/wqflask/wqflask/templates/new_security/login_user.html
+++ b/wqflask/wqflask/templates/new_security/login_user.html
@@ -114,31 +114,5 @@ label.error,div.error{
 {% endblock %}
 
 {% block js %}
-    <!-- Disable plugin, see https://github.com/genenetwork/genenetwork2/issues/310
-
-    <script type="text/javascript" src="/static/new/packages/ValidationPlugin/dist/jquery.validate.min.js"></script>
-    <script>
-    $(document).ready(function () {
-        $("#loginUserForm").validate({
-            onkeyup: false,
-            onsubmit: true,
-            onfocusout: function(element) { $(element).valid(); },
-            rules: {
-                email_address: {
-                    required: true,
-                    email: true
-                },
-                password: {
-                    required: true
-                }
-            }
-        });
-    });
-
-    </script>
-
-    -->
-
-    {% include "new_security/_scripts.html" %}
 
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/password_reset.html b/wqflask/wqflask/templates/new_security/password_reset.html
index 684c12b1..e21f075c 100644
--- a/wqflask/wqflask/templates/new_security/password_reset.html
+++ b/wqflask/wqflask/templates/new_security/password_reset.html
@@ -73,7 +73,6 @@
 
 {% block js %}
 
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html
index 3ae4488b..c2895517 100644
--- a/wqflask/wqflask/templates/new_security/register_user.html
+++ b/wqflask/wqflask/templates/new_security/register_user.html
@@ -100,7 +100,6 @@
 
 {% block js %}
 
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/registered.html b/wqflask/wqflask/templates/new_security/registered.html
index f2f58ec1..29889a97 100644
--- a/wqflask/wqflask/templates/new_security/registered.html
+++ b/wqflask/wqflask/templates/new_security/registered.html
@@ -19,7 +19,6 @@
 
 {% block js %}
 
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/thank_you.html b/wqflask/wqflask/templates/new_security/thank_you.html
index 0ff7ee8d..d4f5e574 100644
--- a/wqflask/wqflask/templates/new_security/thank_you.html
+++ b/wqflask/wqflask/templates/new_security/thank_you.html
@@ -18,7 +18,6 @@
 {% endblock %}
 
 {% block js %}
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/verification_still_needed.html b/wqflask/wqflask/templates/new_security/verification_still_needed.html
index dc0f9e68..1f91fd8d 100644
--- a/wqflask/wqflask/templates/new_security/verification_still_needed.html
+++ b/wqflask/wqflask/templates/new_security/verification_still_needed.html
@@ -21,7 +21,6 @@
 {% endblock %}
 
 {% block js %}
-    {% include "new_security/_scripts.html" %}
     <script language="javascript" type="text/javascript" src="{{ url_for('js', filename='zxcvbn/zxcvbn.js') }}"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html
index 7ec335d5..c499aa8f 100644
--- a/wqflask/wqflask/templates/search_result_page.html
+++ b/wqflask/wqflask/templates/search_result_page.html
@@ -53,6 +53,7 @@
                 A total of {{ results|count }} records were found.
         </p>
 
+        {% if results|count > 0 %}
         {% if go_term is not none %}
         <p><b>The associated genes include:</b><br><br>{% for word in search_terms %}{{ word.search_term[0] }}{% endfor %}</p>
         {% endif %}
@@ -133,8 +134,11 @@
           </div>
         </div>
         {% endif %}
+        {% else %}
+        <br>
+        <button type="button" onclick="window.location.href='/'">Return To Index Page</button>
+        {% endif %}
     </div>
-
     <div id="myModal"></div>
 
 <!-- End of body -->
@@ -171,6 +175,7 @@
                 return params;
             };
 
+            {% if results|count > 0 %}
             //ZS: Need to make sort by symbol, also need to make sure blank symbol fields at the bottom and symbols starting with numbers below letters
             trait_table = $('#trait_table').DataTable( {
                 'drawCallback': function( settings ) {
@@ -412,6 +417,7 @@
                 var table = $('#trait_table').DataTable();
                 table.colReorder.reset()
             });
+            {% endif %}
 
             submit_special = function(url) {
                 $("#trait_submission_form").attr("action", url);
diff --git a/wqflask/wqflask/templates/show_trait.html b/wqflask/wqflask/templates/show_trait.html
index 3dbf5f57..f3fa1332 100644
--- a/wqflask/wqflask/templates/show_trait.html
+++ b/wqflask/wqflask/templates/show_trait.html
@@ -254,8 +254,6 @@
                 } );
                 {% endif %}
 
-                $('#samples_primary, #samples_other').find("tr.outlier").css('background-color', 'orange')
-
                 $('.edit_sample_checkbox:checkbox').change(function() {
                     if ($(this).is(":checked")) {
                         if (!$(this).closest('tr').hasClass('selected')) {
diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html
index bb30c54c..2a21dd24 100644
--- a/wqflask/wqflask/templates/show_trait_details.html
+++ b/wqflask/wqflask/templates/show_trait_details.html
@@ -236,12 +236,15 @@
         <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' %}
         <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/trait/edit/probeset-name/{{ this_trait.name }}', '_blank')">Edit</button>
         {% endif %}
+        {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %}
+        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('./resources/manage?resource_id={{ resource_id }}', '_blank')">Edit Privileges</button>
+        {% endif %}
         {% endif %}
     </div>
 </div>
diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html
index 3dd44c85..3af94ed6 100755
--- a/wqflask/wqflask/templates/show_trait_mapping_tools.html
+++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html
@@ -74,17 +74,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>
@@ -93,15 +96,6 @@
                 <div class="tab-pane" id="interval_mapping">
                     <div class="form-horizontal section-form-div">
                         <div class="mapping_method_fields form-group">
-                            <label for="reaper_version" class="col-xs-3 control-label">Version<sup><a href="https://github.com/chfi/rust-qtlreaper" target="_blank" title="'New' is the new qtlreaper implementation written in RUST by Christian Fischer. 'Original' corresponds to the original version written in C.">?</a></sup></label>
-                            <div class="col-xs-3 controls">
-                                <select name="reaper_version" class="form-control reaper-ver-select">
-                                    <option value="new">New</option>
-                                    <option value="original">Original</option>
-                                </select>
-                            </div>
-                        </div>
-                        <div class="mapping_method_fields form-group">
                             <label for="chr_select" class="col-xs-3 control-label">Chromosome</label>
                             <div class="col-xs-2 controls">
                                     <select id="chr_reaper" class="form-control chr-select">
@@ -187,7 +181,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>
@@ -226,6 +220,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>
@@ -249,21 +254,6 @@
                         </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_geno" 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_geno" class="col-xs-3 control-label">Model</label>
                             <div class="col-xs-4 controls">
                               <select id="mapmodel_rqtl_geno" name="mapmodel_rqtl_geno" class="form-control">
@@ -317,17 +307,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_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" class="btn submit_special btn-success" data-url="/marker_regression" title="Compute Marker Regression" value="Compute">Compute</button>
+                              <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>
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/views.py b/wqflask/wqflask/views.py
index a462b31a..b0da1f21 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -28,6 +28,7 @@ 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
@@ -63,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
@@ -84,7 +86,7 @@ from wqflask.export_traits import export_search_results_csv
 from wqflask.gsearch import GSearch
 from wqflask.update_search_results import GSearch as UpdateGSearch
 from wqflask.docs import Docs, update_text
-from wqflask.decorators import admin_login_required
+from wqflask.decorators import edit_access_required
 from wqflask.db_info import InfoPage
 
 from utility import temp_data
@@ -159,28 +161,37 @@ def shutdown_session(exception=None):
 
 
 @app.errorhandler(Exception)
-def handle_bad_request(e):
+def handle_generic_exceptions(e):
+    import werkzeug
     err_msg = str(e)
-    logger.error(err_msg)
-    logger.error(request.url)
-    # get the stack trace and send it to the logger
-    exc_type, exc_value, exc_traceback = sys.exc_info()
-    logger.error(traceback.format_exc())
     now = datetime.datetime.utcnow()
     time_str = now.strftime('%l:%M%p UTC %b %d, %Y')
-    formatted_lines = [request.url
-                       + " (" + time_str + ")"] + traceback.format_exc().splitlines()
-
+    # get the stack trace and send it to the logger
+    exc_type, exc_value, exc_traceback = sys.exc_info()
+    formatted_lines = {f"{request.url} ({time_str}) "
+                       f" {traceback.format_exc().splitlines()}"}
+
+    _message_templates = {
+        werkzeug.exceptions.NotFound: ("404: Not Found: "
+                                       f"{time_str}: {request.url}"),
+        werkzeug.exceptions.BadRequest: ("400: Bad Request: "
+                                         f"{time_str}: {request.url}"),
+        werkzeug.exceptions.RequestTimeout: ("408: Request Timeout: "
+                                             f"{time_str}: {request.url}")}
+    # Default to the lengthy stack trace!
+    logger.error(_message_templates.get(exc_type,
+                                        formatted_lines))
     # Handle random animations
     # Use a cookie to have one animation on refresh
     animation = request.cookies.get(err_msg[:32])
     if not animation:
-        list = [fn for fn in os.listdir(
-            "./wqflask/static/gif/error") if fn.endswith(".gif")]
-        animation = random.choice(list)
+        animation = random.choice([fn for fn in os.listdir(
+            "./wqflask/static/gif/error") if fn.endswith(".gif")])
 
     resp = make_response(render_template("error.html", message=err_msg,
-                                         stack=formatted_lines, error_image=animation, version=GN_VERSION))
+                                         stack=formatted_lines,
+                                         error_image=animation,
+                                         version=GN_VERSION))
 
     # logger.error("Set cookie %s with %s" % (err_msg, animation))
     resp.set_cookie(err_msg[:32], animation)
@@ -372,7 +383,6 @@ def wcgna_results():
     results = run_wgcna(dict(request.form))
     return render_template("test_wgcna_results.html", **results)
 
-
 @app.route("/ctl_setup", methods=('POST',))
 def ctl_setup():
     # We are going to get additional user input for the analysis
@@ -382,20 +392,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 +426,9 @@ def submit_trait_form():
         version=GN_VERSION)
 
 
-@app.route("/trait/<name>/edit/phenotype-id/<phenotype_id>")
-@admin_login_required
-def edit_phenotype(name, phenotype_id):
+@app.route("/trait/<name>/edit/inbredset-id/<inbredset_id>")
+@edit_access_required
+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 +437,7 @@ def edit_phenotype(name, phenotype_id):
         conn=conn,
         table="PublishXRef",
         where=PublishXRef(id_=name,
-                          phenotype_id=phenotype_id))
+                          inbred_set_id=inbredset_id))
     phenotype_ = fetchone(
         conn=conn,
         table="Phenotype",
@@ -488,7 +484,7 @@ def edit_phenotype(name, phenotype_id):
 
 
 @app.route("/trait/edit/probeset-name/<dataset_name>")
-# @admin_login_required
+@edit_access_required
 def edit_probeset(dataset_name):
     conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
                            user=current_app.config.get("DB_USER"),
@@ -531,7 +527,7 @@ def edit_probeset(dataset_name):
 
 
 @app.route("/trait/update", methods=["POST"])
-@admin_login_required
+@edit_access_required
 def update_phenotype():
     conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
                            user=current_app.config.get("DB_USER"),
@@ -653,11 +649,11 @@ def update_phenotype():
                                   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/phenotype-id/{data_.get('phenotype-id')}")
+                    f"/edit/inbredset-id/{data_.get('inbred-set-id')}")
 
 
 @app.route("/probeset/update", methods=["POST"])
-@admin_login_required
+@edit_access_required
 def update_probeset():
     conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"),
                            user=current_app.config.get("DB_USER"),
@@ -1018,10 +1014,10 @@ 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'])
@@ -1043,6 +1039,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
@@ -1067,6 +1067,7 @@ def mapping_results_page():
         'samples',
         'vals',
         'sample_vals',
+        'vals_hash',
         'first_run',
         'output_files',
         'geno_db_exists',
@@ -1083,7 +1084,6 @@ def mapping_results_page():
         'num_perm',
         'permCheck',
         'perm_strata',
-        'strat_var',
         'categorical_vars',
         'perm_output',
         'num_bootstrap',
@@ -1113,7 +1113,6 @@ def mapping_results_page():
         'mapmethod_rqtl_geno',
         'mapmodel_rqtl_geno',
         'temp_trait',
-        'reaper_version',
         'n_samples',
         'transform'
     )
@@ -1186,7 +1185,7 @@ def export_mapping_results():
     results_csv = open(file_path, "r").read()
     response = Response(results_csv,
                         mimetype='text/csv',
-                        headers={"Content-Disposition": "attachment;filename=mapping_results.csv"})
+                        headers={"Content-Disposition": "attachment;filename=" + os.path.basename(file_path)})
 
     return response
 
@@ -1389,7 +1388,7 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int):
 
 
 @app.route("/admin/data-sample/diffs/")
-@admin_login_required
+@edit_access_required
 def display_diffs_admin():
     TMPDIR = current_app.config.get("TMPDIR")
     DIFF_DIR = f"{TMPDIR}/sample-data/diffs"