Index: src/sas/sasgui/guiframe/CategoryInstaller.py
===================================================================
--- src/sas/sasgui/guiframe/CategoryInstaller.py	(revision a26f67f1457e7e39b79d4c3800c03bcaa208dff7)
+++ src/sas/sasgui/guiframe/CategoryInstaller.py	(revision cb64d86aa05427be27c71cc415727ce4d7fdce69)
@@ -20,4 +20,13 @@
 
 logger = logging.getLogger(__name__)
+
+if sys.version_info[0] > 2:
+    def json_dump(obj, filename):
+        with open(filename, 'w', newline='\n') as fd:
+            json.dump(obj, fd)
+else: # CRUFT: python 2.7 support
+    def json_dump(obj, filename):
+        with open(filename, 'wb') as fd:
+            json.dump(obj, fd)
 
 class CategoryInstaller(object):
@@ -121,5 +130,5 @@
         """
         _model_dict = {model.name: model for model in model_list}
-        _model_list = _model_dict.keys()
+        _model_list = list(_model_dict.keys())
 
         serialized_file = None
@@ -146,6 +155,6 @@
                         by_model_dict.pop(model_name)
                         model_enabled_dict.pop(model_name)
-                    except Exception:
-                        logger.error("CategoryInstaller: %s", sys.exc_value)
+                    except Exception as exc:
+                        logger.error("CategoryInstaller: %s", exc)
                 else:
                     add_list.remove(model_name)
@@ -171,3 +180,3 @@
                                                           model_enabled_dict)
 
-            json.dump(master_category_dict, open(serialized_file, 'wb'))
+            json_dump(master_category_dict, serialized_file)
Index: src/sas/sasgui/guiframe/CategoryManager.py
===================================================================
--- src/sas/sasgui/guiframe/CategoryManager.py	(revision 463e7ffce130ae386f93cd18b87a1120743e88b5)
+++ src/sas/sasgui/guiframe/CategoryManager.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -230,5 +230,5 @@
 
         for model in model_name_list:
-            index = self.cat_list.InsertStringItem(sys.maxint, model)
+            index = self.cat_list.InsertStringItem(sys.maxsize, model)
             self.cat_list.SetStringItem(index, 1, \
                                             str(self.by_model_dict[model]).\
@@ -384,5 +384,5 @@
         """
         cat_list = list()
-        for category in self.master_category_dict.iterkeys():
+        for category in self.master_category_dict.keys():
             if not category == 'Uncategorized':
                 cat_list.append(category)
Index: src/sas/sasgui/guiframe/custom_pstats.py
===================================================================
--- src/sas/sasgui/guiframe/custom_pstats.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/custom_pstats.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -36,30 +36,30 @@
         width = self.max_name_len
         if self.fcn_list:
-            list = self.fcn_list[:]
+            stats = self.fcn_list[:]
             temp_msg = "   Ordered by: " + self.sort_type + '\n'
         else:
-            list = self.stats.keys()
+            stats = list(self.stats.keys())
             temp_msg = "   Random listing order was used\n"
 
         for selection in amount:
-            list, temp_msg = self.eval_print_amount(selection, list, temp_msg)
+            stats, temp_msg = self.eval_print_amount(selection, stats, temp_msg)
 
-        count = len(list)
+        count = len(stats)
 
-        if not list:
-            width, list = 0, list
+        if not stats:
+            width = 0
         else:
-            msg +=   str(temp_msg) + '\n'
+            msg += str(temp_msg) + '\n'
             if count < len(self.stats):
                 width = 0
-                for func in list:
+                for func in stats:
                     if  len(func_std_string(func)) > width:
                         width = len(func_std_string(func))
        
-            width, list = width+2, list
-        if list:
+            width = width+2
+        if stats:
             msg += '   ncalls  tottime  percall  cumtime  percall'
             msg +=  ' filename:lineno(function)' + "\n"
-            for func in list:
+            for func in stats:
                 cc, nc, tt, ct, callers = self.stats[func]
                 c = str(nc)
Index: src/sas/sasgui/guiframe/data_manager.py
===================================================================
--- src/sas/sasgui/guiframe/data_manager.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/data_manager.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -51,5 +51,5 @@
         _str += "No of states  is %s \n" % str(len(self.stored_data))
         n_count = 0
-        for  value in self.stored_data.values():
+        for value in self.stored_data.values():
             n_count += 1 
             _str += "State No %s \n"  % str(n_count)
@@ -134,5 +134,5 @@
         receive a list of 
         """
-        for id, data in data_list.iteritems():
+        for id, data in data_list.items():
             if id  in self.stored_data:
                 msg = "Data manager already stores %s" % str(data.name)
@@ -150,10 +150,10 @@
         """
         """
-        if prev_data.id not in self.stored_data.keys():
+        if prev_data.id not in self.stored_data:
             return None, {}
         data_state = self.stored_data[prev_data.id] 
         self.stored_data[new_data.id]  = data_state.clone()
         self.stored_data[new_data.id].data = new_data
-        if prev_data.id in self.stored_data.keys():
+        if prev_data.id in self.stored_data:
             del self.stored_data[prev_data.id] 
         return prev_data.id, {new_data.id: self.stored_data[new_data.id]}
@@ -165,5 +165,5 @@
         if data_id is None and theory is not None:
             uid = theory.id
-        if uid in self.stored_data.keys():
+        if uid in self.stored_data:
              data_state = self.stored_data[uid] 
         else:
@@ -195,5 +195,5 @@
                 if search_id == d_id:
                     _selected_data[search_id] = data
-                if search_id in theory_list.keys():
+                if search_id in theory_list:
                      _selected_theory_list[search_id] = theory_list[search_id]
                    
@@ -204,5 +204,5 @@
         """
         """
-        return self.freeze_theory(self.stored_data.keys(), theory_id)
+        return self.freeze_theory(list(self.stored_data.keys()), theory_id)
         
     def freeze_theory(self, data_id, theory_id):
@@ -215,5 +215,5 @@
                 theory_list = data_state.get_theory()
                 for t_id in theory_id:
-                    if t_id in theory_list.keys():
+                    if t_id in theory_list:
                         theory_data, theory_state = theory_list[t_id]
                         new_theory = copy.deepcopy(theory_data)
@@ -235,5 +235,5 @@
         """
         for d_id in data_id:
-            if d_id in self.stored_data.keys():
+            if d_id in self.stored_data:
                 data_state = self.stored_data[d_id]
                 if data_state.data.name in self.data_name_dict:
@@ -253,5 +253,5 @@
                 data_state = self.stored_data[d_id]
                 theory_list = data_state.get_theory()
-                if theory_id in theory_list.keys():
+                if theory_id in theory_list:
                     del theory_list[theory_id]
         #del pure theory
@@ -273,5 +273,5 @@
         _selected_data = {}
         for selected_name in name_list:
-            for id, data_state in self.stored_data.iteritems():
+            for id, data_state in self.stored_data.items():
                 if data_state.data.name == selected_name:
                     _selected_data[id] = data_state.data
@@ -283,5 +283,5 @@
         """
         for selected_name in name_list:
-            for id, data_state in self.stored_data.iteritems():
+            for id, data_state in self.stored_data.items():
                 if data_state.data.name == selected_name:
                     del self.stored_data[id]
@@ -293,5 +293,5 @@
         _selected_data_state = {}
         for id in data_id:
-            if id in self.stored_data.keys():
+            if id in self.stored_data:
                 _selected_data_state[id] = self.stored_data[id]
         return _selected_data_state
@@ -303,4 +303,2 @@
         return self.stored_data
     
-
-        
Index: src/sas/sasgui/guiframe/data_panel.py
===================================================================
--- src/sas/sasgui/guiframe/data_panel.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
+++ src/sas/sasgui/guiframe/data_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -13,10 +13,11 @@
 from __future__ import print_function
 
+import sys
+
 import wx
-from wx.build import build_options
-
-import sys
 from wx.lib.scrolledpanel import ScrolledPanel
 import wx.lib.agw.customtreectrl as CT
+#from wx.build import build_options
+
 from sas.sasgui.guiframe.dataFitting import Data1D
 from sas.sasgui.guiframe.dataFitting import Data2D
@@ -444,7 +445,7 @@
             self.parent.get_data_manager().get_by_id(id_list=[id])
         if data_list:
-            data = data_list.values()[0]
+            data = list(data_list.values())[0]
         if data is None:
-            data = theory_list.values()[0][0]
+            data = list(theory_list.values())[0][0]
         return data
 
@@ -667,5 +668,5 @@
         """
         if list:
-            for state_id, dstate in list.iteritems():
+            for state_id, dstate in list.items():
                 data = dstate.get_data()
                 theory_list = dstate.get_theory()
@@ -764,5 +765,5 @@
         if not theory_list:
             return
-        if state_id not in self.list_cb_data.keys():
+        if state_id not in self.list_cb_data:
             root = self.tree_ctrl_theory.root
             tree = self.tree_ctrl_theory
@@ -783,9 +784,9 @@
         Append theory helper
         """
-        if state_id in self.list_cb_theory.keys():
+        if state_id in self.list_cb_theory:
             # update current list of theory for this data
             theory_list_ctrl = self.list_cb_theory[state_id]
 
-            for theory_id, item in theory_list.iteritems():
+            for theory_id, item in theory_list.items():
                 theory_data, _ = item
                 if theory_data is None:
@@ -828,5 +829,5 @@
             # data didn't have a theory associated it before
             theory_list_ctrl = {}
-            for theory_id, item in theory_list.iteritems():
+            for theory_id, item in theory_list.items():
                 theory_data, _ = item
                 if theory_data is not None:
@@ -868,5 +869,5 @@
 
         for theory_dict in self.list_cb_theory.values():
-            for _, value in theory_dict.iteritems():
+            for _, value in theory_dict.items():
                 item, _, _ = value
                 if item.IsChecked():
@@ -924,15 +925,15 @@
         theory_key = []
         # remove  data from treectrl
-        for d_key, item in self.list_cb_data.iteritems():
+        for d_key, item in self.list_cb_data.items():
             data_c, _, _, _,  _, _, _, _ = item
             if data_c.IsChecked():
                 self.tree_ctrl.Delete(data_c)
                 data_key.append(d_key)
-                if d_key in self.list_cb_theory.keys():
+                if d_key in self.list_cb_theory:
                     theory_list_ctrl = self.list_cb_theory[d_key]
-                    theory_to_remove += theory_list_ctrl.keys()
+                    theory_to_remove += list(theory_list_ctrl.keys())
         # Remove theory from treectrl
-        for _, theory_dict in self.list_cb_theory.iteritems():
-            for key, value in theory_dict.iteritems():
+        for _, theory_dict in self.list_cb_theory.items():
+            for key, value in theory_dict.items():
                 item, _, _ = value
                 if item.IsChecked():
@@ -950,7 +951,7 @@
         # remove theory  references independently of data
         for key in theory_key:
-            for _, theory_dict in self.list_cb_theory.iteritems():
+            for _, theory_dict in self.list_cb_theory.items():
                 if key in theory_dict:
-                    for key, value in theory_dict.iteritems():
+                    for key, value in theory_dict.items():
                         item, _, _ = value
                         if item.IsChecked():
@@ -1047,5 +1048,5 @@
         if self.cb_plotpanel and self.cb_plotpanel.IsBeingDeleted():
             return
-        for _, value in self.parent.plot_panels.iteritems():
+        for _, value in self.parent.plot_panels.items():
             name_plot_panel = str(value.window_caption)
             if name_plot_panel not in self.cb_plotpanel.GetItems():
@@ -1129,5 +1130,5 @@
         #import documentation window here to avoid circular imports
         #if put at top of file with rest of imports.
-        from documentation_window import DocumentationWindow
+        from .documentation_window import DocumentationWindow
 
         _TreeLocation = "user/sasgui/guiframe/data_explorer_help.html"
@@ -1499,7 +1500,7 @@
         window.Show(True)
         window.load_data_list(list=temp_data_list)
-    except:
+    except Exception as exc:
         # raise
-        print("error", sys.exc_value)
+        print("error", exc)
 
     app.MainLoop()
Index: src/sas/sasgui/guiframe/data_processor.py
===================================================================
--- src/sas/sasgui/guiframe/data_processor.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/data_processor.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -234,5 +234,10 @@
         # overriden in this subclassn - actually we have currently
         # disabled the on_context_menu that would override the OnRightClick
-        self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnCellChange)
+        try:
+            EVT_GRID_CELL_CHANGED = wx.grid.EVT_GRID_CELL_CHANGED
+        except AttributeError:
+            # CRUFT: wx 3.x uses CHANGE rather than CHANGING/CHANGED
+            EVT_GRID_CELL_CHANGED = wx.grid.EVT_GRID_CELL_CHANGE
+        self.Bind(EVT_GRID_CELL_CHANGED, self.OnCellChange)
         self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
         self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnRightClick)
@@ -561,5 +566,5 @@
                 value = self.GetCellValue(row, col)
                 self.data[col_name].append(value)
-                for k, value_list in self.data.iteritems():
+                for k, value_list in self.data.items():
                     if k != col_name:
                         length = len(value_list)
@@ -621,5 +626,5 @@
         if col_name.strip() != "Empty":
             self.SetCellValue(row, col, str(col_name.strip()))
-        if col_name in self.data.keys():
+        if col_name in self.data:
             value_list = self.data[col_name]
             cell_row = 1
@@ -674,5 +679,5 @@
             self._cols = self.GetNumberCols()
             self._rows = self.GetNumberRows()
-            self.col_names = self.data_outputs.keys()
+            self.col_names = list(self.data_outputs.keys())
             self.col_names.sort()
             nbr_user_cols = len(self.col_names)
@@ -682,5 +687,5 @@
                 self.AppendCols(new_col_nbr, True)
             #Add more rows to the grid if necessary
-            nbr_user_row = len(self.data_outputs.values()[0])
+            nbr_user_row = len(list(self.data_outputs.values())[0])
             if nbr_user_row > self._rows + 1:
                 new_row_nbr = nbr_user_row - self._rows + 1
@@ -909,5 +914,5 @@
                     msg = "Edit axis doesn't understand this selection.\n"
                     msg += "Please select only one column"
-                    raise ValueError, msg
+                    raise ValueError(msg)
             for (_, cell_col) in grid.selected_cells:
                 if cell_col != col:
@@ -915,5 +920,5 @@
                     msg += "this operation.\n"
                     msg += "Please select elements of the same col.\n"
-                    raise ValueError, msg
+                    raise ValueError(msg)
 
             # Finally check the highlighted cell if any cells missing
@@ -922,5 +927,5 @@
             msg = "No item selected.\n"
             msg += "Please select only one column or one cell"
-            raise ValueError, msg
+            raise ValueError(msg)
         return grid.selected_cells
 
@@ -1326,5 +1331,5 @@
             if sentence.strip() == "":
                 msg = "Select column values for x axis"
-                raise ValueError, msg
+                raise ValueError(msg)
         except:
             msg = "X axis value error."
@@ -1345,5 +1350,5 @@
             if sentence.strip() == "":
                 msg = "select value for y axis"
-                raise ValueError, msg
+                raise ValueError(msg)
         except:
             msg = "Y axis value error."
@@ -1440,5 +1445,5 @@
         """
 
-        for tok, (col_name, list) in dict.iteritems():
+        for tok, (col_name, list) in dict.items():
             col = column_names[col_name]
             axis = self.get_plot_axis(col, list)
@@ -1446,5 +1451,5 @@
                 return None
             sentence = sentence.replace(tok, "numpy.array(%s)" % str(axis))
-        for key, value in FUNC_DICT.iteritems():
+        for key, value in FUNC_DICT.items():
             sentence = sentence.replace(key.lower(), value)
         return sentence
@@ -1546,6 +1551,6 @@
             cell_list = self.notebook.on_edit_axis()
             label, title = self.create_axis_label(cell_list)
-        except:
-            msg = str(sys.exc_value)
+        except Exception as exc:
+            msg = str(exc)
             wx.PostEvent(self.parent.parent, StatusEvent(status=msg, info="error"))
             return
@@ -2036,6 +2041,6 @@
         frame = GridFrame(data_outputs=data, data_inputs=data_input)
         frame.Show(True)
-    except:
-        print(sys.exc_value)
+    except Exception as exc:
+        print(exc)
 
     app.MainLoop()
Index: src/sas/sasgui/guiframe/data_state.py
===================================================================
--- src/sas/sasgui/guiframe/data_state.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/data_state.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -37,5 +37,5 @@
         _str += "Theories available: %s \n" % len(self.theory_list)
         if self.theory_list:
-            for id, item in self.theory_list.iteritems():
+            for id, item in self.theory_list.items():
                 theory_data, theory_state = item
                 _str += "Theory name : %s \n" % str(theory_data.name)
@@ -53,5 +53,5 @@
         obj.message = self.message
         obj.id = self.id
-        for id, item in self.theory_list.iteritems():
+        for id, item in self.theory_list.items():
             theory_data, theory_state = item
             state = None
@@ -95,5 +95,5 @@
         """
         self.theory_list[theory_data.id] = [theory_data, theory_state]
-        data, state = self.theory_list.values()[0]
+        data, state = list(self.theory_list.values())[0]
        
     def get_theory(self):
@@ -106,3 +106,2 @@
         return self.message
     
-  
Index: src/sas/sasgui/guiframe/gui_manager.py
===================================================================
--- src/sas/sasgui/guiframe/gui_manager.py	(revision 9220e89c31ca5156d9c3a40634b4257084d139a2)
+++ src/sas/sasgui/guiframe/gui_manager.py	(revision 9220e89c31ca5156d9c3a40634b4257084d139a2)
@@ -13,6 +13,4 @@
 
 
-import wx
-import wx.aui
 import os
 import sys
@@ -25,4 +23,8 @@
 import urllib
 import json
+import copy
+
+import wx
+import wx.aui
 
 from matplotlib import _pylab_helpers
@@ -275,5 +277,5 @@
         return client size tuple
         """
-        width, height = self.GetClientSizeTuple()
+        width, height = self.GetClientSize()
         height -= 45
         # Adjust toolbar height
@@ -350,9 +352,8 @@
         try:
             self.read_batch_tofile(file_name=path)
-        except:
+        except Exception as exc:
             msg = "Error occurred when reading the file; %s\n" % path
-            msg += "%s\n" % sys.exc_value
-            wx.PostEvent(self, StatusEvent(status=msg,
-                                           info="error"))
+            msg += "%s\n" % exc
+            wx.PostEvent(self, StatusEvent(status=msg, info="error"))
 
     def read_batch_tofile(self, file_name):
@@ -694,8 +695,8 @@
         try:
             self.load_from_cmd(self._input_file)
-        except:
+        except Exception as exc:
             msg = "%s Cannot load file %s\n" % (str(APPLICATION_NAME),
                                                 str(self._input_file))
-            msg += str(sys.exc_value) + '\n'
+            msg += str(exc) + '\n'
             logger.error(msg)
         if self._data_panel is not None and len(self.plugins) > 0:
@@ -786,7 +787,7 @@
                 self._data_plugin = data_loader.Plugin()
                 plugins.append(self._data_plugin)
-            except:
+            except Exception as exc:
                 msg = "ViewerFrame._get_local_plugins:"
-                msg += "cannot import dataloader plugin.\n %s" % sys.exc_value
+                msg += "cannot import dataloader plugin.\n %s" % exc
                 logger.error(msg)
         if style2 == GUIFRAME.PLOTTING_ON:
@@ -796,7 +797,7 @@
                 self._plotting_plugin = plotting.Plugin()
                 plugins.append(self._plotting_plugin)
-            except:
+            except Exception as exc:
                 msg = "ViewerFrame._get_local_plugins:"
-                msg += "cannot import plotting plugin.\n %s" % sys.exc_value
+                msg += "cannot import plotting plugin.\n %s" % exc
                 logger.error(msg)
 
@@ -845,18 +846,18 @@
                                 msg = "Found plug-in: %s" % module.PLUGIN_ID
                                 logger.info(msg)
-                            except:
+                            except Exception as exc:
                                 msg = "Error accessing PluginPanel"
-                                msg += " in %s\n  %s" % (name, sys.exc_value)
+                                msg += " in %s\n  %s" % (name, exc)
                                 config.printEVT(msg)
-                    except:
-                        msg = "ViewerFrame._find_plugins: %s" % sys.exc_value
+                    except Exception as exc:
+                        msg = "ViewerFrame._find_plugins: %s" % exc
                         logger.error(msg)
                     finally:
                         if file is not None:
                             file.close()
-        except:
+        except Exception as exc:
             # Should raise and catch at a higher level and
             # display error on status bar
-            logger.error(sys.exc_value)
+            logger.error(exc)
 
         return plugins
@@ -1588,5 +1589,5 @@
         """
         # This CallAfter fixes many crashes on MAC.
-        wx.CallAfter(self.sb.set_status, evt)
+        wx.CallAfter(self.sb.set_status, evt.Clone())
 
     def on_view(self, evt):
@@ -1632,5 +1633,5 @@
         ID = str(uid)
         config.printEVT("delete_panel: %s" % ID)
-        if ID in self.panels.keys():
+        if ID in self.panels:
             self.panel_on_focus = None
             panel = self.panels[ID]
@@ -1644,7 +1645,7 @@
 
             # delete uid number not str(uid)
-            if ID in self.plot_panels.keys():
+            if ID in self.plot_panels:
                 del self.plot_panels[ID]
-            if ID in self.panels.keys():
+            if ID in self.panels:
                 del self.panels[ID]
         else:
@@ -1680,7 +1681,7 @@
                     try:
                         reader.read(path)
-                    except:
+                    except Exception as exc:
                         msg = "DataLoader Error: Encounted Non-ASCII character"
-                        msg += "\n(%s)" % sys.exc_value
+                        msg += "\n(%s)" % exc
                         wx.PostEvent(self, StatusEvent(status=msg,
                                                        info="error",
@@ -1763,8 +1764,8 @@
 
             self.add_data(data_list=output)
-        except:
+        except Exception as exc:
             error_message = "Error while loading"
             error_message += " Data from cmd:\n %s\n" % str(path)
-            error_message += str(sys.exc_value) + "\n"
+            error_message += str(exc) + "\n"
             logger.error(error_message)
 
@@ -1784,8 +1785,8 @@
             else:
                 return
-        except:
+        except Exception as exc:
             error_message = "Error while loading"
             error_message += " Data folder from cmd:\n %s\n" % str(path)
-            error_message += str(sys.exc_value) + "\n"
+            error_message += str(exc) + "\n"
             logger.error(error_message)
 
@@ -1854,6 +1855,6 @@
             self._data_panel.selection_cbox.SetValue('Select all Data')
             self._data_panel._on_selection_type(None)
-            for _, theory_dict in self._data_panel.list_cb_theory.iteritems():
-                for key, value in theory_dict.iteritems():
+            for _, theory_dict in self._data_panel.list_cb_theory.items():
+                for key, value in theory_dict.items():
                     item, _, _ = value
                     item.Check(True)
@@ -1963,5 +1964,5 @@
             # case of a panel with multi-pages
             if hasattr(panel, "opened_pages"):
-                for _, page in panel.opened_pages.iteritems():
+                for _, page in panel.opened_pages.items():
                     data = page.get_data()
                     # state must be cloned
@@ -2085,5 +2086,5 @@
                 msg += " Please try again later."
                 self.SetStatusText(msg)
-            elif cmp(version, config.__version__) > 0:
+            elif version > config.__version__:
                 msg = "Version %s is available! " % str(version)
                 if not standalone:
@@ -2101,7 +2102,7 @@
                     msg += " of %s" % str(config.__appname__)
                     self.SetStatusText(msg)
-        except:
+        except Exception as exc:
             msg = "guiframe: could not get latest application"
-            msg += " version number\n  %s" % sys.exc_value
+            msg += " version number\n  %s" % exc
             logger.error(msg)
             if not standalone:
@@ -2143,5 +2144,5 @@
         # S King, Sep 2018
 
-        from documentation_window import DocumentationWindow
+        from .documentation_window import DocumentationWindow
         _TreeLocation = "user/release.html"
         DocumentationWindow(self, -1, _TreeLocation, "",
@@ -2164,5 +2165,5 @@
         # S King, Sep 2018
 
-        from documentation_window import DocumentationWindow
+        from .documentation_window import DocumentationWindow
         _TreeLocation = "user/tutorial.html"
         DocumentationWindow(self, -1, _TreeLocation, "",
@@ -2179,5 +2180,5 @@
         # Running SasView "in-place" using run.py means the docs will be in a
         # different place than they would otherwise.
-        from documentation_window import DocumentationWindow
+        from .documentation_window import DocumentationWindow
         _TreeLocation = "user/user.html"
         DocumentationWindow(self, -1, _TreeLocation, "",
@@ -2227,5 +2228,5 @@
                 if (style == GUIFRAME.MANAGER_ON) \
                         and self.panels[item] == self._data_panel:
-                    if 'data_panel' in self.panels.keys():
+                    if 'data_panel' in self.panels:
                         frame = self.panels['data_panel'].get_frame()
                         if frame is None:
@@ -2279,5 +2280,5 @@
         # set data in the data panel
         if self._data_panel is not None:
-            data_state = self._data_manager.get_data_state(data_list.keys())
+            data_state = self._data_manager.get_data_state(list(data_list.keys()))
             self._data_panel.load_data_list(data_state)
         # if the data panel is shown wait for the user to press a button
@@ -2291,5 +2292,5 @@
         else:
             # automatically send that to the current perspective
-            self.set_data(data_id=data_list.keys())
+            self.set_data(data_id=list(data_list.keys()))
 
     def set_data(self, data_id, theory_id=None):
@@ -2299,5 +2300,5 @@
         list_data, _ = self._data_manager.get_by_id(data_id)
         if self._current_perspective is not None:
-            self._current_perspective.set_data(list_data.values())
+            self._current_perspective.set_data(list(list_data.values()))
 
         else:
@@ -2311,7 +2312,7 @@
         if self._current_perspective is not None:
             try:
-                self._current_perspective.set_theory(list_theory.values())
-            except:
-                msg = "Guiframe set_theory: \n" + str(sys.exc_value)
+                self._current_perspective.set_theory(list(list_theory.values()))
+            except Exception as exc:
+                msg = "Guiframe set_theory: \n" + str(exc)
                 logger.info(msg)
                 wx.PostEvent(self, StatusEvent(status=msg, info="error"))
@@ -2327,5 +2328,5 @@
         data_list, _ = self._data_manager.get_by_id(data_id)
         _, temp_list_theory = self._data_manager.get_by_id(theory_id)
-        total_plot_list = data_list.values()
+        total_plot_list = list(data_list.values())
         for item in temp_list_theory.values():
             theory_data, theory_state = item
@@ -2376,5 +2377,5 @@
         data_list, _ = self._data_manager.get_by_id(data_id)
         _, temp_list_theory = self._data_manager.get_by_id(theory_id)
-        total_plot_list = data_list.values()
+        total_plot_list = list(data_list.values())
         for item in temp_list_theory.values():
             theory_data, theory_state = item
@@ -2399,6 +2400,6 @@
                                             group_id=("res" + str(id)),
                                             action='remove'))
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
     def save_data1d(self, data, fname):
@@ -2504,5 +2505,5 @@
             wx.PostEvent(self, StatusEvent(status=msg,
                                            info="error"))
-            raise ValueError, msg
+            raise ValueError(msg)
         # text = str(data)
         text = data.__str__()
@@ -2535,5 +2536,5 @@
                                                   dy_val,
                                                   dx_val)
-        from pdfview import TextFrame
+        from .pdfview import TextFrame
         frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
         # put icon
@@ -2627,5 +2628,5 @@
                 break
 
-        from pdfview import TextFrame
+        from .pdfview import TextFrame
         frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
         # put icon
@@ -2694,5 +2695,5 @@
         check = "Theory1D"
         is_theory = len(self.panel_on_focus.plots) <= 1 and \
-            self.panel_on_focus.plots.values()[0].__class__.__name__ == check
+            list(self.panel_on_focus.plots.values())[0].__class__.__name__ == check
 
         is_data2d = hasattr(new_plot, 'data')
@@ -3210,9 +3211,9 @@
             else:
                 self.frame.Show()
-        except:
+        except Exception as exc:
             if self.s_screen is not None:
                 self.s_screen.Close()
             msg = "Cannot display splash screen\n"
-            msg += str(sys.exc_value)
+            msg += str(exc)
             logger.error(msg)
             self.frame.Show()
@@ -3269,7 +3270,7 @@
                         if os.path.isfile(file_path):
                             os.remove(file_path)
-                except:
+                except Exception as exc:
                     logger.error("gui_manager.clean_plugin_models:\n  %s"
-                                  % sys.exc_value)
+                                 % exc)
 
     def set_manager(self, manager):
@@ -3364,4 +3365,9 @@
                               path=SPLASH_SCREEN_PATH):
         """Displays the splash screen.  It will exactly cover the main frame."""
+        try:
+            from wx.adv import SplashScreen, SPLASH_TIMEOUT, SPLASH_CENTRE_ON_SCREEN
+        except ImportError:
+            # CRUFT: wx 4 moved SplashScreen from wx to wx.adv
+            from wx import SplashScreen, SPLASH_TIMEOUT, SPLASH_CENTRE_ON_SCREEN
 
         # Prepare the picture.  On a 2GHz intel cpu, this takes about a second.
@@ -3380,13 +3386,13 @@
         # Note that on Linux, the timeout appears to occur immediately in which
         # case the splash screen disappears upon entering the event loop.
-        s_screen = wx.SplashScreen(bitmap=bm,
-                                   splashStyle=(wx.SPLASH_TIMEOUT |
-                                                wx.SPLASH_CENTRE_ON_SCREEN),
-                                   style=(wx.SIMPLE_BORDER |
-                                          wx.FRAME_NO_TASKBAR |
-                                          wx.FRAME_FLOAT_ON_PARENT),
-                                   milliseconds=SS_MAX_DISPLAY_TIME,
-                                   parent=parent,
-                                   id=wx.ID_ANY)
+        s_screen = SplashScreen(bitmap=bm,
+                                splashStyle=(SPLASH_TIMEOUT |
+                                             SPLASH_CENTRE_ON_SCREEN),
+                                style=(wx.SIMPLE_BORDER |
+                                       wx.FRAME_NO_TASKBAR |
+                                       wx.FRAME_FLOAT_ON_PARENT),
+                                milliseconds=SS_MAX_DISPLAY_TIME,
+                                parent=parent,
+                                id=wx.ID_ANY)
         from sas.sasgui.guiframe.gui_statusbar import SPageStatusbar
         statusBar = SPageStatusbar(s_screen)
Index: src/sas/sasgui/guiframe/gui_statusbar.py
===================================================================
--- src/sas/sasgui/guiframe/gui_statusbar.py	(revision 463e7ffce130ae386f93cd18b87a1120743e88b5)
+++ src/sas/sasgui/guiframe/gui_statusbar.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -86,5 +86,7 @@
                                                     wx.ART_TOOLBAR)
         self.msg_txt.Newline()
-        self.msg_txt.WriteBitmap(icon_bmp)
+        # wx 3 has WriteImage and WriteBitmap; not sure if there
+        # is any difference between them.  wx 4 has only WriteImage.
+        self.msg_txt.WriteImage(icon_bmp)
         self.msg_txt.BeginTextColour(color)
         self.msg_txt.WriteText("\t")
Index: src/sas/sasgui/guiframe/gui_style.py
===================================================================
--- src/sas/sasgui/guiframe/gui_style.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sasgui/guiframe/gui_style.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -58,24 +58,30 @@
     DRAG_ID_PATH = os.path.join(PATH, 'drag_hand.png')
     RESET_ID_PATH = os.path.join(PATH, 'reset.png')
-    PREVIEW_ID_PATH = os.path.join(PATH, 'report.png')
+    REPORT_ID_PATH = os.path.join(PATH, 'report.png')
+    PREVIEW_ID_PATH = os.path.join(PATH, 'preview.png')
     PRINT_ID_PATH = os.path.join(PATH, 'printer.png')
     HIDE_ID_PATH = os.path.join(PATH, 'hide.png')
     
-  
-    SAVE_ICON = wx.Image(os.path.join(PATH, 'save.png'))
-    UNDO_ICON = wx.Image(os.path.join(PATH, 'undo.png'))
-    REDO_ICON = wx.Image(os.path.join(PATH, 'redo.png'))
-    COPY_ICON = wx.Image(os.path.join(PATH, 'copy.png'))
-    PASTE_ICON = wx.Image(os.path.join(PATH, 'paste.png'))
-    BOOKMARK_ICON = wx.Image(os.path.join(PATH, 'bookmark.png'))
-    ZOOM_IN_ICON = wx.Image(os.path.join(PATH, 'zoom_in.png'))
-    ZOOM_OUT_ICON = wx.Image(os.path.join(PATH, 'zoom_out.png'))
-    ZOOM_ICON = wx.Image(os.path.join(PATH, 'search_pan.png'))
-    DRAG_ICON = wx.Image(os.path.join(PATH, 'drag_hand.png'))
-    RESET_ICON = wx.Image(os.path.join(PATH, 'reset.png'))
-    REPORT_ICON = wx.Image(os.path.join(PATH, 'report.png'))
-    PREVIEW_ICON = wx.Image(os.path.join(PATH, 'preview.png'))
-    PRINT_ICON = wx.Image(os.path.join(PATH, 'printer.png'))
-    HIDE_ICON = wx.Image(os.path.join(PATH, 'hide.png'))
+    # Note: wx 4 requires that the app be defined before
+    # the images are loaded, so they can't be processed
+    # at module load time.  Instead, need to load icons
+    # when the app is created.
+    @classmethod
+    def load_icons(cls):
+        cls.SAVE_ICON = wx.Image(cls.SAVE_ICON_PATH)
+        cls.UNDO_ICON = wx.Image(cls.UNDO_ICON_PATH)
+        cls.REDO_ICON = wx.Image(cls.REDO_ICON_PATH)
+        cls.COPY_ICON = wx.Image(cls.COPY_ICON_PATH)
+        cls.PASTE_ICON = wx.Image(cls.PASTE_ICON_PATH)
+        cls.BOOKMARK_ICON = wx.Image(cls.BOOKMARK_ICON_PATH)
+        cls.ZOOM_IN_ICON = wx.Image(cls.ZOOM_IN_ID_PATH)
+        cls.ZOOM_OUT_ICON = wx.Image(cls.ZOOM_OUT_ID_PATH)
+        cls.ZOOM_ICON = wx.Image(cls.ZOOM_ID_PATH)
+        cls.DRAG_ICON = wx.Image(cls.DRAG_ID_PATH)
+        cls.RESET_ICON = wx.Image(cls.RESET_ID_PATH)
+        cls.REPORT_ICON = wx.Image(cls.REPORT_ID_PATH)
+        cls.PREVIEW_ICON = wx.Image(cls.PREVIEW_ID_PATH)
+        cls.PRINT_ICON = wx.Image(cls.PRINT_ID_PATH)
+        cls.HIDE_ICON = wx.Image(cls.HIDE_ID_PATH)
 
 if __name__ == "__main__":
Index: src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py	(revision 9c7e2b8361553f8e2f246d344c791c385f3b2254)
+++ src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -70,5 +70,5 @@
                 cards.remove(item)
         wlist = '|'.join(cards)
-        style = wx.OPEN | wx.FD_MULTIPLE
+        style = wx.FD_OPEN | wx.FD_MULTIPLE
         dlg = wx.FileDialog(self.parent,
                             "Choose a file",
@@ -196,5 +196,5 @@
                                                       error_message)
                     if data_error:
-                        if basename in file_errors.keys():
+                        if basename in file_errors:
                             file_errors[basename] += [error_message]
                         else:
@@ -219,5 +219,5 @@
         if len(file_errors) > 0:
             error_message = ""
-            for filename, error_array in file_errors.iteritems():
+            for filename, error_array in file_errors.items():
                 error_message += "The following issues were found whilst "
                 error_message += "loading {}:\n".format(filename)
Index: src/sas/sasgui/guiframe/local_perspectives/data_loader/load_thread.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/data_loader/load_thread.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/local_perspectives/data_loader/load_thread.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -72,7 +72,7 @@
                 if self.updatefn is not None:
                     self.updatefn(output=output, message=message)
-            except:
+            except Exception as exc:
                 error_message = "Error while loading: %s\n" % str(path)
-                error_message += str(sys.exc_value) + "\n"
+                error_message += str(exc) + "\n"
                 self.updatefn(output=output, message=error_message)
 
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/AnnulusSlicer.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/AnnulusSlicer.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/AnnulusSlicer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -7,13 +7,15 @@
 
 import math
+# from copy import deepcopy
+
 import wx
-# from copy import deepcopy
-# Debug printout
+
 from sas.sasgui.guiframe.events import NewPlotEvent
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.events import SlicerParameterEvent
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS
-from BaseInteractor import _BaseInteractor
 from sas.sasgui.guiframe.dataFitting import Data1D
+
+from .BaseInteractor import _BaseInteractor
 
 class AnnulusInteractor(_BaseInteractor):
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/Arc.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/Arc.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/Arc.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -4,6 +4,7 @@
 import math
 
-from BaseInteractor import _BaseInteractor
 from sas.sasgui.guiframe.events import SlicerParameterEvent
+
+from .BaseInteractor import _BaseInteractor
 
 class ArcInteractor(_BaseInteractor):
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/AzimutSlicer.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/AzimutSlicer.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/AzimutSlicer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -5,8 +5,11 @@
 #
 import math
+
 import wx
-from BaseInteractor import _BaseInteractor
+
 from sas.sasgui.guiframe.events import NewPlotEvent
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS
+
+from .BaseInteractor import _BaseInteractor
 
 class SectorInteractor(_BaseInteractor):
@@ -29,5 +32,5 @@
 
         # Inner circle
-        from Arc import ArcInteractor
+        from .Arc import ArcInteractor
         self.inner_circle = ArcInteractor(self, self.base.subplot,
                                           zorder=zorder,
@@ -43,5 +46,5 @@
         self.outer_circle.qmax = self.qmax * 1.2
         # self.outer_circle.set_cursor(self.base.qmax/1.8, 0)
-        from Edge import RadiusInteractor
+        from .Edge import RadiusInteractor
         self.right_edge = RadiusInteractor(self, self.base.subplot,
                                            zorder=zorder + 1,
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/Edge.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/Edge.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/Edge.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,4 +1,5 @@
 import math
-from BaseInteractor import _BaseInteractor
+
+from .BaseInteractor import _BaseInteractor
 
 
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter1D.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter1D.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter1D.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -11,9 +11,11 @@
 
 
-import wx
 import sys
 import math
+import logging
+
+import wx
 import numpy as np
-import logging
+
 from sas.sasgui.plottools.PlotPanel import PlotPanel
 from sas.sasgui.guiframe.events import StatusEvent
@@ -22,6 +24,7 @@
 from sas.sasgui.guiframe.panel_base import PanelBase
 from sas.sasgui.guiframe.gui_style import GUIFRAME_ICON
-from appearanceDialog import appearanceDialog
-from graphAppearance import graphAppearance
+
+from .appearanceDialog import appearanceDialog
+from .graphAppearance import graphAppearance
 
 logger = logging.getLogger(__name__)
@@ -36,5 +39,5 @@
 def find_key(dic, val):
     """return the key of dictionary dic given the value"""
-    return [k for k, v in dic.iteritems() if v == val][0]
+    return [k for k, v in dic.items() if v == val][0]
 
 class ModelPanel1D(PlotPanel, PanelBase):
@@ -220,5 +223,5 @@
         if hasattr(event, 'is_corfunc'):
             self.is_corfunc = event.is_corfunc
-        if event.id in self.plots.keys():
+        if event.id in self.plots:
             ctrl = event.ctrl
             self.cursor_id = event.id
@@ -243,6 +246,6 @@
                 if position is not None and not self.is_corfunc:
                     wx.PostEvent(self.parent, StatusEvent(status=position))
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
             if not event.leftdown:
                 # text event
@@ -256,6 +259,6 @@
                     if is_moved:
                         self.canvas.draw()
-                except:
-                    logger.error(sys.exc_value)
+                except Exception as exc:
+                    logger.error(exc)
                 event.Skip()
                 return
@@ -410,6 +413,6 @@
             self.canvas.draw()
             self.q_ctrl[vl_ind].SetValue(str(pos_x))
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
     def set_resizing(self, resizing=False):
@@ -431,5 +434,5 @@
             Remove data from plot
         """
-        if id in self.plots.keys():
+        if id in self.plots:
             data = self.plots[id]
             self.graph.delete(data)
@@ -437,7 +440,7 @@
             data_list, theory_list = data_manager.get_by_id(id_list=[id])
 
-            if id in data_list.keys():
+            if id in data_list:
                 data = data_list[id]
-            if id in theory_list.keys():
+            if id in theory_list:
                 data = theory_list[id]
 
@@ -457,5 +460,5 @@
         if data.__class__.__name__ == 'Data2D':
             return
-        plot_keys = self.plots.keys()
+        plot_keys = list(self.plots.keys())
         if data.id in plot_keys:
             # Recover panel prop.s
@@ -476,5 +479,5 @@
             try:
                 self._onEVT_FUNC_PROPERTY()
-            except Exception, exc:
+            except Exception as exc:
                 wx.PostEvent(self.parent,
                              StatusEvent(status="Plotting Error: %s" % str(exc), info="error"))
@@ -493,5 +496,5 @@
                     # MAC: forcing to plot 2D avg
                     self.canvas._onDrawIdle()
-            except Exception, exc:
+            except Exception as exc:
                 wx.PostEvent(self.parent, StatusEvent(status=\
                     "Plotting Error: %s" % str(exc), info="error"))
@@ -567,5 +570,5 @@
         self.set_selected_from_menu(menu, event_id)
         ## Check if there is a selected graph to remove
-        if self.graph.selected_plottable in self.plots.keys():
+        if self.graph.selected_plottable in self.plots:
             graph_id = self.graph.selected_plottable
             self.remove_data_by_id(graph_id)
@@ -603,5 +606,5 @@
             plot_menu = wx.Menu()
             if self.graph.selected_plottable:
-                if not self.graph.selected_plottable in self.plots.keys():
+                if not self.graph.selected_plottable in self.plots:
                     continue
                 if plot != self.plots[self.graph.selected_plottable]:
@@ -623,7 +626,7 @@
                         plot_menu.Append(wx_id, item[0], name)
                         wx.EVT_MENU(self, wx_id, item[2])
-                    except:
+                    except Exception as exc:
                         msg = "ModelPanel1D.onContextMenu: "
-                        msg += "bad menu item  %s" % sys.exc_value
+                        msg += "bad menu item  %s" % exc
                         wx.PostEvent(self.parent, StatusEvent(status=msg))
                 plot_menu.AppendSeparator()
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter2D.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter2D.py	(revision 3e5648b0f738718f823af66d5095c1cc74103d39)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/Plotter2D.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -11,9 +11,12 @@
 
 
-import wx
 import sys
 import math
+import logging
+
+import wx
 import numpy as np
-import logging
+from matplotlib.font_manager import FontProperties
+
 from sas.sasgui.plottools.PlotPanel import PlotPanel
 from sas.sasgui.plottools.plottables import Graph
@@ -24,9 +27,10 @@
 from sas.sasgui.guiframe.events import SlicerEvent
 from sas.sasgui.guiframe.utils import PanelMenu
-from  sas.sasgui.guiframe.local_perspectives.plotting.binder import BindArtist
-from Plotter1D import ModelPanel1D
+from sas.sasgui.guiframe.local_perspectives.plotting.binder import BindArtist
 from sas.sasgui.plottools.toolbar import NavigationToolBar
-from matplotlib.font_manager import FontProperties
-from graphAppearance import graphAppearance
+
+from .Plotter1D import ModelPanel1D
+from .graphAppearance import graphAppearance
+
 (InternalEvent, EVT_INTERNAL) = wx.lib.newevent.NewEvent()
 
@@ -41,5 +45,5 @@
 def find_key(dic, val):
     """return the key of dictionary dic given the value"""
-    return [k for k, v in dic.iteritems() if v == val][0]
+    return [k for k, v in dic.items() if v == val][0]
 
 
@@ -200,5 +204,5 @@
         ## Update self.data2d with the current plot
         self.data2D = data
-        if data.id in self.plots.keys():
+        if data.id in self.plots:
             #replace
             xlo, xhi = self.subplot.get_xlim()
@@ -326,7 +330,7 @@
                         slicerpop.Append(wx_id, item[0], item[1])
                         wx.EVT_MENU(self, wx_id, item[2])
-                    except:
+                    except Exception as exc:
                         msg = "ModelPanel1D.onContextMenu: "
-                        msg += "bad menu item  %s" % sys.exc_value
+                        msg += "bad menu item  %s" % exc
                         wx.PostEvent(self.parent, StatusEvent(status=msg))
                 slicerpop.AppendSeparator()
@@ -447,5 +451,5 @@
 
         """
-        import detector_dialog
+        from . import detector_dialog
         dialog = detector_dialog.DetectorDialog(self, -1, base=self.parent,
                                                 reset_zmin_ctl=self.default_zmin_ctl,
@@ -630,5 +634,5 @@
         """
         if self.slicer is not None:
-            from parameters_panel_slicer import SlicerParameterPanel
+            from .parameters_panel_slicer import SlicerParameterPanel
             dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
             dialog.set_slicer(self.slicer.__class__.__name__,
@@ -641,5 +645,5 @@
         Perform sector averaging on Q and draw sector slicer
         """
-        from SectorSlicer import SectorInteractor
+        from .SectorSlicer import SectorInteractor
         self.onClearSlicer(event)
         wx.PostEvent(self, InternalEvent(slicer=SectorInteractor))
@@ -649,5 +653,5 @@
         Perform sector averaging on Phi and draw annulus slicer
         """
-        from AnnulusSlicer import AnnulusInteractor
+        from .AnnulusSlicer import AnnulusInteractor
         self.onClearSlicer(event)
         wx.PostEvent(self, InternalEvent(slicer=AnnulusInteractor))
@@ -657,5 +661,5 @@
         """
         from sas.sasgui.guiframe.gui_manager import MDIFrame
-        from boxSum import BoxSum
+        from .boxSum import BoxSum
         self.onClearSlicer(event)
         self.slicer_z += 1
@@ -668,5 +672,5 @@
         params = self.slicer.get_params()
         ## Create a new panel to display results of summation of Data2D
-        from parameters_panel_boxsum import SlicerPanel
+        from .parameters_panel_boxsum import SlicerPanel
         win = MDIFrame(self.parent, None, 'None', (100, 200))
         new_panel = SlicerPanel(parent=win, id=-1,
@@ -699,5 +703,5 @@
         :param event: wx.menu event
         """
-        from boxSlicer import BoxInteractorX
+        from .boxSlicer import BoxInteractorX
         self.onClearSlicer(event)
         wx.PostEvent(self, InternalEvent(slicer=BoxInteractorX))
@@ -711,5 +715,5 @@
 
         """
-        from boxSlicer import BoxInteractorY
+        from .boxSlicer import BoxInteractorY
         self.onClearSlicer(event)
         wx.PostEvent(self, InternalEvent(slicer=BoxInteractorY))
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/SectorSlicer.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/SectorSlicer.py	(revision 8de66b66b52c8e66ba582290c53d3257a6fa6401)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/SectorSlicer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -3,6 +3,7 @@
 """
 import math
+
 import wx
-from BaseInteractor import _BaseInteractor
+
 from sas.sasgui.guiframe.events import NewPlotEvent
 from sas.sasgui.guiframe.events import StatusEvent
@@ -10,4 +11,6 @@
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS
 from sas.sasgui.guiframe.dataFitting import Data1D
+
+from .BaseInteractor import _BaseInteractor
 
 
@@ -238,5 +241,5 @@
             msg = "Phi left and phi right are different"
             msg += " %f, %f" % (self.left_line.phi, self.right_line.phi)
-            raise ValueError, msg
+            raise ValueError(msg)
         params["Phi [deg]"] = self.main_line.theta * 180 / math.pi
         params["Delta_Phi [deg]"] = math.fabs(self.left_line.phi * 180 / math.pi)
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/appearanceDialog.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/appearanceDialog.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/appearanceDialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -153,5 +153,5 @@
         Populate Symbols
         """
-        self.sorted_symbo_labels = sorted(self.symbo_labels.iteritems(),
+        self.sorted_symbo_labels = sorted(self.symbo_labels.items(),
                                           key=operator.itemgetter(1))
         self.sorted_sym_dic = {}
@@ -166,5 +166,5 @@
         Populate Colors
         """
-        sortedcolor_labels = sorted(self.color_labels.iteritems(),
+        sortedcolor_labels = sorted(self.color_labels.items(),
                                     key=operator.itemgetter(1))
         for color in sortedcolor_labels:
@@ -197,5 +197,5 @@
         Find key
         """
-        return [k for k, v in dic.iteritems() if v == val][0]
+        return [k for k, v in dic.items() if v == val][0]
 
     def get_current_values(self):
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/binder.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/binder.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/binder.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,6 +2,8 @@
 Extension to MPL to support the binding of artists to key/mouse events.
 """
+from __future__ import print_function
+
+import sys
 import logging
-import sys
 
 logger = logging.getLogger(__name__)
@@ -16,4 +18,5 @@
     artist = None
     prop = {}
+
     def __init__(self, artist=None, prop={}):
         self.artist, self.prop = artist, self.prop
@@ -25,15 +28,18 @@
         return self.artist is not other.artist
 
-    def __nonzero__(self):
+    def __bool__(self):
         return self.artist is not None
+
+    __nonzero__ = __bool__
+
 
 class BindArtist(object):
     """
-        Track keyboard modifiers for events.
-        TODO: Move keyboard modifier support into the backend.  We cannot
-        TODO: properly support it from outside the windowing system since there
-        TODO: is no way to recognized whether shift is held down when the mouse
-        TODO: first clicks on the the application window.
+    Track keyboard modifiers for events.
     """
+    # TODO: Move keyboard modifier support into the backend.  We cannot
+    # TODO: properly support it from outside the windowing system since there
+    # TODO: is no way to recognized whether shift is held down when the mouse
+    # TODO: first clicks on the the application window.
     control, shift, alt, meta = False, False, False, False
 
@@ -46,6 +52,8 @@
               'scroll', 'key', 'keyup']
     # TODO: Need our own event structure
+
     def __init__(self, figure):
         canvas = figure.canvas
+
         # Link to keyboard/mouse
         try:
@@ -59,5 +67,5 @@
             ]
         except:
-            # print "bypassing scroll_event: wrong matplotlib version"
+            logger.warn("bypassing scroll_event: wrong matplotlib version")
             self._connections = [
                 canvas.mpl_connect('motion_notify_event', self._onMotion),
@@ -67,10 +75,14 @@
                 canvas.mpl_connect('key_release_event', self._onKeyRelease),
             ]
+
         # Turn off picker if it hasn't already been done
         try:
             canvas.mpl_disconnect(canvas.button_pick_id)
             canvas.mpl_disconnect(canvas.scroll_pick_id)
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
+
+        self._current = None
+        self._actions = {}
         self.canvas = canvas
         self.figure = figure
@@ -83,5 +95,4 @@
 
         Use clearall() to reset all connections.
-
         """
         for h in artists:
@@ -108,4 +119,5 @@
         for action in self.events:
             self._actions[action] = {}
+
         # Need activity state
         self._artists = []
@@ -121,6 +133,6 @@
             for cid in self._connections:
                 self.canvas.mpl_disconnect(cid)
-        except:
-            pass
+        except Exception as exc:
+            logger.error("Error disconnection canvas: %s" % exc)
         self._connections = []
 
@@ -169,28 +181,26 @@
         sure it applies.  E.g., the callback for 'press' might be:
             if event.button == 1 and event.shift: process Shift-click
-
-        :TODO: Only receive events with the correct modifiers (e.g., S-click,
-        :TODO:   or *-click for any modifiers).
-        :TODO: Only receive button events for the correct button (e.g., click1
-        :TODO:   release3, or dclick* for any button)
-        :TODO: Support virtual artist, so that and artist can be flagged as
-        :TODO:   having a tag list and receive the correct events
-        :TODO: Support virtual events for binding to button-3 vs shift button-1
-        :TODO:   without changing callback code
-        :TODO: Attach multiple callbacks to the same event?
-        :TODO: Clean up interaction with toolbar modes
-        :TODO: push/pushclear/pop context so that binding changes
-            for the duration
-        :TODO:   e.g., to support ? context sensitive help
-
-        """
+        """
+        #TODO: Only receive events with the correct modifiers (e.g., S-click,
+        #TODO:   or *-click for any modifiers).
+        #TODO: Only receive button events for the correct button (e.g., click1
+        #TODO:   release3, or dclick* for any button)
+        #TODO: Support virtual artist, so that and artist can be flagged as
+        #TODO:   having a tag list and receive the correct events
+        #TODO: Support virtual events for binding to button-3 vs shift button-1
+        #TODO:   without changing callback code
+        #TODO: Attach multiple callbacks to the same event?
+        #TODO: Clean up interaction with toolbar modes
+        #TODO: push/pushclear/pop context so that binding changes for the duration
+        #TODO:   e.g., to support ? context sensitive help
+
         # Check that the trigger is valid
         if trigger not in self._actions:
-            raise ValueError, "%s invalid --- valid triggers are %s" \
-                % (trigger, ", ".join(self.events))
+            raise ValueError("%s invalid --- valid triggers are %s"
+                % (trigger, ", ".join(self.events)))
+
         # Register the trigger callback
         self._actions[trigger][artist] = action
-        # print "==> added",artist,[artist],"to",trigger,":",
-        # self._actions[trigger].keys()
+
         # Maintain a list of all artists
         if artist not in self._artists:
@@ -203,5 +213,6 @@
         """
         if action not in self.events:
-            raise ValueError, "Trigger expects " + ", ".join(self.events)
+            raise ValueError("Trigger expects " + ", ".join(self.events))
+
         # Tag the event with modifiers
         for mod in ('alt', 'control', 'shift', 'meta'):
@@ -210,4 +221,5 @@
         setattr(ev, 'action', action)
         setattr(ev, 'prop', {})
+
         # Fallback scheme. If the event does not return false, pass to parent.
         processed = False
@@ -233,8 +245,6 @@
         """
         # TODO: sort by zorder of axes then by zorder within axes
-        self._artists.sort(cmp=lambda x, y: cmp(y.zorder, x.zorder))
-        # print "search"," ".join([str(h) for h in self._artists])
+        self._artists.sort(key=lambda x: x.zorder, reverse=True)
         found = Selection()
-        # print "searching in",self._artists
         for artist in self._artists:
             # TODO: should contains() return false if invisible?
@@ -250,5 +260,4 @@
                 found.artist, found.prop = artist, prop
                 break
-        # print "found",found.artist
 
         # TODO: how to check if prop is equal?
@@ -257,4 +266,5 @@
             self.trigger(found, 'enter', event)
         self._current = found
+
         return found
 
@@ -274,16 +284,15 @@
 
             transform = self._hasclick.artist.get_transform()
-            # x,y = event.xdata,event.ydata
+            #x,y = event.xdata,event.ydata
             x, y = event.x, event.y
             try:
                 x, y = transform.inverted().transform_point((x, y))
-
-            except:
+            except: # CRUFT: matplotlib-0.91 support
                 x, y = transform.inverse_xy_tup((x, y))
+
             event.xdata, event.ydata = x, y
             self.trigger(self._hasclick, 'drag', event)
         else:
             found = self._find_current(event)
-            # print "found",found.artist
             self.trigger(found, 'motion', event)
 
@@ -296,7 +305,4 @@
         # Check for double-click
         event_time = time.time()
-        # print event_time,self._last_time,self.dclick_threshhold
-        # print (event_time > self._last_time + self.dclick_threshhold)
-        # print event.button,self._last_button
         if (event.button != self._last_button) or \
                 (event_time > self._last_time + self.dclick_threshhold):
@@ -381,4 +387,5 @@
             setattr(self, event.key, False)
             return
+
         if self._haskey:
             self.trigger(self._haskey, 'keyup', event)
@@ -391,3 +398,2 @@
         found = self._find_current(event)
         self.trigger(found, 'scroll', event)
-
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/boxMask.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/boxMask.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/boxMask.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,7 +1,7 @@
 import math
-from BaseInteractor import _BaseInteractor
-from boxSum import PointInteractor
-from boxSum import VerticalDoubleLine
-from boxSum import HorizontalDoubleLine
+from .BaseInteractor import _BaseInteractor
+from .boxSum import PointInteractor
+from .boxSum import VerticalDoubleLine
+from .boxSum import HorizontalDoubleLine
 
 
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/boxSlicer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,11 +1,14 @@
+import math
+
 import wx
-import math
 import numpy as np
+
 from sas.sasgui.guiframe.events import NewPlotEvent
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.events import SlicerParameterEvent
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS
-from BaseInteractor import _BaseInteractor
 from sas.sasgui.guiframe.dataFitting import Data1D
+
+from .BaseInteractor import _BaseInteractor
 
 
@@ -152,5 +155,5 @@
             if new_slab is None:
                 msg = "post data:cannot average , averager is empty"
-                raise ValueError, msg
+                raise ValueError(msg)
             self.averager = new_slab
         if self.direction == "X":
@@ -168,5 +171,5 @@
         else:
             msg = "post data:no Box Average direction was supplied"
-            raise ValueError, msg
+            raise ValueError(msg)
         # # Average data2D given Qx or Qy
         box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max,
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/boxSum.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/boxSum.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/boxSum.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -4,9 +4,12 @@
 """
 import math
+
 import wx
-from BaseInteractor import _BaseInteractor
+
 from sas.sasgui.guiframe.events import SlicerParamUpdateEvent
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS
 from sas.sasgui.guiframe.events import StatusEvent
+
+from .BaseInteractor import _BaseInteractor
 
 
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/detector_dialog.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/detector_dialog.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/detector_dialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -99,6 +99,6 @@
             self.cmap_selector.SetStringSelection("jet")
             self._on_select_cmap(event=None)
-        except:
-            msg = "error occurs while resetting Detector: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "error occurs while resetting Detector: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg))
 
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/masking.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/masking.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/masking.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -18,22 +18,25 @@
 
 # #Todo: cleaning up, improving the maskplotpanel initialization, and testing.
-import wx
 import sys
 import time
-import matplotlib.cm as cm
 import math
 import copy
+from functools import partial
+
+import wx
 import numpy as np
+import matplotlib.cm as cm
+
 from sas.sasgui.plottools.PlotPanel import PlotPanel
 from sas.sasgui.plottools.plottables import Graph
-from binder import BindArtist
 from sas.sasgui.guiframe.dataFitting import Data1D, Data2D
-from boxMask import BoxMask
-from sector_mask import SectorMask
-from AnnulusSlicer import CircularMask
-
 from sas.sasgui.guiframe.events import SlicerEvent
 from sas.sasgui.guiframe.events import StatusEvent
-from functools import partial
+
+from .binder import BindArtist
+from .boxMask import BoxMask
+from .sector_mask import SectorMask
+from .AnnulusSlicer import CircularMask
+
 
 (InternalEvent, EVT_INTERNAL) = wx.lib.newevent.NewEvent()
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_boxsum.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_boxsum.py	(revision 37d461c81bb3405cd6efc6bdadfe0ecd6dc5b338)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_boxsum.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,5 +1,5 @@
 import wx
 import wx.lib.newevent
-from parameters_panel_slicer import SlicerParameterPanel
+
 from sas.sasgui.guiframe.utils import format_number
 from sas.sasgui.guiframe.panel_base import PanelBase
@@ -7,4 +7,5 @@
                                         EVT_SLICER)
 
+from .parameters_panel_slicer import SlicerParameterPanel
 
 class SlicerPanel(wx.Panel, PanelBase):
@@ -64,6 +65,5 @@
             n = 1
             self.parameters = []
-            keys = params.keys()
-            keys.sort()
+            keys = list(sorted(params.keys()))
             for item in keys:
                 if not item.lower() in ["num_points", "avg", "avg_error", "sum",
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_slicer.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_slicer.py	(revision a20a25573879c0953c15607f10eb7cf47e80c047)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/parameters_panel_slicer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,11 +2,15 @@
 
 import os
+
 import wx
 import wx.lib.newevent
+
 from sas.sascalc.dataloader.readers.cansas_reader import Reader
 from sas.sasgui.guiframe.utils import format_number
 from sas.sasgui.guiframe.events import EVT_SLICER_PARS, EVT_SLICER
 from sas.sasgui.guiframe.events import SlicerParameterEvent, StatusEvent
-from Plotter2D import ModelPanel2D
+
+from .Plotter2D import ModelPanel2D
+
 apply_params, EVT_APPLY_PARAMS = wx.lib.newevent.NewEvent()
 save_files, EVT_AUTO_SAVE = wx.lib.newevent.NewEvent()
@@ -100,6 +104,5 @@
             iy = 1
             self.parameters = []
-            keys = params.keys()
-            keys.sort()
+            keys = list(sorted(params.keys()))
             for item in keys:
                 ix = 0
@@ -127,5 +130,5 @@
                     self.bck.Add(text, (iy, ix), (1, 1),
                                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
-                    options = BINNING_OPTIONS.keys()
+                    options = list(BINNING_OPTIONS.keys())
                     self.bin_ctl = wx.ComboBox(parent=self, choices=options)
                     hint_msg = "Modify the value of %s to change" % item
@@ -325,5 +328,5 @@
 
         # Find desired 2D data panels
-        for key, mgr in spp.plot_panels.iteritems():
+        for key, mgr in spp.plot_panels.items():
             if mgr.graph.prop['title'] in self.data_list.CheckedStrings:
                 apply_to_list.append(mgr)
@@ -377,5 +380,5 @@
         self.loaded_data = []
         # Iterate over the loaded plots and find all 2D panels
-        for key, value in self.main_window.plot_panels.iteritems():
+        for key, value in self.main_window.plot_panels.items():
             if isinstance(value, ModelPanel2D):
                 self.loaded_data.append(value.data2D.name)
@@ -453,5 +456,5 @@
 
         # Find the correct plots to save
-        for key, plot in self.main_window.plot_panels.iteritems():
+        for key, plot in self.main_window.plot_panels.items():
             if not hasattr(plot, "data2D"):
                 for item in plot.plots:
@@ -461,5 +464,5 @@
 
         # Save files as Text
-        for item, data1d in data_dic.iteritems():
+        for item, data1d in data_dic.items():
             base = '.'.join(item.split('.')[:-1])
             file_name = base + append + ".txt"
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/plotting.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/plotting.py	(revision 2d9526dfda3bdfe3c9159ab7d9db8399b4a00848)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/plotting.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -12,7 +12,9 @@
 ################################################################################
 
-import wx
 import sys
 from copy import deepcopy
+
+import wx
+
 from sas.sasgui.guiframe.events import EVT_NEW_PLOT
 from sas.sasgui.guiframe.events import EVT_PLOT_QRANGE
@@ -23,4 +25,5 @@
 from sas.sasgui.guiframe.dataFitting import Data2D
 from sas.sasgui.guiframe.gui_manager import MDIFrame
+
 DEFAULT_MENU_ITEM_LABEL = "No graph available"
 DEFAULT_MENU_ITEM_ID = wx.NewId()
@@ -91,7 +94,7 @@
         if event is None:
             return
-        if event.id in self.plot_panels.keys():
+        if event.id in self.plot_panels:
             panel = self.plot_panels[event.id]
-        elif event.group_id in self.plot_panels.keys():
+        elif event.group_id in self.plot_panels:
             panel = self.plot_panels[event.group_id]
         else:
@@ -102,7 +105,7 @@
         if event is None:
             return
-        if event.id in self.plot_panels.keys():
+        if event.id in self.plot_panels:
             panel = self.plot_panels[event.id]
-        elif event.group_id in self.plot_panels.keys():
+        elif event.group_id in self.plot_panels:
             panel = self.plot_panels[event.group_id]
         else:
@@ -123,5 +126,5 @@
         """
 
-        if group_id in self.plot_panels.keys():
+        if group_id in self.plot_panels:
             panel = self.plot_panels[group_id]
             panel.remove_data_by_id(id=id)
@@ -134,5 +137,5 @@
         Clear and Hide all plot panels, and remove them from menu
         """
-        for group_id in self.plot_panels.keys():
+        for group_id in self.plot_panels:
             self.clear_panel_by_id(group_id)
         self.plot_panels = {}
@@ -142,5 +145,5 @@
         clear the graph
         """
-        if group_id in self.plot_panels.keys():
+        if group_id in self.plot_panels:
             panel = self.plot_panels[group_id]
             for plottable in panel.graph.plottables.keys():
@@ -184,5 +187,5 @@
         # Create a new plot panel if none was available
         if issubclass(data.__class__, Data1D):
-            from Plotter1D import ModelPanel1D
+            from .Plotter1D import ModelPanel1D
             ## get the data representation label of the data to plot
             ## when even the user select "change scale"
@@ -200,5 +203,5 @@
 
         msg = "1D Panel of group ID %s could not be created" % str(group_id)
-        raise ValueError, msg
+        raise ValueError(msg)
 
     def create_2d_panel(self, data, group_id):
@@ -207,5 +210,5 @@
         if issubclass(data.__class__, Data2D):
             ##Create a new plotpanel for 2D data
-            from Plotter2D import ModelPanel2D
+            from .Plotter2D import ModelPanel2D
             scale = data.scale
             win = MDIFrame(self.parent, None, 'None', (200, 150))
@@ -218,5 +221,5 @@
             return new_panel
         msg = "2D Panel of group ID %s could not be created" % str(group_id)
-        raise ValueError, msg
+        raise ValueError(msg)
 
     def update_panel(self, data, panel):
@@ -238,5 +241,5 @@
             msg += " to panel %s\n" % str(panel.window_caption)
             msg += "Please edit %s's units, labels" % str(data.name)
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             if panel.group_id not in data.list_group_id:
@@ -247,5 +250,5 @@
         """
         """
-        if group_id in self.plot_panels.keys():
+        if group_id in self.plot_panels:
             panel = self.plot_panels[group_id]
             uid = panel.uid
@@ -254,5 +257,5 @@
                                               caption=panel.window_caption))
             del self.plot_panels[group_id]
-            if uid in self.parent.plot_panels.keys():
+            if uid in self.parent.plot_panels:
                 del self.parent.plot_panels[uid]
                 panel.frame.Destroy()
@@ -280,5 +283,5 @@
                     for data in event.plots:
                         for panel in self.plot_panels.values():
-                            if data.id in panel.plots.keys():
+                            if data.id in panel.plots:
                                 plot_exists = True
                                 # Pass each panel it's own copy of the data
@@ -310,8 +313,8 @@
         data = event.plot
         group_id = data.group_id
-        if group_id in self.plot_panels.keys():
+        if group_id in self.plot_panels:
             if action_check:
                 # Check if the plot already exist. if it does, do nothing.
-                if data.id in self.plot_panels[group_id].plots.keys():
+                if data.id in self.plot_panels[group_id].plots:
                     return
             #update a panel graph
@@ -327,5 +330,5 @@
                     for p_group_id in self.plot_panels.keys():
                         p_plot = self.plot_panels[p_group_id]
-                        if data.id in p_plot.plots.keys():
+                        if data.id in p_plot.plots:
                             p_plot.plots[data.id] = data
                             self.plot_panels[group_id] = p_plot
Index: src/sas/sasgui/guiframe/local_perspectives/plotting/sector_mask.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/plotting/sector_mask.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/local_perspectives/plotting/sector_mask.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -3,10 +3,12 @@
 """
 import math
+
 import wx
-#from copy import deepcopy
-from BaseInteractor import _BaseInteractor
-from SectorSlicer import SideInteractor
-from SectorSlicer import LineInteractor
+
 from sas.sasgui.guiframe.events import SlicerParameterEvent
+
+from .BaseInteractor import _BaseInteractor
+from .SectorSlicer import SideInteractor
+from .SectorSlicer import LineInteractor
 
 class SectorMask(_BaseInteractor):
@@ -175,5 +177,5 @@
             msg += "different %f, %f" % (self.left_line.phi,
                                          self.right_line.phi)
-            raise ValueError, msg
+            raise ValueError(msg)
         params["Phi"] = self.main_line.theta
         params["Delta_Phi"] = math.fabs(self.left_line.phi)
Index: src/sas/sasgui/guiframe/plugin_base.py
===================================================================
--- src/sas/sasgui/guiframe/plugin_base.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/plugin_base.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -112,5 +112,5 @@
         Load  data
         """
-        raise NotImplementedError
+        raise NotImplementedError()
 
     def load_folder(self, event):
@@ -118,5 +118,5 @@
         Load entire folder
         """
-        raise NotImplementedError
+        raise NotImplementedError()
 
     def set_is_active(self, active=False):
@@ -277,5 +277,5 @@
         """
         msg = "%s plugin: does not support import theory" % str(self.sub_menu)
-        raise ValueError, msg
+        raise ValueError(msg)
 
     def on_set_state_helper(self, event):
Index: src/sas/sasgui/guiframe/proxy.py
===================================================================
--- src/sas/sasgui/guiframe/proxy.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/guiframe/proxy.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -3,5 +3,4 @@
 from __future__ import print_function
 
-import urllib2
 import sys
 import json
@@ -9,4 +8,9 @@
 import re
 
+try:
+    # CRUFT: python 3 uses urllib.request instead of urllib2
+    import urllib2
+except ImportError:
+    from urllib import request as urllib2
 
 logger = logging.getLogger(__name__)
@@ -50,5 +54,5 @@
                 this_name, this_val, this_type = winreg.EnumValue(net, i)
                 subkeys[this_name] = this_val
-            if 'AutoConfigURL' in subkeys.keys() and len(subkeys['AutoConfigURL']) > 0:
+            if 'AutoConfigURL' in subkeys and len(subkeys['AutoConfigURL']) > 0:
                 pac_files.append(subkeys['AutoConfigURL'])
         elif sys.platform == 'darwin':
@@ -61,5 +65,5 @@
                 # the first part is a long identifier
                 net_key, network = network
-                if 'ProxyAutoConfigURLString' in network['Proxies'].keys():
+                if 'ProxyAutoConfigURLString' in network['Proxies']:
                     pac_files.append(
                         network['Proxies']['ProxyAutoConfigURLString'])
@@ -127,14 +131,14 @@
             logger.debug("Trying Direct connection to %s..."%self.url)
             response = urllib2.urlopen(req, timeout=self.timeout)
-        except Exception, e:
+        except Exception as exc:
             logger.debug("Failed!")
-            logger.debug(e)
+            logger.debug(exc)
             try:
                 logger.debug("Trying to use system proxy if it exists...")
                 self._set_proxy()
                 response = urllib2.urlopen(req, timeout=self.timeout)
-            except Exception, e:
+            except Exception as exc:
                 logger.debug("Failed!")
-                logger.debug(e)
+                logger.debug(exc)
                 pac_urls = self._get_addresses_of_proxy_pac()
                 proxy_urls = self._parse_proxy_pac(pac_urls)
@@ -144,7 +148,7 @@
                         self._set_proxy(proxy)
                         response = urllib2.urlopen(req, timeout=self.timeout)
-                    except Exception, e:
+                    except Exception as exc:
                         logger.debug("Failed!")
-                        logger.debug(e)
+                        logger.debug(exc)
         if response is not None:
             logger.debug("The connection to %s was successful."%self.url)
Index: src/sas/sasgui/guiframe/report_dialog.py
===================================================================
--- src/sas/sasgui/guiframe/report_dialog.py	(revision d0ce666faaf0f9284be2e607a0ac846cdd9a682b)
+++ src/sas/sasgui/guiframe/report_dialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -147,5 +147,5 @@
             self.Update()
             return pisaStatus.err
-        except Exception:
-            logger.error("Error creating pdf: %s" % sys.exc_value)
+        except Exception as exc:
+            logger.error("Error creating pdf: %s" % exc)
         return False
Index: src/sas/sasgui/guiframe/startup_configuration.py
===================================================================
--- src/sas/sasgui/guiframe/startup_configuration.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
+++ src/sas/sasgui/guiframe/startup_configuration.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -197,5 +197,5 @@
         with open(path, 'w') as out_f:
             out_f.write("#Application appearance custom configuration\n")
-            for key, item in self.return_string.iteritems():
+            for key, item in self.return_string.items():
                 if (key == 'DEFAULT_PERSPECTIVE') or \
                     (key == 'DEFAULT_OPEN_FOLDER' and item != None):
Index: src/sas/sasgui/guiframe/utils.py
===================================================================
--- src/sas/sasgui/guiframe/utils.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/utils.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -208,6 +208,8 @@
         self.id_list = id_list
         self.index = -1
-    def next(self):
+    def __next__(self):
         self.index += 1
         return self.id_list[self.index]
-
+    # CRUFT: python 2 uses next rather than __next__ for iterator
+    next = __next__
+
Index: src/sas/sasgui/perspectives/calculator/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/__init__.py	(revision 5a405bda2da663894b6b32ab94768a91d60525a4)
+++ src/sas/sasgui/perspectives/calculator/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,5 +2,5 @@
 import os
 from distutils.filelist import findall
-from calculator import *
+from .calculator import *
 N_DIR = 12
 def get_data_path(media):
Index: src/sas/sasgui/perspectives/calculator/calculator.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/calculator.py	(revision 61bfd3677b70fdddc3b49008cbc8a8713a2baaa2)
+++ src/sas/sasgui/perspectives/calculator/calculator.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -12,5 +12,8 @@
 ################################################################################
 
+import logging
+
 import wx
+
 from sas.sasgui.guiframe.plugin_base import PluginBase
 from sas.sasgui.perspectives.calculator.data_operator import DataOperatorWindow
@@ -26,5 +29,4 @@
 from sas.sasgui.perspectives.calculator.image_viewer import ImageView
 from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
-import logging
 
 logger = logging.getLogger(__name__)
Index: src/sas/sasgui/perspectives/calculator/collimation_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/collimation_editor.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/calculator/collimation_editor.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,12 +1,15 @@
 """
 """
-import wx
 import sys
 from copy import deepcopy
+
+import wx
+
 from sas.sascalc.dataloader.loader import Loader
 from sas.sascalc.dataloader.data_info import Aperture, Collimation
-from aperture_editor import ApertureDialog
-
 from sas.sasgui.guiframe.utils import check_float
+
+from .aperture_editor import ApertureDialog
+
 _BOX_WIDTH = 60
 
Index: src/sas/sasgui/perspectives/calculator/data_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/data_editor.py	(revision 235f514ba93b57fa63e44309f182acef0b98bb55)
+++ src/sas/sasgui/perspectives/calculator/data_editor.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -7,9 +7,11 @@
 from sas.sascalc.dataloader.loader import Loader
 from sas.sascalc.dataloader.data_info import Data2D
-from detector_editor import DetectorDialog
-from collimation_editor import CollimationDialog
-from console import ConsoleDialog
 
 from sas.sasgui.guiframe.events import StatusEvent
+
+from .detector_editor import DetectorDialog
+from .collimation_editor import CollimationDialog
+from .console import ConsoleDialog
+
 
 
@@ -397,5 +399,5 @@
         if data is None:
             return
-        from sample_editor import SampleDialog
+        from .sample_editor import SampleDialog
         dlg = SampleDialog(parent=self, sample=data.sample)
         dlg.set_manager(self)
@@ -409,5 +411,5 @@
         if data is None:
             return
-        from source_editor import SourceDialog
+        from .source_editor import SourceDialog
         dlg = SourceDialog(parent=self, source=data.source)
         dlg.set_manager(self)
@@ -426,5 +428,5 @@
         wlist = '|'.join(cards)
 
-        dlg = wx.FileDialog(self, "Choose a file", location, "", wlist, wx.OPEN)
+        dlg = wx.FileDialog(self, "Choose a file", location, "", wlist, wx.FD_OPEN)
         if dlg.ShowModal() == wx.ID_OK:
             path = dlg.GetPath()
@@ -527,5 +529,5 @@
         try:
             #Load data
-            from load_thread import DataReader
+            from .load_thread import DataReader
             ## If a thread is already started, stop it
             if self.reader is not None and self.reader.isrunning():
@@ -535,6 +537,6 @@
                                     updatefn=None)
             self.reader.queue()
-        except:
-            msg = "Data Editor: %s" % (sys.exc_value)
+        except Exception as exc:
+            msg = "Data Editor: %s" % exc
             load_error(msg)
             return
Index: src/sas/sasgui/perspectives/calculator/data_operator.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/data_operator.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/calculator/data_operator.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -202,10 +202,9 @@
         else:
             text = name
-        state_list = self.get_datalist().values()
         name_list = []
-        for state in state_list:
+        for state in self.get_datalist().values():
             if state.data is None:
                 theory_list = state.get_theory()
-                theory, _ = theory_list.values()[0]
+                theory, _ = list(theory_list.values())[0]
                 d_name = str(theory.name)
             else:
@@ -393,9 +392,9 @@
         try:
             self.output = self.make_data_out(data1, data2)
-        except:
+        except Exception as exc:
             self._check_newname()
             self._set_textctrl_color(self.data1_cbox, 'pink')
             self._set_textctrl_color(self.data2_cbox, 'pink')
-            msg = "DataOperation: %s" % sys.exc_value
+            msg = "DataOperation: %s" % exc
             self.send_warnings(msg, 'error')
             self.output = None
@@ -411,5 +410,6 @@
         operator = self.operator_cbox.GetClientData(pos)
         try:
-            exec "output = data1 %s data2" % operator
+            output = eval("data1 %s data2" % operator,
+                          {"data1": data1, "data2": data2})
         except:
             raise
@@ -532,6 +532,5 @@
         self.data2_cbox.SetClientData(pos3, val)
         dnames = []
-        ids = self._data.keys()
-        for id in ids:
+        for id in self._data.keys():
             if id is not None:
                 if self._data[id].data is not None:
@@ -539,9 +538,9 @@
                 else:
                     theory_list = self._data[id].get_theory()
-                    theory, _ = theory_list.values()[0]
+                    theory, _ = list(theory_list.values())[0]
                     dnames.append(theory.name)
         ind = np.argsort(dnames)
         if len(ind) > 0:
-            val_list = np.array(self._data.values())[ind]
+            val_list = np.array(list(self._data.values()))[ind]
             for datastate in val_list:
                 data = datastate.data
@@ -588,11 +587,10 @@
         self.send_warnings('')
         self.data_namectr.SetBackgroundColour('white')
-        state_list = self.get_datalist().values()
         name = self.data_namectr.GetValue().strip()
         name_list = []
-        for state in state_list:
+        for state in self.get_datalist().values():
             if state.data is None:
                 theory_list = state.get_theory()
-                theory, _ = theory_list.values()[0]
+                theory, _ = list(theory_list.values())[0]
                 d_name = str(theory.name)
             else:
@@ -889,13 +887,12 @@
     def _onProperties(self, event):
         """
-        when clicking on Properties on context menu ,
-        The Property dialog is displayed
-        The user selects a transformation for x or y value and
-        a new plot is displayed
-        """
-        list = []
-        list = self.graph.returnPlottable()
-        if len(list.keys()) > 0:
-            first_item = list.keys()[0]
+        When clicking on Properties on context menu, the
+        Property dialog is displayed the user selects a
+        transformation for x or y value and a new plot is displayed
+        """
+        plottables = self.graph.returnPlottable()
+        if plottables:
+            # TODO: key order is random prior to py 3.7
+            first_item = list(plottables.keys())[0]
             if first_item.x != []:
                 from sas.sasgui.plottools.PropertyDialog import Properties
@@ -929,6 +926,4 @@
         and set the scale
         """
-        list = []
-        list = self.graph.returnPlottable()
         # Changing the scale might be incompatible with
         # currently displayed data (for instance, going
@@ -940,5 +935,5 @@
         _xscale = 'linear'
         _yscale = 'linear'
-        for item in list:
+        for item in self.graph.returnPlottable():
             item.setLabel(self.xLabel, self.yLabel)
             # control axis labels from the panel itself
Index: src/sas/sasgui/perspectives/calculator/density_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/density_panel.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/calculator/density_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -362,7 +362,7 @@
             self.molar_mass_ctl.SetValue(str(self._format_number(molar_mass)))
             self.output_ctl.SetValue(str(output))
-        except:
+        except Exception as exc:
             if self.base is not None:
-                msg = "Density/Volume Calculator: %s" % (sys.exc_value)
+                msg = "Density/Volume Calculator: %s" % exc
                 wx.PostEvent(self.base, StatusEvent(status=msg))
         if event is not None:
Index: src/sas/sasgui/perspectives/calculator/detector_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/detector_editor.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sasgui/perspectives/calculator/detector_editor.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -34,6 +34,6 @@
             self._do_layout()
             self.set_values()
-        except:
-            print("error", sys.exc_value)
+        except Exception as exc:
+            print("error", exc)
 
     def _define_structure(self):
Index: src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -228,7 +228,5 @@
         sizer.Add(unit_title, (iy, ix), (1, 1), \
                             wx.EXPAND | wx.ADJUST_MINSIZE, 0)
-        key_list = params.keys()
-        key_list.sort()
-        for param in key_list:
+        for param in sorted(params.keys()):
             iy += 1
             ix = 0
@@ -341,5 +339,4 @@
         ix = 0
         iy = 0
-        #key_list.sort()
         name = wx.StaticText(self, -1, 'No. of Qx (Qy) bins: ')
         sizer.Add(name, (iy, ix), (1, 1), \
@@ -509,5 +506,5 @@
         wildcard = '|'.join(wildcard)
         dlg = wx.FileDialog(self, "Choose a file", location,
-                            "", wildcard, wx.OPEN)
+                            "", wildcard, wx.FD_OPEN)
         if dlg.ShowModal() == wx.ID_OK:
             path = dlg.GetPath()
@@ -556,9 +553,9 @@
             self.reader.queue()
             #self.load_update()
-        except:
+        except Exception as exc:
             self.ext = None
             if self.parent.parent is None:
                 return
-            msg = "Generic SAS Calculator: %s" % (sys.exc_value)
+            msg = "Generic SAS Calculator: %s" % exc
             wx.PostEvent(self.parent.parent,
                           StatusEvent(status=msg, type='stop'))
@@ -774,6 +771,6 @@
             if output.pix_type == 'atom':
                 # Get atom names not in the list
-                a_names = [symb  for symb in pix_symbol \
-                           if symb not in color_dic.keys()]
+                a_names = [symb  for symb in pix_symbol
+                           if symb not in color_dic]
                 a_name = a_names[0]
                 for name in a_names:
@@ -898,6 +895,6 @@
             cal_out.queue()
 
-        except:
-            msg = "%s." % sys.exc_value
+        except Exception as exc:
+            msg = "%s." % exc
             status_type = 'stop'
             self._status_info(msg, status_type)
@@ -1344,6 +1341,6 @@
             self.sld_data.is_data = False
             self.sld_data.filename = "Default SLD Profile"
-        except:
-            msg = "OMF Panel: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "OMF Panel: %s" % exc
             infor = 'Error'
             #logger.error(msg)
@@ -1441,14 +1438,9 @@
             raise
         sld_key_list = self._get_slds_key_list(omfdata)
-        # Dic is not sorted
-        key_list = [key for key in sld_key_list.keys()]
-        # Sort here
-        key_list.sort()
         is_data = self.sld_data.is_data
         sizer = wx.GridBagSizer(2, 3)
         ix = 0
         iy = -1
-        for key in key_list:
-            value = sld_key_list[key]
+        for key, value in sorted(sld_key_list.items()):
             iy += 1
             ix = 0
@@ -1485,5 +1477,5 @@
         ix = 0
         iy = -1
-        for key, value in key_list.iteritems():
+        for key, value in sorted(key_list.items()):
             iy += 1
             ix = 0
@@ -1520,6 +1512,5 @@
         ix = 0
         iy = -1
-        #key_list.sort()
-        for key, value in key_list.iteritems():
+        for key, value in sorted(key_list.items()):
             iy += 1
             ix = 0
Index: src/sas/sasgui/perspectives/calculator/image_viewer.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/image_viewer.py	(revision 412e9e8b8f844bae61656ffb2a2d0c1c53e6e8de)
+++ src/sas/sasgui/perspectives/calculator/image_viewer.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -95,12 +95,14 @@
         if location is None:
             location = os.getcwd()
-        wildcard="Images (*.bmp;*.gif;*jpeg,*jpg;*.png;*tif;*.tiff)|*bmp;\
-            *.gif; *.jpg; *.jpeg;*png;*.png;*.tif;*.tiff|"\
-            "Bitmap (*.bmp)|*.bmp|"\
-            "GIF (*.gif)|*.gif|"\
-            "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|"\
-            "PNG (*.png)|*.png|"\
-            "TIFF (*.tif;*.tiff)|*.tif;*tiff|"\
-            "All Files (*.*)|*.*|"
+        wildcard="|".join((
+            "Images (*.bmp;*.gif;*jpeg,*jpg;*.png;*tif;*.tiff)"
+                "|*bmp;*.gif;*.jpg;*.jpeg;*png;*.png;*.tif;*.tiff",
+            "Bitmap (*.bmp)|*.bmp",
+            "GIF (*.gif)|*.gif",
+            "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg",
+            "PNG (*.png)|*.png",
+            "TIFF (*.tif;*.tiff)|*.tif;*tiff",
+            "All Files (*.*)|*.*",
+            ))
 
         dlg = wx.FileDialog(self.parent, "Image Viewer: Choose an image file",
Index: src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -12,10 +12,10 @@
 import sys
 
+from sas.sascalc.calculator.kiessig_calculator import KiessigThicknessCalculator
 from sas.sasgui.guiframe.panel_base import PanelBase
-from sas.sascalc.calculator.kiessig_calculator import KiessigThicknessCalculator
-from calculator_widgets import OutputTextCtrl
-from calculator_widgets import InputTextCtrl
 from sas.sasgui.perspectives.calculator import calculator_widgets as widget
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+from .calculator_widgets import OutputTextCtrl
+from .calculator_widgets import InputTextCtrl
 
 _BOX_WIDTH = 77
Index: src/sas/sasgui/perspectives/calculator/model_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/model_editor.py	(revision 1752f38b456a43e18ad070757b9e3f32528721ca)
+++ src/sas/sasgui/perspectives/calculator/model_editor.py	(revision 1752f38b456a43e18ad070757b9e3f32528721ca)
@@ -578,5 +578,5 @@
         self.base = base
         self.path = path
-        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
+        self.font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
         self.font.SetPointSize(10)
         self.reader = None
@@ -781,5 +781,5 @@
             if item.count("_") < 1:
                 try:
-                    exec "float(math.%s)" % item
+                    exec("float(math.%s)" % item)
                     self.math_combo.Append(str(item))
                 except Exception:
Index: src/sas/sasgui/perspectives/calculator/resolcal_thread.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/resolcal_thread.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/calculator/resolcal_thread.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -46,7 +46,7 @@
         executing computation
         """
-        self.image = map(self.func, self.qx, self.qy,
-                        self.qx_min, self.qx_max,
-                        self.qy_min, self.qy_max)[0]
+        self.image = list(map(self.func, self.qx, self.qy,
+                              self.qx_min, self.qx_max,
+                              self.qy_min, self.qy_max))[0]
         elapsed = time.time() - self.starttime
 
Index: src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py	(revision 1cf490b648906629e67d57ec00ad3cd52e08523f)
+++ src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -148,5 +148,5 @@
         # Custom sorting
         source_list = []
-        for key, _ in self.source_mass.iteritems():
+        for key, _ in self.source_mass.items():
             name_source = str(key)
             source_list.append(name_source)
@@ -667,4 +667,10 @@
         Execute the computation of resolution
         """
+        # Clone the event before CallAfter; the event seems
+        # to delete the event when it is done processing, so
+        # the original will not be available when the call
+        # after method starts.
+        if event is not None:
+            event = event.Clone()
         wx.CallAfter(self.on_compute_call, event)
 
@@ -753,5 +759,5 @@
             wx.MessageBox(msg, 'Warning')
             return
-            #raise ValueError, "Invalid Q Input..."
+            #raise ValueError("Invalid Q Input...")
 
         # Validate the q inputs
@@ -934,5 +940,5 @@
     def _sigma_strings(self):
         """
-        Recode sigmas as strins
+        Recode sigmas as strings
         """
         sigma_r = self.format_number(self.resolution.sigma_1)
@@ -1082,5 +1088,5 @@
                 msg = "The numbers must be one or two (separated by ',')..."
                 self._status_info(msg, 'stop')
-                raise RuntimeError, msg
+                raise RuntimeError(msg)
 
         return new_string
@@ -1099,6 +1105,6 @@
                 value = float(string_split[ind])
                 new_string.append(value)
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
 
         return new_string
@@ -1141,6 +1147,6 @@
                         out = self._string2inputlist(string)
                         return out
-                except:
-                    logger.error(sys.exc_value)
+                except Exception as exc:
+                    logger.error(exc)
 
     def _on_xy_coordinate(self, event=None):
@@ -1269,5 +1275,5 @@
             try:
                 basename = os.path.basename(path)
-                if basename not in self.spectrum_dic.keys():
+                if basename not in self.spectrum_dic:
                     self.spectrum_cb.Append(basename)
                 self.spectrum_dic[basename] = self._read_file(path)
@@ -1286,5 +1292,5 @@
         dlg = wx.FileDialog(self,
                             "Choose a wavelength spectrum file: Intensity vs. wavelength",
-                            self.parent.parent.get_save_location() , "", "*.*", wx.OPEN)
+                            self.parent.parent.get_save_location() , "", "*.*", wx.FD_OPEN)
         path = None
         if dlg.ShowModal() == wx.ID_OK:
@@ -1318,7 +1324,7 @@
                     wavelength.append(wave)
                     intensity.append(intens)
-                except:
+                except Exception as exc:
                     # Skip non-data lines
-                    logger.error(sys.exc_value)
+                    logger.error(exc)
 
             return [wavelength, intensity]
Index: src/sas/sasgui/perspectives/calculator/sld_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/sld_panel.py	(revision fe09e9bb0ad6caa9284d13a5c7dfdfc420d0c28b)
+++ src/sas/sasgui/perspectives/calculator/sld_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -363,8 +363,11 @@
 
         """
+        # TODO: use periodictable.elements object
+        #    energy = xray_energy(periodictable.elements[element].K_alpha)
+        # TODO: code is very similar to sld helper
         myformula = formula(str(element))
         if len(myformula.atoms) != 1:
             return
-        element = myformula.atoms.keys()[0]
+        element = list(myformula.atoms.keys())[0]
         energy = xray_energy(element.K_alpha)
 
@@ -413,4 +416,7 @@
                     msg += "Error for wavelength value :expect float"
             elif (self.xray_source == 'Element'):
+                # TODO: use periodictable.elements instead of exec() hacks
+                #     if self.xray_source_input not in periodictable.elements:
+                #         ...
                 try:
                     import periodictable
@@ -447,9 +453,12 @@
 
         """
+        # TODO: use periodictable.elements object
+        #    energy = xray_energy(periodictable.elements[element].K_alpha)
         element_formula = formula(str(element))
         if len(element_formula.atoms) != 1:
             return
-        element = element_formula.atoms.keys()[0]
+        element = list(element_formula.atoms.keys())[0]
         energy = xray_energy(element.K_alpha)
+
         atom = molecule_formula.atoms
         return xray_sld_from_atoms(atom, density=density, energy=energy)
@@ -505,7 +514,7 @@
             #self.wavelength_ctl.SetValue(str(self.wavelength))
             #self.wavelength_ctl.SetValue(str(self.wavelength))
-        except:
+        except Exception as exc:
             if self.base is not None:
-                msg = "SLD Calculator: %s" % (sys.exc_value)
+                msg = "SLD Calculator: %s" % exc
                 wx.PostEvent(self.base, StatusEvent(status=msg))
         if event is not None:
Index: src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py	(revision d78861987017138f4c36cc97a261c26e80c03f64)
+++ src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -17,8 +17,8 @@
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sascalc.calculator.slit_length_calculator import SlitlengthCalculator
-from calculator_widgets import OutputTextCtrl
-from calculator_widgets import InterActiveOutputTextCtrl
 from sas.sasgui.perspectives.calculator import calculator_widgets as widget
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+from .calculator_widgets import OutputTextCtrl
+from .calculator_widgets import InterActiveOutputTextCtrl
 
 _BOX_WIDTH = 76
@@ -163,5 +163,5 @@
 
         dlg = wx.FileDialog(self, "Choose a file", location,
-                            "", wildcard, wx.OPEN)
+                            "", wildcard, wx.FD_OPEN)
         if dlg.ShowModal() == wx.ID_OK:
             path = dlg.GetPath()
@@ -223,8 +223,8 @@
             self.reader.queue()
             self.load_update()
-        except:
+        except Exception as exc:
             if self.parent.parent is None:
                 return
-            msg = "Slit Length Calculator: %s" % (sys.exc_value)
+            msg = "Slit Length Calculator: %s" % exc
             wx.PostEvent(self.parent.parent,
                           StatusEvent(status=msg, type='stop'))
@@ -264,12 +264,12 @@
             if x == [] or  x is None or y == [] or y is None:
                 msg = "The current data is empty please check x and y"
-                raise ValueError, msg
+                raise ValueError(msg)
             slit_length_calculator = SlitlengthCalculator()
             slit_length_calculator.set_data(x=x, y=y)
             slit_length = slit_length_calculator.calculate_slit_length()
-        except:
+        except Exception as exc:
             if self.parent.parent is None:
                 return
-            msg = "Slit Size Calculator: %s" % (sys.exc_value)
+            msg = "Slit Size Calculator: %s" % exc
             wx.PostEvent(self.parent.parent,
                           StatusEvent(status=msg, type='stop'))
Index: src/sas/sasgui/perspectives/corfunc/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/corfunc/__init__.py	(revision c23f303448cfceb9e5a58e5d7da46258cdb3cbd8)
+++ src/sas/sasgui/perspectives/corfunc/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,2 +1,2 @@
 PLUGIN_ID = "Corfunc Plug-In 0.1"
-from corfunc import *
+from .corfunc import *
Index: src/sas/sasgui/perspectives/corfunc/corfunc.py
===================================================================
--- src/sas/sasgui/perspectives/corfunc/corfunc.py	(revision 9b90bf84a52cb67272ccbed5bac69156a6ca6bc3)
+++ src/sas/sasgui/perspectives/corfunc/corfunc.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -18,5 +18,5 @@
 from sas.sascalc.dataloader.loader import Loader
 import sas.sascalc.dataloader
-from plot_labels import *
+from .plot_labels import *
 
 logger = logging.getLogger(__name__)
@@ -148,6 +148,6 @@
                     self.data_id = data.id
                     self.corfunc_panel.set_data(data)
-                except:
-                    msg = "Corfunc set_data: " + str(sys.exc_value)
+                except Exception as exc:
+                    msg = "Corfunc set_data: " + str(exc)
                     wx.PostEvent(self.parent, StatusEvent(status=msg,
                         info='error'))
Index: src/sas/sasgui/perspectives/corfunc/corfunc_panel.py
===================================================================
--- src/sas/sasgui/perspectives/corfunc/corfunc_panel.py	(revision ef74a8b4c5fbc72695853e60c09c0379d8d1c2c6)
+++ src/sas/sasgui/perspectives/corfunc/corfunc_panel.py	(revision ef74a8b4c5fbc72695853e60c09c0379d8d1c2c6)
@@ -17,5 +17,5 @@
 from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
-from plot_labels import *
+from .plot_labels import *
 
 OUTPUT_STRINGS = {
@@ -396,5 +396,5 @@
                 output.SetValue('-')
             return
-        for key, value in params.iteritems():
+        for key, value in params.items():
             output = self._extrapolation_outputs[key]
             rounded = self._round_sig_figs(value, 6)
@@ -417,5 +417,5 @@
                 # Not all parameters were calculated
                 error = True
-            for key, value in params.iteritems():
+            for key, value in params.items():
                 rounded = self._round_sig_figs(value, 6)
                 self._output_boxes[key].SetValue(rounded)
@@ -549,5 +549,5 @@
             wx.CENTER | wx.ADJUST_MINSIZE, 15)
 
-        file_sizer.AddSpacer((1, 25), pos=(0,2))
+        #file_sizer.AddSpacer((1, 25), pos=(0,2))
         databox_sizer.Add(file_sizer, wx.TOP, 15)
 
@@ -713,5 +713,5 @@
         self._output_boxes = dict()
         i = 0
-        for key, value in OUTPUT_STRINGS.iteritems():
+        for key, value in OUTPUT_STRINGS.items():
             # Create a label and a text box for each poperty
             label = wx.StaticText(self, -1, value)
Index: src/sas/sasgui/perspectives/corfunc/corfunc_state.py
===================================================================
--- src/sas/sasgui/perspectives/corfunc/corfunc_state.py	(revision 5652efcc300905660181472befe611868f2ddeb8)
+++ src/sas/sasgui/perspectives/corfunc/corfunc_state.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -74,5 +74,5 @@
         if self.outputs != {} and self.outputs is not None:
             state += "\nOutputs:\n"
-            for key, value in self.outputs.iteritems():
+            for key, value in self.outputs.items():
                 name = output_list[key][1]
                 state += "{}: {}\n".format(name, str(value))
@@ -158,5 +158,5 @@
         state = new_doc.createElement("state")
         top_element.appendChild(state)
-        for name, value in self.saved_state.iteritems():
+        for name, value in self.saved_state.items():
             element = new_doc.createElement(name)
             element.appendChild(new_doc.createTextNode(str(value)))
@@ -181,5 +181,5 @@
             output = new_doc.createElement("output")
             top_element.appendChild(output)
-            for key, value in self.outputs.iteritems():
+            for key, value in self.outputs.items():
                 element = new_doc.createElement(key)
                 element.appendChild(new_doc.createTextNode(str(value)))
@@ -214,7 +214,7 @@
                 try:
                     self.timestamp = (entry.get('epoch'))
-                except:
+                except Exception as exc:
                     msg = ("CorfuncState.fromXML: Could not read timestamp",
-                        "\n{}").format(sys.exc_value)
+                        "\n{}").format(exc)
                     logger.error(msg)
 
@@ -222,5 +222,5 @@
             entry = get_content('ns:state', node)
             if entry is not None:
-                for item in DEFAULT_STATE.iterkeys():
+                for item in DEFAULT_STATE.keys():
                     input_field = get_content("ns:{}".format(item), entry)
                     if input_field is not None:
@@ -283,5 +283,5 @@
             root, ext = os.path.splitext(basename)
             if not ext.lower() in self.ext:
-                raise IOError, "{} is not a supported file type".format(ext)
+                raise IOError("{} is not a supported file type".format(ext))
             tree = etree.parse(path, parser=etree.ETCompatXMLParser())
             root = tree.getroot()
@@ -299,5 +299,5 @@
             # File not found
             msg = "{} is not a valid file path or doesn't exist".format(path)
-            raise IOError, msg
+            raise IOError(msg)
 
         if len(output) == 0:
@@ -323,5 +323,5 @@
             msg = ("The CanSAS writer expects a Data1D instance. {} was "
                 "provided").format(datainfo.__class__.__name__)
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         if datainfo.title is None or datainfo.title == '':
             datainfo.title = datainfo.name
@@ -358,7 +358,7 @@
                 state = CorfuncState()
                 state.fromXML(nodes[0])
-        except:
+        except Exception as exc:
             msg = "XML document does not contain CorfuncState information\n{}"
-            msg.format(sys.exc_value)
+            msg.format(exc)
             logger.info(msg)
         return state
Index: src/sas/sasgui/perspectives/file_converter/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/file_converter/__init__.py	(revision 77d92cd296d0af12557c509f0c48df88ef5f59e9)
+++ src/sas/sasgui/perspectives/file_converter/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,2 +1,2 @@
 PLUGIN_ID = "File-Converter Plug-In 1.0"
-from file_converter import *
+from .file_converter import *
Index: src/sas/sasgui/perspectives/file_converter/converter_panel.py
===================================================================
--- src/sas/sasgui/perspectives/file_converter/converter_panel.py	(revision a26f67f1457e7e39b79d4c3800c03bcaa208dff7)
+++ src/sas/sasgui/perspectives/file_converter/converter_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -112,5 +112,5 @@
             [group_path, group_name] = os.path.split(filepath)
             ext = "." + group_name.split('.')[-1] # File extension
-            for frame_number, frame_data in frame_data.iteritems():
+            for frame_number, frame_data in frame_data.items():
                 # Append frame number to base filename
                 filename = group_name.replace(ext, str(frame_number)+ext)
@@ -155,5 +155,5 @@
             # If lines end with comma or semi-colon, trim the last character
             if end_char == ',' or end_char == ';':
-                data = map(lambda s: s[0:-1], data)
+                data = [s[0:-1] for s in data]
             else:
                 msg = ("Error reading {}: Lines must end with a digit, comma "
@@ -275,5 +275,5 @@
             else:
                 return { 'frames': [], 'inc': None, 'file': single_file }
-        frames = range(first_frame, last_frame + 1, increment)
+        frames = list(range(first_frame, last_frame + 1, increment))
         return { 'frames': frames, 'inc': increment, 'file': single_file }
 
@@ -335,7 +335,7 @@
         if single_file:
             # Only need to set metadata on first Data1D object
-            frame_data = frame_data.values() # Don't need to know frame numbers
+            frame_data = list(frame_data.values()) # Don't need to know frame numbers
             frame_data[0].filename = output_path.split('\\')[-1]
-            for key, value in metadata.iteritems():
+            for key, value in metadata.items():
                 setattr(frame_data[0], key, value)
         else:
@@ -343,5 +343,5 @@
             for datainfo in frame_data.values():
                 datainfo.filename = output_path.split('\\')[-1]
-                for key, value in metadata.iteritems():
+                for key, value in metadata.items():
                     setattr(datainfo, key, value)
 
@@ -355,5 +355,5 @@
     def convert_2d_data(self, dataset):
         metadata = self.get_metadata()
-        for key, value in metadata.iteritems():
+        for key, value in metadata.items():
             setattr(dataset[0], key, value)
 
Index: src/sas/sasgui/perspectives/file_converter/converter_widgets.py
===================================================================
--- src/sas/sasgui/perspectives/file_converter/converter_widgets.py	(revision 0e11ec7528fae0e8cd8f2e311b10e73c4b19c77b)
+++ src/sas/sasgui/perspectives/file_converter/converter_widgets.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -54,5 +54,5 @@
         v = Vector()
         if not self.Validate(): return v
-        for direction, control in self._inputs.iteritems():
+        for direction, control in self._inputs.items():
             try:
                 value = float(control.GetValue())
Index: src/sas/sasgui/perspectives/fitting/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/__init__.py	(revision 12d3e0e31a7f9e8d72d50e8ad904316b003a6bea)
+++ src/sas/sasgui/perspectives/fitting/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,6 +1,6 @@
 PLUGIN_ID = "Fitting plug-in 1.0"
 import os
-from fitting import *
 from distutils.filelist import findall
+from .fitting import *
 def get_data_path(media):
     """
Index: src/sas/sasgui/perspectives/fitting/basepage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/basepage.py	(revision 5323490d62009a786d3d071b2197b51ce207354d)
+++ src/sas/sasgui/perspectives/fitting/basepage.py	(revision cb64d86aa05427be27c71cc415727ce4d7fdce69)
@@ -12,5 +12,8 @@
 import logging
 import traceback
-from Queue import Queue
+try: # CRUFT: python 2.x
+    from Queue import Queue
+except ImportError:
+    from queue import Queue
 from threading import Thread
 from collections import defaultdict
@@ -66,4 +69,6 @@
     FONT_VARIANT = 1
     ON_MAC = True
+if sys.version_info[0] >= 3:
+    unicode = str
 
 CUSTOM_MODEL = 'Plugin Models'
@@ -115,5 +120,5 @@
         # Q range for data set
         self.qmin_data_set = np.inf
-        self.qmax_data_set = None
+        self.qmax_data_set = -np.inf
         self.npts_data_set = 0
         # Q range
@@ -624,5 +629,5 @@
         if self.model is not None:
             self.m_name = self.model.name
-        if name in self.saved_states.keys():
+        if name in self.saved_states:
             previous_state = self.saved_states[name]
             # reset state of checkbox,textcrtl  and  regular parameters value
@@ -893,5 +898,5 @@
 
         if len(self._disp_obj_dict) > 0:
-            for k, v in self._disp_obj_dict.iteritems():
+            for k, v in self._disp_obj_dict.items():
                 self.state.disp_obj_dict[k] = v.type
 
@@ -960,5 +965,5 @@
 
             if len(self.disp_cb_dict) > 0:
-                for k, v in self.disp_cb_dict.iteritems():
+                for k, v in self.disp_cb_dict.items():
                     if v is None:
                         self.state.disp_cb_dict[k] = v
@@ -969,5 +974,5 @@
                             self.state.disp_cb_dict[k] = None
             if len(self._disp_obj_dict) > 0:
-                for k, v in self._disp_obj_dict.iteritems():
+                for k, v in self._disp_obj_dict.items():
                     self.state.disp_obj_dict[k] = v.type
 
@@ -1097,6 +1102,5 @@
 
             else:
-                keys = self.model.getParamList()
-                for item in keys:
+                for item in self.model.getParamList():
                     if item in self.disp_list and \
                             item not in self.model.details:
@@ -1142,5 +1146,5 @@
         :return: combo_box_position
         """
-        for key, value in self.master_category_dict.iteritems():
+        for key, value in self.master_category_dict.items():
             formfactor = state.formfactorcombobox.split(":")
             if isinstance(formfactor, list):
@@ -1318,10 +1322,9 @@
         Help to rest page for dispersions
         """
-        keys = self.model.getParamList()
-        for item in keys:
+        for item in self.model.getParamList():
             if item in self.disp_list and \
                             item not in self.model.details:
                 self.model.details[item] = ["", None, None]
-        # for k,v in self.state.disp_cb_dict.iteritems():
+        # for k,v in self.state.disp_cb_dict.items():
         self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
         self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
@@ -1329,5 +1332,5 @@
         self.weights = copy.deepcopy(state.weights)
 
-        for key, disp_type in state.disp_obj_dict.iteritems():
+        for key, disp_type in state.disp_obj_dict.items():
             # disp_model = disp
             disp_model = POLYDISPERSITY_MODELS[disp_type]()
@@ -1378,5 +1381,5 @@
         dlg = wx.FileDialog(self, "Choose a weight file",
                             self._default_save_location, "",
-                            "*.*", wx.OPEN)
+                            "*.*", wx.FD_OPEN)
         path = None
         if dlg.ShowModal() == wx.ID_OK:
@@ -1390,5 +1393,5 @@
         """
         ids = iter(self._id_pool)  # Reusing ids for context menu
-        for name, _ in self.state.saved_states.iteritems():
+        for name, _ in self.state.saved_states.items():
             self.number_saved_state += 1
             # Add item in the context menu
@@ -1919,11 +1922,11 @@
                 else:
                     tcrtl.SetBackgroundColour("pink")
-                    msg = "Model Error: wrong value entered: %s" % \
-                          sys.exc_info()[1]
+                    _, exc, _ = sys.exc_info()
+                    msg = "Model Error: wrong value entered: %s" % exc
                     wx.PostEvent(self.parent, StatusEvent(status=msg))
                     return
-            except Exception:
+            except Exception as exc:
                 tcrtl.SetBackgroundColour("pink")
-                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
+                msg = "Model Error: wrong value entered: %s" % exc
                 wx.PostEvent(self.parent, StatusEvent(status=msg))
                 return
@@ -1976,11 +1979,11 @@
                 else:
                     tcrtl.SetBackgroundColour("pink")
-                    msg = "Model Error: wrong value entered: %s" % \
-                          sys.exc_info()[1]
+                    _, exc, _ = sys.exc_info()
+                    msg = "Model Error: wrong value entered: %s" % exc
                     wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                     return
-            except Exception:
+            except Exception as exc:
                 tcrtl.SetBackgroundColour("pink")
-                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
+                msg = "Model Error: wrong value entered: %s" % exc
                 wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                 return
@@ -2291,5 +2294,5 @@
                     value_ctrl.SetValue(format_number(value))
 
-                if name not in self.model.details.keys():
+                if name not in self.model.details:
                     self.model.details[name] = ["", None, None]
                 old_low, old_high = self.model.details[name][1:3]
@@ -2750,5 +2753,5 @@
         gui_manager = self._manager.parent
         # loops through the panels [dic]
-        for _, item2 in gui_manager.plot_panels.iteritems():
+        for _, item2 in gui_manager.plot_panels.items():
             data_title = self.data.group_id
             # try to get all plots belonging to this control panel
@@ -3324,5 +3327,5 @@
             if self.data.__class__.__name__ == "Data2D":
                 name = item[1]
-                if name in content.keys():
+                if name in content:
                     values = content[name]
                     check = values[0]
@@ -3373,5 +3376,5 @@
                 if not item[1] in orient_param:
                     name = item[1]
-                    if name in content.keys():
+                    if name in content:
                         check = content[name][0]
                         # Avoid changing combox content
@@ -3480,8 +3483,7 @@
                 self.state.weights = self.weights
 
-        except Exception:
+        except Exception as exc:
             logger.error(traceback.format_exc())
-            print("Error in BasePage._paste_poly_help: %s" % \
-                  sys.exc_info()[1])
+            print("Error in BasePage._paste_poly_help: %s" % exc)
 
     def _set_disp_cb(self, isarray, item):
@@ -3548,5 +3550,5 @@
 
         self.categorybox.Clear()
-        cat_list = sorted(self.master_category_dict.keys())
+        cat_list = list(sorted(self.master_category_dict.keys()))
         if uncat_str not in cat_list:
             cat_list.append(uncat_str)
Index: src/sas/sasgui/perspectives/fitting/fit_thread.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision ba8d326f0f4ceb55c77296a9b5af8bd013c7dd92)
+++ src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -12,5 +12,6 @@
 
 def map_apply(arguments):
-    return apply(arguments[0], arguments[1:])
+    fn, args = arguments[0], arguments[1:]
+    return fn(*args)
 
 class FitThread(CalcThread):
@@ -50,5 +51,5 @@
         except KeyboardInterrupt:
             msg = "Fitting: terminated by the user."
-            raise KeyboardInterrupt, msg
+            raise KeyboardInterrupt(msg)
 
     def compute(self):
@@ -76,5 +77,5 @@
                          list_q, list_q, list_handler, list_curr_thread,
                          list_reset_flag)
-            result = map(map_apply, inputs)
+            result = list(map(map_apply, inputs))
 
             self.complete(result=result,
@@ -85,5 +86,5 @@
                           elapsed=time.time() - self.starttime)
 
-        except KeyboardInterrupt, msg:
+        except KeyboardInterrupt as msg:
             # Thread was interrupted, just proceed and re-raise.
             # Real code should not print, but this is an example...
Index: src/sas/sasgui/perspectives/fitting/fitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitpage.py	(revision ca4f40d38aa2c0abd8d7a1571cf953a85a676091)
+++ src/sas/sasgui/perspectives/fitting/fitpage.py	(revision ca4f40d38aa2c0abd8d7a1571cf953a85a676091)
@@ -780,5 +780,5 @@
                 disp_box = wx.ComboBox(self, wx.ID_ANY, size=(65, -1),
                                        style=wx.CB_READONLY, name='%s' % name1)
-                for key, value in POLYDISPERSITY_MODELS.iteritems():
+                for key, value in POLYDISPERSITY_MODELS.items():
                     name_disp = str(key)
                     disp_box.Append(name_disp, value)
@@ -938,5 +938,5 @@
                 disp_box = wx.ComboBox(self, wx.ID_ANY, size=(65, -1),
                                 style=wx.CB_READONLY, name='%s' % name1)
-                for key, value in POLYDISPERSITY_MODELS.iteritems():
+                for key, value in POLYDISPERSITY_MODELS.items():
                     name_disp = str(key)
                     disp_box.Append(name_disp, value)
@@ -1378,7 +1378,7 @@
                 self._check_value_enter(self.fittable_param)
                 self._check_value_enter(self.parameters)
-            except:
+            except Exception as exc:
                 tcrtl.SetBackgroundColour("pink")
-                msg = "Model Error:wrong value entered : %s" % sys.exc_value
+                msg = "Model Error:wrong value entered : %s" % exc
                 wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                 return
@@ -1492,10 +1492,11 @@
                 else:
                     tcrtl.SetBackgroundColour("pink")
-                    msg = "Model Error:wrong value entered : %s" % sys.exc_value
+                    _, exc, _ = sys.exc_info()
+                    msg = "Model Error:wrong value entered : %s" % exc
                     wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                     return
-            except:
+            except Exception as exc:
                 tcrtl.SetBackgroundColour("pink")
-                msg = "Model Error:wrong value entered : %s" % sys.exc_value
+                msg = "Model Error:wrong value entered : %s" % exc
                 wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                 return
@@ -1902,5 +1903,5 @@
                     wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
                                                info="error"))
-                    raise ValueError, msg
+                    raise ValueError(msg)
 
             else:
@@ -1914,5 +1915,5 @@
                     wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
                                                info="error"))
-                    raise ValueError, msg
+                    raise ValueError(msg)
                 # Maximum value of data
                 qmax = math.sqrt(x * x + y * y)
@@ -2154,5 +2155,5 @@
         self._on_fit_complete()
         if out is None or not np.isfinite(chisqr):
-            raise ValueError, "Fit error occured..."
+            raise ValueError("Fit error occured...")
 
         is_modified = False
@@ -2229,5 +2230,5 @@
                 i += 1
             else:
-                raise ValueError, "onsetValues: Invalid parameters..."
+                raise ValueError("onsetValues: Invalid parameters...")
         # Show error title when any errors displayed
         if has_error:
@@ -2985,7 +2986,7 @@
         # type can be either Guassian or Array
         if len(self.model.dispersion.values()) > 0:
-            type = self.model.dispersion.values()[0]["type"]
-        else:
-            type = "Gaussian"
+            dist_type = list(self.model.dispersion.values())[0]["type"]
+        else:
+            dist_type = "Gaussian"
 
         iy += 1
@@ -3045,5 +3046,5 @@
 
         # For Gaussian only
-        if type.lower() != "array":
+        if dist_type.lower() != "array":
             for item in self.model.orientation_params:
                 if not self.magnetic_on:
Index: src/sas/sasgui/perspectives/fitting/fitpanel.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitpanel.py	(revision c9ecd1b562e38798001341ea1382b347f0cc1af9)
+++ src/sas/sasgui/perspectives/fitting/fitpanel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -94,5 +94,5 @@
             batch_state = self.sim_page.set_state()
 
-        for uid, page in self.opened_pages.iteritems():
+        for uid, page in self.opened_pages.items():
             data = page.get_data()
             # state must be cloned
@@ -135,5 +135,5 @@
         if uid not in self.opened_pages:
             msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             return self.opened_pages[uid]
@@ -217,5 +217,5 @@
         page_is_opened = False
         if state is not None:
-            for uid, panel in self.opened_pages.iteritems():
+            for uid, panel in self.opened_pages.items():
                 # Don't return any panel is the exact same page is created
                 if uid == panel.uid and panel.data == state.data:
@@ -396,5 +396,5 @@
         """
         if data.__class__.__name__ != "list":
-            raise ValueError, "Fitpanel delete_data expect list of id"
+            raise ValueError("Fitpanel delete_data expect list of id")
         else:
             for page in self.opened_pages.values():
@@ -592,5 +592,5 @@
         if selected_page in page_finder:
             # Delete the name of the page into the list of open page
-            for uid, list in self.opened_pages.iteritems():
+            for uid, list in self.opened_pages.items():
                 # Don't return any panel is the exact same page is created
                 if flag and selected_page.uid == uid:
@@ -600,5 +600,5 @@
 
         # Delete the name of the page into the list of open page
-        for uid, list in self.opened_pages.iteritems():
+        for uid, list in self.opened_pages.items():
             # Don't return any panel is the exact same page is created
             if selected_page.uid == uid:
Index: src/sas/sasgui/perspectives/fitting/fitproblem.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -545,5 +545,5 @@
         return fitproblem contained in this dictionary
         """
-        return self.values()
+        return list(self.values())
 
     def set_result(self, result, fid):
Index: src/sas/sasgui/perspectives/fitting/fitting.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitting.py	(revision a5cffe570a689752aa7b74ce8a71d2b1704e624f)
+++ src/sas/sasgui/perspectives/fitting/fitting.py	(revision a5cffe570a689752aa7b74ce8a71d2b1704e624f)
@@ -147,5 +147,5 @@
         Given an ID create a fitproblem container
         """
-        if page_id in self.page_finder.iterkeys():
+        if page_id in self.page_finder:
             del self.page_finder[page_id]
 
@@ -356,5 +356,5 @@
             # categories, the updated plugin may be in either the form factor
             # or the structure factor combo boxes
-            for uid, page in self.fit_panel.opened_pages.iteritems():
+            for uid, page in self.fit_panel.opened_pages.items():
                 pbox = getattr(page, "formfactorbox", None)
                 sbox = getattr(page, "structurebox", None)
@@ -417,6 +417,6 @@
                 self.fit_panel.SetSelection(current_page_index)
 
-        except Exception:
-            logger.error("update_custom_combo: %s", sys.exc_value)
+        except Exception as exc:
+            logger.error("update_custom_combo: %s", exc)
 
     def set_edit_menu(self, owner):
@@ -642,6 +642,6 @@
                             data.list_group_id.append(group_id)
                         self.add_fit_page(data=[data])
-            except:
-                msg = "Fitting set_data: " + str(sys.exc_value)
+            except Exception as exc:
+                msg = "Fitting set_data: " + str(exc)
                 wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
 
@@ -654,8 +654,8 @@
                 _, theory_state = item
                 self.fit_panel.set_model_state(theory_state)
-            except Exception:
+            except Exception as exc:
                 msg = "Fitting: cannot deal with the theory received"
                 evt = StatusEvent(status=msg, info="error")
-                logger.error("set_theory " + msg + "\n" + str(sys.exc_value))
+                logger.error("set_theory " + msg + "\n" + str(exc))
                 wx.PostEvent(self.parent, evt)
 
@@ -778,5 +778,5 @@
         # Note: this is used to set the data weights for the fit based on
         # the weight selection in the GUI.
-        if uid in self.page_finder.keys():
+        if uid in self.page_finder:
             self.page_finder[uid].set_weight(flag=flag, is2d=is2d)
 
@@ -791,5 +791,5 @@
         :param qmax: maximum  value of the fit range
         """
-        if uid in self.page_finder.keys():
+        if uid in self.page_finder:
             self.page_finder[uid].set_range(qmin=qmin, qmax=qmax, fid=fid)
 
@@ -802,5 +802,5 @@
         :param uid: the id related to a page containing fitting information
         """
-        if uid in self.page_finder.keys():
+        if uid in self.page_finder:
             self.page_finder[uid].schedule_tofit(value)
 
@@ -821,5 +821,5 @@
         """
         sim_page_id = self.sim_page.uid
-        for uid, value in self.page_finder.iteritems():
+        for uid, value in self.page_finder.items():
             if uid != sim_page_id and uid != self.batch_page.uid:
                 model_list = value.get_model()
@@ -886,5 +886,5 @@
         Stop the fit
         """
-        if uid in self.fit_thread_list.keys():
+        if uid in self.fit_thread_list:
             calc_fit = self.fit_thread_list[uid]
             if calc_fit is not  None and calc_fit.isrunning():
@@ -898,7 +898,7 @@
         batch_flag = self.batch_page is not None and uid == self.batch_page.uid
         if sim_flag or batch_flag:
-            for uid, value in self.page_finder.iteritems():
+            for uid, value in self.page_finder.items():
                 if value.get_scheduled() == 1:
-                    if uid in self.fit_panel.opened_pages.keys():
+                    if uid in self.fit_panel.opened_pages:
                         panel = self.fit_panel.opened_pages[uid]
                         panel._on_fit_complete()
@@ -917,5 +917,5 @@
         :param draw: Determine if the theory needs to be plot
         """
-        if uid not in self.page_finder.keys():
+        if uid not in self.page_finder:
             return
         self.page_finder[uid].enable_smearing(flag=enable_smearer)
@@ -928,5 +928,5 @@
                 msg += "Got data = %s .\n" % str(data)
                 return
-                #raise ValueError, msg
+                #raise ValueError(msg)
             model = self.page_finder[uid].get_model(fid=fid)
             if model is None:
@@ -1030,5 +1030,5 @@
         list_page_id = []
         fit_id = 0
-        for page_id, page_info in self.page_finder.iteritems():
+        for page_id, page_info in self.page_finder.items():
             # For simulfit (uid give with None), do for-loop
             # if uid is specified (singlefit), do it only on the page.
@@ -1057,6 +1057,5 @@
 
                     pars = [str(element[1]) for element in page.param_toFit]
-                    fitproblem_list = page_info.values()
-                    for fitproblem in  fitproblem_list:
+                    for fitproblem in page_info.values():
                         if sim_fitter is None:
                             fitter = Fit()
@@ -1077,7 +1076,7 @@
                 wx.PostEvent(self.parent, evt)
                 return True
-            except:
+            except Exception as exc:
                 raise
-                msg = "Fitting error: %s" % str(sys.exc_value)
+                msg = "Fitting error: %s" % exc
                 evt = StatusEvent(status=msg, info="error", type="stop")
                 wx.PostEvent(self.parent, evt)
@@ -1136,5 +1135,5 @@
         :param fid: the id of the fitproblem(data, model, range,etc)
         """
-        if uid not in self.page_finder.keys():
+        if uid not in self.page_finder:
             return
         fitproblemList = self.page_finder[uid].get_fit_problem(fid)
@@ -1181,6 +1180,6 @@
                 evt = StatusEvent(status=msg, info="warning")
                 wx.PostEvent(self.parent, evt)
-        except Exception:
-            msg = "Creating Fit page: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "Creating Fit page: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
 
@@ -1262,9 +1261,9 @@
         # case that uid is not specified
         if uid is None:
-            for page_id in self.page_finder.keys():
+            for page_id in self.page_finder:
                 self.page_finder[page_id].schedule_tofit(value)
         # when uid is given
         else:
-            if uid in self.page_finder.keys():
+            if uid in self.page_finder:
                 self.page_finder[uid].schedule_tofit(value)
 
@@ -1300,5 +1299,5 @@
         panel = self.plot_panel
         if panel is None:
-            raise ValueError, "Fitting:_onSelect: NonType panel"
+            raise ValueError("Fitting:_onSelect: NonType panel")
         Plugin.on_perspective(self, event=event)
         self.select_data(panel)
@@ -1333,5 +1332,5 @@
         """
         uid = page_id[0]
-        if uid in self.fit_thread_list.keys():
+        if uid in self.fit_thread_list:
             del self.fit_thread_list[uid]
 
@@ -1359,16 +1358,16 @@
                 #get all fittable parameters of the current model
                 for param in  model.getParamList():
-                    if param  not in batch_outputs.keys():
+                    if param  not in batch_outputs:
                         batch_outputs[param] = []
                 for param in model.getDispParamList():
                     if not model.is_fittable(param) and \
-                        param in batch_outputs.keys():
+                        param in batch_outputs:
                         del batch_outputs[param]
                 # Add fitted parameters and their error
                 for param in res.param_list:
-                    if param not in batch_outputs.keys():
+                    if param not in batch_outputs:
                         batch_outputs[param] = []
                     err_param = "error on %s" % str(param)
-                    if err_param not in batch_inputs.keys():
+                    if err_param not in batch_inputs:
                         batch_inputs[err_param] = []
         msg = ""
@@ -1524,5 +1523,5 @@
         model = fitproblem.get_model()
         #fill batch result information
-        if "Data" not in batch_outputs.keys():
+        if "Data" not in batch_outputs:
             batch_outputs["Data"] = []
         cell = BatchCell()
@@ -1553,6 +1552,6 @@
         cell.object = [data, theory_data]
         batch_outputs["Data"].append(cell)
-        for key, value in data.meta_data.iteritems():
-            if key not in batch_inputs.keys():
+        for key, value in data.meta_data.items():
+            if key not in batch_inputs:
                 batch_inputs[key] = []
             #if key.lower().strip() != "loader":
@@ -1560,5 +1559,5 @@
         param = "temperature"
         if hasattr(data.sample, param):
-            if param not in  batch_inputs.keys():
+            if param not in  batch_inputs:
                 batch_inputs[param] = []
             batch_inputs[param].append(data.sample.temperature)
@@ -1631,7 +1630,6 @@
                     wx.PostEvent(self.parent, evt)
 
-        except Exception:
-            msg = ("Fit completed but the following error occurred: %s"
-                   % sys.exc_value)
+        except Exception as exc:
+            msg = "Fit completed but the following error occurred: %s" % exc
             #msg = "\n".join((traceback.format_exc(), msg))
             evt = StatusEvent(status=msg, info="warning", type="stop")
@@ -1721,5 +1719,5 @@
         if model is None:
             return
-        if uid not in self.page_finder.keys():
+        if uid not in self.page_finder:
             return
         # save the name containing the data name with the appropriate model
@@ -2078,7 +2076,7 @@
                                   source=source)
             self.calc_1D.queue()
-        except:
+        except Exception as exc:
             msg = " Error occurred when drawing %s Model 1D: " % model.name
-            msg += " %s" % sys.exc_value
+            msg += " %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg))
 
Index: src/sas/sasgui/perspectives/fitting/model_thread.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/model_thread.py	(revision 0f9ea1cee80a92f71e4bec65998ca56a3de65fd6)
+++ src/sas/sasgui/perspectives/fitting/model_thread.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -65,5 +65,5 @@
         if self.data is None:
             msg = "Compute Calc2D receive data = %s.\n" % str(self.data)
-            raise ValueError, msg
+            raise ValueError(msg)
 
         # Define matrix where data will be plotted
Index: src/sas/sasgui/perspectives/fitting/resultpanel.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/resultpanel.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/fitting/resultpanel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -59,5 +59,5 @@
     def on_plot_results(self, event):
         self.frame.Show(True)
-        result = event.result[0][0]
+        result = list(event.result[0])[0]
         filename = result.data.sas_data.filename
         current_time = datetime.datetime.now().strftime("%I:%M%p, %B %d, %Y")
Index: src/sas/sasgui/perspectives/fitting/simfitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -168,5 +168,5 @@
         i = 0
         for model in self.model_list:
-            model_id = self._format_id(model[1].keys()[0])
+            model_id = self._format_id(list(model[1].keys())[0])
             for saved_model in sim_state.model_list:
                 save_id = saved_model.pop('name')
@@ -328,5 +328,5 @@
         sizer.Add(tab_used, (iy, ix), (1, 1),
                   wx.EXPAND | wx.ADJUST_MINSIZE, 0)
-        for id, value in self.page_finder.iteritems():
+        for id, value in self.page_finder.items():
             if id not in self.parent.opened_pages:
                 continue
@@ -757,10 +757,10 @@
         self.set_button.Disable()
 
-        for id, model in self.constraint_dict.iteritems():
+        for id, model in self.constraint_dict.items():
             # check if all parameters have been selected for constraint
             # then do not allow add constraint on parameters
             self.model_cbox_left.Append(str(model.name), model)
         self.model_cbox_left.Select(0)
-        for id, model in self.constraint_dict.iteritems():
+        for id, model in self.constraint_dict.items():
             # check if all parameters have been selected for constraint
             # then do not allow add constraint on parameters
@@ -814,5 +814,5 @@
         model_right = self.model_cbox_right.GetValue()
         model_b = self.model_cbox_right.GetClientData(selection_b)
-        for id, dic_model in self.constraint_dict.iteritems():
+        for id, dic_model in self.constraint_dict.items():
             if model == dic_model:
                 param_list = self.page_finder[id].get_param2fit()
@@ -857,5 +857,5 @@
         if len(self.constraints_list) != 0:
             nb_fit_param = 0
-            for id, model in self.constraint_dict.iteritems():
+            for id, model in self.constraint_dict.items():
                 nb_fit_param += len(self.page_finder[id].get_param2fit())
             # Don't add anymore
@@ -879,5 +879,5 @@
         model_cbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
         model_cbox.Clear()
-        for id, model in self.constraint_dict.iteritems():
+        for id, model in self.constraint_dict.items():
             # check if all parameters have been selected for constraint
             # then do not allow add constraint on parameters
@@ -931,5 +931,5 @@
         hide buttons related constraint
         """
-        for id in self.page_finder.iterkeys():
+        for id in self.page_finder.keys():
             self.page_finder[id].clear_model_param()
 
@@ -969,5 +969,5 @@
         model = model_cbox.GetClientData(n)
         param_list = []
-        for id, dic_model in self.constraint_dict.iteritems():
+        for id, dic_model in self.constraint_dict.items():
             if model == dic_model:
                 param_list = self.page_finder[id].get_param2fit()
@@ -1056,5 +1056,5 @@
                 msg += " in combobox to set constraint! "
                 wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
-            for id, value in self.constraint_dict.iteritems():
+            for id, value in self.constraint_dict.items():
                 if model == value:
                     if constraint == "":
@@ -1080,5 +1080,5 @@
                         return False
 
-                    for fid in self.page_finder[id].iterkeys():
+                    for fid in self.page_finder[id].keys():
                         # wrap in param/constraint in str() to remove unicode
                         self.page_finder[id].set_model_param(str(param),
Index: src/sas/sasgui/perspectives/invariant/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/__init__.py	(revision 5a405bda2da663894b6b32ab94768a91d60525a4)
+++ src/sas/sasgui/perspectives/invariant/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -3,5 +3,5 @@
 
 from distutils.filelist import findall
-from invariant import *
+from .invariant import *
 
 def get_data_path(media):
Index: src/sas/sasgui/perspectives/invariant/invariant.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/invariant/invariant.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -133,5 +133,5 @@
             name = data.__class__.__name__
             msg = "Invariant use only Data1D got: [%s] " % str(name)
-            raise ValueError, msg
+            raise ValueError(msg)
         self.compute_helper(data=data)
 
@@ -144,4 +144,6 @@
         if data_list is None:
             data_list = []
+        else:
+            data_list = list(data_list)  # force iterator to list
         if len(data_list) >= 1:
             if len(data_list) == 1:
@@ -190,6 +192,6 @@
                 try:
                     self.compute_helper(data)
-                except:
-                    msg = "Invariant Set_data: " + str(sys.exc_value)
+                except Exception as exc:
+                    msg = "Invariant Set_data: " + str(exc)
                     wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
         else:
@@ -240,5 +242,5 @@
             msg = "invariant.save_file: the data being saved is"
             msg += " not a sas.sascalc.dataloader.data_info.Data1D object"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
     def set_state(self, state=None, datainfo=None):
@@ -258,5 +260,5 @@
                 msg = "invariant.set_state: datainfo parameter cannot"
                 msg += " be None in standalone mode"
-                raise RuntimeError, msg
+                raise RuntimeError(msg)
             # Make sure the user sees the invariant panel after loading
             # self.parent.set_perspective(self.perspective)
@@ -281,6 +283,6 @@
             self.on_set_state_helper(None)
 
-        except:
-            logger.error("invariant.set_state: %s" % sys.exc_value)
+        except Exception as exc:
+            logger.error("invariant.set_state: %s" % exc)
 
     def on_set_state_helper(self, event=None):
@@ -320,5 +322,5 @@
         else:
             msg = "Scale can not be zero."
-            raise ValueError, msg
+            raise ValueError(msg)
         if len(new_plot.x) == 0:
             return
Index: src/sas/sasgui/perspectives/invariant/invariant_details.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant_details.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/invariant/invariant_details.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,9 +2,11 @@
     Invariant panel
 """
+import sys
+
 import wx
-import sys
 
 from sas.sasgui.guiframe.utils import format_number
-from invariant_widgets import OutputTextCtrl
+from .invariant_widgets import OutputTextCtrl
+
 # Dimensions related to chart
 RECTANGLE_WIDTH = 400.0
@@ -33,5 +35,5 @@
 
 
-class InvariantContainer(wx.Object):
+class InvariantContainer:
     """
     This class stores some values resulting resulting from invariant
Index: src/sas/sasgui/perspectives/invariant/invariant_panel.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant_panel.py	(revision 44e8f48e6ada97db8eb483009edc07705c6329a2)
+++ src/sas/sasgui/perspectives/invariant/invariant_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -319,10 +319,10 @@
         background = self.background_tcl.GetValue().lstrip().rstrip()
         if background == "":
-            raise ValueError, "Need a background"
+            raise ValueError("Need a background")
         if check_float(self.background_tcl):
             return float(background)
         else:
             msg = "Receive invalid value for background : %s" % (background)
-            raise ValueError, msg
+            raise ValueError(msg)
 
     def get_scale(self):
@@ -332,5 +332,5 @@
         scale = self.scale_tcl.GetValue().lstrip().rstrip()
         if scale == "":
-            raise ValueError, "Need a background"
+            raise ValueError("Need a background")
         if check_float(self.scale_tcl):
             if float(scale) <= 0.0:
@@ -338,8 +338,8 @@
                 self.scale_tcl.Refresh()
                 msg = "Receive invalid value for scale: %s" % (scale)
-                raise ValueError, msg
+                raise ValueError(msg)
             return float(scale)
         else:
-            raise ValueError, "Receive invalid value for scale : %s" % (scale)
+            raise ValueError("Receive invalid value for scale : %s" % (scale))
 
     def get_contrast(self):
@@ -386,9 +386,9 @@
                 self.volume_tcl.SetValue(format_number(v))
                 self.volume_err_tcl.SetValue(format_number(dv))
-            except:
+            except Exception as exc:
                 self.volume_tcl.SetValue(format_number(None))
                 self.volume_err_tcl.SetValue(format_number(None))
                 msg = "Error occurred computing volume "
-                msg += " fraction: %s" % sys.exc_value
+                msg += " fraction: %s" % exc
                 wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                       info="error",
@@ -406,9 +406,9 @@
                 self.surface_tcl.SetValue(format_number(s))
                 self.surface_err_tcl.SetValue(format_number(ds))
-            except:
+            except Exception as exc:
                 self.surface_tcl.SetValue(format_number(None))
                 self.surface_err_tcl.SetValue(format_number(None))
                 msg = "Error occurred computing "
-                msg += "specific surface: %s" % sys.exc_value
+                msg += "specific surface: %s" % exc
                 wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
                                                       type="stop"))
@@ -426,5 +426,5 @@
             self.inv_container.qstar_total = qstar_total
             self.inv_container.qstar_total_err = qstar_total_err
-        except:
+        except Exception as exc:
             self.inv_container.qstar_total = "Error"
             self.inv_container.qstar_total_err = "Error"
@@ -432,5 +432,5 @@
             self.invariant_total_err_tcl.SetValue(format_number(None))
             msg = "Error occurred computing invariant using"
-            msg += " extrapolation: %s" % sys.exc_value
+            msg += " extrapolation: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
 
@@ -450,10 +450,10 @@
                 self._manager.plot_theory(data=extrapolated_data,
                                           name="Low-Q extrapolation")
-            except:
+            except Exception as exc:
                 self.inv_container.qstar_low = "ERROR"
                 self.inv_container.qstar_low_err = "ERROR"
                 self._manager.plot_theory(name="Low-Q extrapolation")
                 msg = "Error occurred computing low-Q "
-                msg += "invariant: %s" % sys.exc_value
+                msg += "invariant: %s" % exc
                 wx.PostEvent(self.parent,
                              StatusEvent(status=msg, type="stop"))
@@ -462,6 +462,6 @@
             try:
                 self._manager.plot_theory(name="Low-Q extrapolation")
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
 
     def get_high_qstar(self, inv, high_q=False):
@@ -483,5 +483,5 @@
                 self._manager.plot_theory(data=high_out_data,
                                           name="High-Q extrapolation")
-            except:
+            except Exception as exc:
                 #raise
                 self.inv_container.qstar_high = "ERROR"
@@ -489,5 +489,5 @@
                 self._manager.plot_theory(name="High-Q extrapolation")
                 msg = "Error occurred computing high-Q "
-                msg += "invariant: %s" % sys.exc_value
+                msg += "invariant: %s" % exc
                 wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                       type="stop"))
@@ -496,6 +496,6 @@
             try:
                 self._manager.plot_theory(name="High-Q extrapolation")
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
 
     def get_qstar(self, inv):
@@ -625,6 +625,6 @@
             background = self.get_background()
             scale = self.get_scale()
-        except:
-            msg = "Invariant Error: %s" % (sys.exc_value)
+        except Exception as exc:
+            msg = "Invariant Error: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
             return
@@ -641,6 +641,6 @@
             inv, npts_low = self.set_extrapolation_low(inv=inv, low_q=low_q)
             inv, npts_high = self.set_extrapolation_high(inv=inv, high_q=high_q)
-        except:
-            msg = "Error occurred computing invariant: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "Error occurred computing invariant: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                   info="warning", type="stop"))
@@ -652,6 +652,6 @@
         try:
             self.get_qstar(inv=inv)
-        except:
-            msg = "Error occurred computing invariant: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "Error occurred computing invariant: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                   info="warning",
@@ -674,7 +674,6 @@
             porod_const = self.get_porod_const()
             contrast = self.get_contrast()
-        except:
-            msg = r_msg + "Error occurred computing invariant: %s" % \
-                                                            sys.exc_value
+        except Exception as exc:
+            msg = r_msg + "Error occurred computing invariant: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                   info="error",
@@ -685,6 +684,6 @@
                             extrapolation=extrapolation)
             #compute surface and set value to txtcrtl
-        except:
-            msg = "Error occurred computing invariant: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "Error occurred computing invariant: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                   info="warning",
@@ -695,6 +694,6 @@
                              extrapolation=extrapolation)
 
-        except:
-            msg = "Error occurred computing invariant: %s" % sys.exc_value
+        except Exception as exc:
+            msg = "Error occurred computing invariant: %s" % exc
             wx.PostEvent(self.parent, StatusEvent(status=msg,
                                                   info="warning",
@@ -845,11 +844,13 @@
             if attr.__class__.__name__ == "StaticText":
                 return
-            if value in ["True", "False", True, False]:
-                value = bool(value)
+            if value in ["True", True]:
+                value = 1
+            elif value in ["False", False]:
+                value = 0
             else:
                 value = str(value)
             attr.SetValue(value)
-        except:
-            logger.error("Invariant state: %s", sys.exc_value)
+        except Exception as exc:
+            logger.error("Invariant state: %s", exc)
 
     def get_bookmark_by_num(self, num=None):
@@ -867,7 +868,7 @@
         try:
             _, _, current_state, comp_state = self.state.bookmark_list[int(num)]
-        except:
-            logger.error(sys.exc_value)
-            raise ValueError, "No such bookmark exists"
+        except Exception as exc:
+            logger.error(exc)
+            raise ValueError("No such bookmark exists")
 
         # set the parameters
@@ -962,6 +963,6 @@
             self.state.state_list[str(self.state.state_num)] = \
                     self.state.clone_state()
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
         self._set_undo_flag(True)
@@ -1006,6 +1007,6 @@
             try:
                 del self.state.state_list[str(i)]
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
         # Enable the undo button if it was not
         self._set_undo_flag(True)
@@ -1071,6 +1072,6 @@
             try:
                 del self.state.state_list[str(i)]
-            except:
-                logger.error(sys.exc_value)
+            except Exception as exc:
+                logger.error(exc)
 
         # try to add new state of the text changes in the state_list
@@ -1086,6 +1087,6 @@
             self.state.state_num = self.state.saved_state['state_num']
             self.state.state_list[str(self.state.state_num)] = self.state.clone_state()
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
         self._set_undo_flag(True)
@@ -1108,6 +1109,6 @@
             self.state.saved_state[name] = str(value)
             self.state.state_list[str(self.state.state_num)] = self.state.clone_state()
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
     def _get_input_list(self):
@@ -1118,6 +1119,6 @@
         compute_num = self.state.saved_state['compute_num']
         # find values and put into the input list
-        for key1, value1 in self.state.state_list[str(compute_num)].iteritems():
-            for key, _ in self.state.input_list.iteritems():
+        for key1, value1 in self.state.state_list[str(compute_num)].items():
+            for key, _ in self.state.input_list.items():
                 if key == key1:
                     self.state.input_list[key] = value1
Index: src/sas/sasgui/perspectives/invariant/invariant_state.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant_state.py	(revision e9920cd1fef162ad399cc9f42a588bb14006c327)
+++ src/sas/sasgui/perspectives/invariant/invariant_state.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,4 +2,5 @@
     State class for the invariant UI
 """
+from __future__ import print_function
 
 # import time
@@ -140,5 +141,5 @@
 
         # text ctl general inputs ( excluding extrapolation text ctl)
-        for key, value in self.input_list.iteritems():
+        for key, value in self.input_list.items():
             if value == '':
                 continue
@@ -166,5 +167,5 @@
         low_off = False
         high_off = False
-        for key, value in self.input_list.iteritems():
+        for key, value in self.input_list.items():
             key_split = key.split('_')
             max_ind = len(key_split) - 1
@@ -216,5 +217,5 @@
                 # other outputs than Q*
                 name = item[0] + "_tcl"
-                if name in self.saved_state.keys():
+                if name in self.saved_state:
                     value = self.saved_state[name]
 
@@ -301,5 +302,5 @@
         top_element.appendChild(state)
 
-        for name, value in self.saved_state.iteritems():
+        for name, value in self.saved_state.items():
             element = newdoc.createElement(str(name))
             element.appendChild(newdoc.createTextNode(str(value)))
@@ -310,7 +311,7 @@
         top_element.appendChild(history)
 
-        for name, value in self.state_list.iteritems():
+        for name, value in self.state_list.items():
             history_element = newdoc.createElement('state_' + str(name))
-            for state_name, state_value in value.iteritems():
+            for state_name, state_value in value.items():
                 state_element = newdoc.createElement(str(state_name))
                 child = newdoc.createTextNode(str(state_value))
@@ -325,5 +326,5 @@
         top_element.appendChild(bookmark)
         item_list = ['time', 'date', 'state', 'comp_state']
-        for name, value_list in self.bookmark_list.iteritems():
+        for name, value_list in self.bookmark_list.items():
             element = newdoc.createElement('mark_' + str(name))
             _, date, state, comp_state = value_list
@@ -334,10 +335,10 @@
             state_list_element = newdoc.createElement('state')
             comp_state_list_element = newdoc.createElement('comp_state')
-            for state_name, state_value in value_list[2].iteritems():
+            for state_name, state_value in value_list[2].items():
                 state_element = newdoc.createElement(str(state_name))
                 child = newdoc.createTextNode(str(state_value))
                 state_element.appendChild(child)
                 state_list_element.appendChild(state_element)
-            for comp_name, comp_value in value_list[3].iteritems():
+            for comp_name, comp_value in value_list[3].items():
                 comp_element = newdoc.createElement(str(comp_name))
                 comp_element.appendChild(newdoc.createTextNode(str(comp_value)))
@@ -368,5 +369,5 @@
             msg = "InvariantSate no longer supports non-CanSAS"
             msg += " format for invariant files"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         if node.get('version')\
@@ -383,7 +384,7 @@
                 try:
                     timestamp = (entry.get('epoch'))
-                except:
+                except Exception as exc:
                     msg = "InvariantSate.fromXML: Could not read"
-                    msg += " timestamp\n %s" % sys.exc_value
+                    msg += " timestamp\n %s" % exc
                     logger.error(msg)
 
@@ -453,49 +454,46 @@
         Get the values (strings) from __str__ for report
         """
-        strings = self.__str__()
-
         # default string values
-        for num in range(1, 19):
-            exec "s_%s = 'NA'" % str(num)
-        lines = strings.split('\n')
+        s = {num: 'NA' for num in range(1, 19)}
         # get all string values from __str__()
-        for line in range(0, len(lines)):
-            if line == 1:
-                s_1 = lines[1]
-            elif line == 2:
-                s_2 = lines[2]
+        lines = str(self).split('\n')
+        for line_num, line in enumerate(lines):
+            if line_num == 1:
+                s[1] = line
+            elif line_num == 2:
+                s[2] = line
             else:
-                item = lines[line].split(':')
+                item = line.split(':')
                 item[0] = item[0].strip()
                 if item[0] == "scale":
-                    s_3 = item[1]
+                    s[3] = item[1]
                 elif item[0] == "porod constant":
-                    s_4 = item[1]
+                    s[4] = item[1]
                 elif item[0] == "background":
-                    s_5 = item[1]
+                    s[5] = item[1]
                 elif item[0] == "contrast":
-                    s_6 = item[1]
+                    s[6] = item[1]
                 elif item[0] == "Extrapolation":
                     extra = item[1].split(";")
                     bool_0 = extra[0].split("=")
                     bool_1 = extra[1].split("=")
-                    s_8 = " " + bool_0[0] + "Q region = " + bool_0[1]
-                    s_7 = " " + bool_1[0] + "Q region = " + bool_1[1]
+                    s[8] = " " + bool_0[0] + "Q region = " + bool_0[1]
+                    s[7] = " " + bool_1[0] + "Q region = " + bool_1[1]
                 elif item[0] == "npts low":
-                    s_9 = item[1]
+                    s[9] = item[1]
                 elif item[0] == "npts high":
-                    s_10 = item[1]
+                    s[10] = item[1]
                 elif item[0] == "volume fraction":
                     val = item[1].split("+-")[0].strip()
                     error = item[1].split("+-")[1].strip()
-                    s_17 = val + " &plusmn; " + error
+                    s[17] = val + " &plusmn; " + error
                 elif item[0] == "specific surface":
                     val = item[1].split("+-")[0].strip()
                     error = item[1].split("+-")[1].strip()
-                    s_18 = val + " &plusmn; " + error
+                    s[18] = val + " &plusmn; " + error
                 elif item[0].split("(")[0].strip() == "power low":
-                    s_11 = item[0] + " =" + item[1]
+                    s[11] = item[0] + " =" + item[1]
                 elif item[0].split("(")[0].strip() == "power high":
-                    s_12 = item[0] + " =" + item[1]
+                    s[12] = item[0] + " =" + item[1]
                 elif item[0].split("[")[0].strip() == "Q* from low Q extrapolation":
                     # looks messy but this way the symbols +_ and % work on html
@@ -504,5 +502,5 @@
                     err = error.split("%")[0].strip()
                     percent = error.split("%")[1].strip()
-                    s_13 = val + " &plusmn; " + err + "&#37" + percent
+                    s[13] = val + " &plusmn; " + err + "&#37" + percent
                 elif item[0].split("[")[0].strip() == "Q* from data":
                     val = item[1].split("+-")[0].strip()
@@ -510,5 +508,5 @@
                     err = error.split("%")[0].strip()
                     percent = error.split("%")[1].strip()
-                    s_14 = val + " &plusmn; " + err + "&#37" + percent
+                    s[14] = val + " &plusmn; " + err + "&#37" + percent
                 elif item[0].split("[")[0].strip() == "Q* from high Q extrapolation":
                     val = item[1].split("+-")[0].strip()
@@ -516,13 +514,13 @@
                     err = error.split("%")[0].strip()
                     percent = error.split("%")[1].strip()
-                    s_15 = val + " &plusmn; " + err + "&#37" + percent
+                    s[15] = val + " &plusmn; " + err + "&#37" + percent
                 elif item[0].split("[")[0].strip() == "total Q*":
                     val = item[1].split("+-")[0].strip()
                     error = item[1].split("+-")[1].strip()
-                    s_16 = val + " &plusmn; " + error
+                    s[16] = val + " &plusmn; " + error
                 else:
                     continue
 
-        s_1 = self._check_html_format(s_1)
+        s[1] = self._check_html_format(s[1])
         file_name = self._check_html_format(self.file)
 
@@ -530,8 +528,7 @@
         self.set_plot_state(extra_high=bool_0[1], extra_low=bool_1[1])
         # get ready for report with setting all the html strings
-        self.report_str = str(self.template_str) % (s_1, s_2,
-                                                    s_3, s_4, s_5, s_6, s_7, s_8,
-                                                    s_9, s_10, s_11, s_12, s_13, s_14, s_15,
-                                                    s_16, s_17, s_18, file_name, "%s")
+        self.report_str = str(self.template_str) % (
+            s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
+            s[12], s[13], s[14], s[15], s[16], s[17], s[18], file_name, "%s")
 
     def _check_html_format(self, name):
@@ -692,7 +689,7 @@
                 state = InvariantState()
                 state.fromXML(node=nodes[0])
-        except:
+        except Exception as exc:
             msg = "XML document does not contain invariant"
-            msg += " information.\n %s" % sys.exc_value
+            msg += " information.\n %s" % exc
             logger.info(msg)
         return state
@@ -736,5 +733,5 @@
                         output.append(sas_entry)
         else:
-            raise RuntimeError, "%s is not a file" % path
+            raise RuntimeError("%s is not a file" % path)
 
         # Return output consistent with the loader's api
@@ -782,5 +779,5 @@
             msg = "The cansas writer expects a Data1D"
             msg += " instance: %s" % str(datainfo.__class__.__name__)
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
         # make sure title and data run is filled up.
         if datainfo.title is None or datainfo.title == '':
Index: src/sas/sasgui/perspectives/invariant/report_dialog.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/report_dialog.py	(revision d0ce666faaf0f9284be2e607a0ac846cdd9a682b)
+++ src/sas/sasgui/perspectives/invariant/report_dialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -92,7 +92,7 @@
                         # Mac
                         os.system("open %s" % fName)
-                    except:
+                    except Exception as exc:
                         # DO not open
-                        logger.error("Could not open file: %s" % sys.exc_value)
+                        logger.error("Could not open file: %s" % exc)
             # delete image file
             os.remove(pic_fname)
Index: src/sas/sasgui/perspectives/pr/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/pr/__init__.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/pr/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,2 +1,2 @@
 PLUGIN_ID = "P(r) plug-in 1.0"
-from pr import *
+from .pr import *
Index: src/sas/sasgui/perspectives/pr/explore_dialog.py
===================================================================
--- src/sas/sasgui/perspectives/pr/explore_dialog.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/perspectives/pr/explore_dialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -35,5 +35,5 @@
 from sas.sasgui.plottools.plottables import Graph
 
-from pr_widgets import PrTextCtrl
+from .pr_widgets import PrTextCtrl
 
 # Default number of points on the output plot
@@ -416,8 +416,8 @@
                 results.pos_err.append(pos_err)
                 results.osc.append(osc)
-            except:
+            except Exception as exc:
                 # This inversion failed, skip this D_max value
                 msg = "ExploreDialog: inversion failed "
-                msg += "for D_max=%s\n%s" % (str(d), sys.exc_value)
+                msg += "for D_max=%s\n%s" % (str(d), exc)
                 logger.error(msg)
 
Index: src/sas/sasgui/perspectives/pr/inversion_panel.py
===================================================================
--- src/sas/sasgui/perspectives/pr/inversion_panel.py	(revision cb62bd55ae79395ac6e01d8b2077f4d496625c41)
+++ src/sas/sasgui/perspectives/pr/inversion_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -5,16 +5,19 @@
 __revision__ = "$Revision: 1193 $"
 
-import wx
 import os
 import sys
 import logging
+
+import wx
 from wx.lib.scrolledpanel import ScrolledPanel
+
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.panel_base import PanelBase
-from inversion_state import InversionState
-from pr_widgets import PrTextCtrl
-from pr_widgets import DataFileTextCtrl
-from pr_widgets import OutputTextCtrl
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+from .inversion_state import InversionState
+from .pr_widgets import PrTextCtrl
+from .pr_widgets import DataFileTextCtrl
+from .pr_widgets import OutputTextCtrl
 
 logger = logging.getLogger(__name__)
@@ -752,7 +755,7 @@
         except ValueError:
             logger.error("InversionControl._on_accept_alpha got a value that was not a number: %s" % alpha )
-        except:
+        except Exception as exc:
             # No estimate or bad estimate, either do nothing
-            logger.error("InversionControl._on_accept_alpha: %s" % sys.exc_value)
+            logger.error("InversionControl._on_accept_alpha: %s" % exc)
 
     def _on_accept_nterms(self, evt):
@@ -768,7 +771,7 @@
         except ValueError:
             logger.error("InversionControl._on_accept_nterms got a value that was not a number: %s" % nterms )
-        except:
+        except Exception as exc:
             # No estimate or bad estimate, either do nothing
-            logger.error("InversionControl._on_accept_nterms: %s" % sys.exc_value)
+            logger.error("InversionControl._on_accept_nterms: %s" % exc)
 
     def clear_panel(self):
@@ -901,5 +904,5 @@
                 message += "than the number of points"
                 wx.PostEvent(self._manager.parent, StatusEvent(status=message))
-                raise ValueError, message
+                raise ValueError(message)
             self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
             self.nfunc_ctl.Refresh()
@@ -957,5 +960,5 @@
         Invoke the d_max exploration dialog
         """
-        from explore_dialog import ExploreDialog
+        from .explore_dialog import ExploreDialog
         if self._manager._last_pr is not None:
             pr = self._manager._create_plot_pr()
@@ -1008,6 +1011,6 @@
                 self._on_invert(None)
                 self._set_analysis(True)
-            except:
-                msg = "InversionControl._change_file: %s" % sys.exc_value
+            except Exception as exc:
+                msg = "InversionControl._change_file: %s" % exc
                 logger.error(msg)
 
Index: src/sas/sasgui/perspectives/pr/inversion_state.py
===================================================================
--- src/sas/sasgui/perspectives/pr/inversion_state.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/perspectives/pr/inversion_state.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -18,4 +18,5 @@
 import logging
 from lxml import etree
+
 from sas.sasgui.guiframe.dataFitting import Data1D
 from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
@@ -238,5 +239,5 @@
             msg = "InversionState no longer supports non-CanSAS"
             msg += " format for P(r) files"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         if node.get('version') and node.get('version') == '1.0':
@@ -252,7 +253,7 @@
                 try:
                     self.timestamp = float(entry.get('epoch'))
-                except:
+                except Exception as exc:
                     msg = "InversionState.fromXML: Could not read "
-                    msg += "timestamp\n %s" % sys.exc_value
+                    msg += "timestamp\n %s" % exc
                     logger.error(msg)
 
@@ -432,7 +433,7 @@
                 state = InversionState()
                 state.fromXML(node=nodes[0])
-        except:
+        except Exception as exc:
             msg = "XML document does not contain P(r) "
-            msg += "information.\n %s" % sys.exc_value
+            msg += "information.\n %s" % exc
             logger.info(msg)
 
@@ -481,5 +482,5 @@
                         output.append(sas_entry)
         else:
-            raise RuntimeError, "%s is not a file" % path
+            raise RuntimeError("%s is not a file" % path)
 
         # Return output consistent with the loader's api
@@ -525,5 +526,5 @@
             msg = "The cansas writer expects a Data1D "
             msg += "instance: %s" % str(datainfo.__class__.__name__)
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
         # Create basic XML document
Index: src/sas/sasgui/perspectives/pr/pr.py
===================================================================
--- src/sas/sasgui/perspectives/pr/pr.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/perspectives/pr/pr.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -18,10 +18,12 @@
 
 import sys
-import wx
 import logging
 import time
 import math
+
+import wx
 import numpy as np
 import pylab
+
 from sas.sasgui.guiframe.gui_manager import MDIFrame
 from sas.sasgui.guiframe.dataFitting import Data1D
@@ -33,6 +35,15 @@
 import sas.sascalc.dataloader
 
-from pr_widgets import load_error
 from sas.sasgui.guiframe.plugin_base import PluginBase
+
+from .inversion_state import Reader  # .prv file reader
+from .inversion_panel import InversionControl
+#from .inversion_panel import HelpDialog
+from .inversion_panel import PrDistDialog
+from .pr_thread import CalcPr
+from .pr_thread import EstimatePr
+from .pr_thread import EstimateNT
+from .pr_widgets import load_error
+from .pr_widgets import DataDialog
 
 logger = logging.getLogger(__name__)
@@ -106,7 +117,4 @@
         self.list_plot_id = []
 
-        # Associate the inversion state reader with .prv files
-        from inversion_state import Reader
-
         # Create a CanSAS/Pr reader
         self.state_reader = Reader(self.set_state)
@@ -151,5 +159,5 @@
                 msg = "Pr.set_state: datainfo parameter cannot "
                 msg += "be None in standalone mode"
-                raise RuntimeError, msg
+                raise RuntimeError(msg)
 
             # Ensuring that plots are coordinated correctly
@@ -184,6 +192,6 @@
                                                    title=self.current_plottable.title))
             self.control_panel.set_state(state)
-        except:
-            logger.error("prview.set_state: %s" % sys.exc_value)
+        except Exception as exc:
+            logger.error("prview.set_state: %s" % exc)
 
 
@@ -195,5 +203,4 @@
 
         """
-        from inversion_panel import HelpDialog
         dialog = HelpDialog(None, -1)
         if dialog.ShowModal() == wx.ID_OK:
@@ -369,5 +376,4 @@
         Redisplay P(r) with a different number of points
         """
-        from inversion_panel import PrDistDialog
         dialog = PrDistDialog(None, -1)
         dialog.set_content(self._pr_npts)
@@ -452,5 +458,5 @@
         # Notify the user if we could not read the file
         if dataread is None:
-            raise RuntimeError, "Invalid data"
+            raise RuntimeError("Invalid data")
 
         x = None
@@ -472,5 +478,5 @@
                 if dataread is None:
                     return x, y, err
-                raise RuntimeError, "This tool can only read 1D data"
+                raise RuntimeError("This tool can only read 1D data")
 
         self._current_file_data.x = x
@@ -511,6 +517,6 @@
                     data_y = np.append(data_y, y)
                     data_err = np.append(data_err, err)
-                except:
-                    logger.error(sys.exc_value)
+                except Exception as exc:
+                    logger.error(exc)
 
         if scale is not None:
@@ -562,6 +568,6 @@
                         data_y = np.append(data_y, y)
                         data_err = np.append(data_err, err)
-                    except:
-                        logger.error(sys.exc_value)
+                    except Exception as exc:
+                        logger.error(exc)
                 elif line.find("The 6 columns") >= 0:
                     data_started = True
@@ -720,6 +726,4 @@
             Start a calculation thread
         """
-        from pr_thread import CalcPr
-
         # If a thread is already started, stop it
         if self.calc_thread is not None and self.calc_thread.isrunning():
@@ -849,12 +853,12 @@
             try:
                 pr = self._create_file_pr(data)
-            except:
-                status = "Problem reading data: %s" % sys.exc_value
+            except Exception as exc:
+                status = "Problem reading data: %s" % exc
                 wx.PostEvent(self.parent, StatusEvent(status=status))
-                raise RuntimeError, status
+                raise RuntimeError(status)
 
             # If the file contains nothing, just return
             if pr is None:
-                raise RuntimeError, "Loaded data is invalid"
+                raise RuntimeError("Loaded data is invalid")
 
             self.pr = pr
@@ -906,5 +910,5 @@
             msg = "pr.save_data: the data being saved is not a"
             msg += " sas.data_info.Data1D object"
-            raise RuntimeError, msg
+            raise RuntimeError(msg)
 
     def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None,
@@ -928,6 +932,6 @@
                 self.pr = pr
                 self.perform_inversion()
-        except:
-            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+        except Exception as exc:
+            wx.PostEvent(self.parent, StatusEvent(status=exc))
 
     def estimate_plot_inversion(self, alpha, nfunc, d_max,
@@ -952,6 +956,6 @@
                 self.pr = pr
                 self.perform_estimate()
-        except:
-            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+        except Exception as exc:
+            wx.PostEvent(self.parent, StatusEvent(status=exc))
 
     def _create_plot_pr(self, estimate=False):
@@ -1033,6 +1037,6 @@
                 self.pr = pr
                 self.perform_inversion()
-        except:
-            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+        except Exception as exc:
+            wx.PostEvent(self.parent, StatusEvent(status=exc))
 
     def estimate_file_inversion(self, alpha, nfunc, d_max, data,
@@ -1056,6 +1060,6 @@
                 self.pr = pr
                 self.perform_estimate()
-        except:
-            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+        except Exception as exc:
+            wx.PostEvent(self.parent, StatusEvent(status=exc))
 
     def _create_file_pr(self, data):
@@ -1085,6 +1089,6 @@
             self._current_file_data.err = data.dy
             x, y, err = data.x, data.y, data.dy
-        except:
-            load_error(sys.exc_value)
+        except Exception as exc:
+            load_error(exc)
             return None
 
@@ -1124,6 +1128,6 @@
             pr.slit_width = self.slit_width
             return pr
-        except:
-            load_error(sys.exc_value)
+        except Exception as exc:
+            load_error(exc)
         return None
 
@@ -1132,6 +1136,4 @@
             Perform parameter estimation
         """
-        from pr_thread import EstimatePr
-
         # If a thread is already started, stop it
         if self.estimation_thread is not None and \
@@ -1162,6 +1164,4 @@
             Perform parameter estimation
         """
-        from pr_thread import EstimateNT
-
         # If a thread is already started, stop it
         if self.estimation_thread is not None and self.estimation_thread.isrunning():
@@ -1239,6 +1239,4 @@
             Create and return a list of panel objects
         """
-        from inversion_panel import InversionControl
-
         self.parent = parent
         self.frame = MDIFrame(self.parent, None, 'None', (100, 200))
@@ -1262,4 +1260,6 @@
         if data_list is None:
             data_list = []
+        else:
+            data_list = list(data_list)  # force iterator to list
         if len(data_list) >= 1:
             if len(data_list) == 1:
@@ -1287,5 +1287,4 @@
                 msg += "Please select one.\n"
                 if len(data_list) > 1:
-                    from pr_widgets import DataDialog
                     dlg = DataDialog(data_list=data_1d_list, text=msg)
                     if dlg.ShowModal() == wx.ID_OK:
@@ -1306,6 +1305,6 @@
                     self.data_id = data.id
                     self.control_panel._change_file(evt=None, data=data)
-                except:
-                    msg = "Prview Set_data: " + str(sys.exc_value)
+                except Exception as exc:
+                    msg = "Prview Set_data: " + str(exc)
                     wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
             else:
Index: src/sas/sasgui/perspectives/pr/pr_thread.py
===================================================================
--- src/sas/sasgui/perspectives/pr/pr_thread.py	(revision ac07a3acc20dbf79c564bf642aece71a02c2c862)
+++ src/sas/sasgui/perspectives/pr/pr_thread.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -41,7 +41,7 @@
             # Thread was interrupted, just proceed
             pass
-        except:
+        except Exception as exc:
             if self.error_func is not None:
-                self.error_func("CalcPr.compute: %s" % sys.exc_value)
+                self.error_func("CalcPr.compute: %s" % exc)
 
 class EstimatePr(CalcThread):
@@ -69,7 +69,7 @@
             # Thread was interrupted, just proceed
             pass
-        except:
+        except Exception as exc:
             if self.error_func is not None:
-                self.error_func("EstimatePr.compute: %s" % sys.exc_value)
+                self.error_func("EstimatePr.compute: %s" % exc)
 
 class EstimateNT(CalcThread):
@@ -109,5 +109,5 @@
             # Thread was interrupted, just proceed
             pass
-        except:
+        except Exception as exc:
             if self.error_func is not None:
-                self.error_func("EstimatePr2.compute: %s" % sys.exc_value)
+                self.error_func("EstimatePr2.compute: %s" % exc)
Index: src/sas/sasgui/perspectives/simulation/ShapeAdapter.py
===================================================================
--- src/sas/sasgui/perspectives/simulation/ShapeAdapter.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/simulation/ShapeAdapter.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -87,5 +87,5 @@
             self.sim_canvas._model_changed()
         else:
-            raise ValueError, "SimShapeVisitor: Wrong class for visited object"
+            raise ValueError("SimShapeVisitor: Wrong class for visited object")
         
         
@@ -115,5 +115,5 @@
             self.sim_canvas._model_changed()
         else:
-            raise ValueError, "SimShapeVisitor: Wrong class for visited object"
+            raise ValueError("SimShapeVisitor: Wrong class for visited object")
         
     
@@ -130,4 +130,4 @@
             shape.accept_update(self)
         else:
-            raise ValueError, "ShapeAdapter: Shape [%s] not in list" % shape.name
+            raise ValueError("ShapeAdapter: Shape [%s] not in list" % shape.name)
 
Index: src/sas/sasgui/perspectives/simulation/ShapeParameters.py
===================================================================
--- src/sas/sasgui/perspectives/simulation/ShapeParameters.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sasgui/perspectives/simulation/ShapeParameters.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -10,9 +10,11 @@
 from __future__ import print_function
 
+import sys
+from copy import deepcopy
+
 import wx
-import sys
 import wx.lib.newevent
-from copy import deepcopy
-import SimCanvas
+
+from . import SimCanvas
 
 (CreateShapeEvent, EVT_ADD_SHAPE) = wx.lib.newevent.NewEvent()
@@ -201,8 +203,6 @@
             n = 1
             self.parameters = []
-            keys = shape.params.keys()
-            keys.sort()
-            
-            for item in keys:
+            
+            for item in sorted(shape.params.keys()):
                 if item in ['contrast', 'order']:
                     continue
@@ -393,7 +393,7 @@
                 if not tmp==None:
                     self.current_shape.params[item[0]] = tmp 
-        except:
+        except Exception as exc:
             print("Could not create")
-            print(sys.exc_value)
+            print(exc)
                 
     def _onCreate(self, evt):
@@ -489,3 +489,2 @@
             print("NOT YET IMPLMENTED")
             print("renaming", self.shape_listbox.GetString(indices[0]))
-                
Index: src/sas/sasgui/perspectives/simulation/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/simulation/__init__.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/simulation/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,2 +1,2 @@
 PLUGIN_ID = "Simulation plug-in 1.0"
-from simulation import *
+from .simulation import *
Index: src/sas/sasgui/perspectives/simulation/simulation.py
===================================================================
--- src/sas/sasgui/perspectives/simulation/simulation.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/simulation/simulation.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -15,13 +15,12 @@
 
 # Application imports
-import SimCanvas
-import ShapeParameters
-import ShapeAdapter
+from sas.sascalc.realspace import VolumeCanvas
+from sas.sascalc.data_util.calcthread import CalcThread
 from sas.sasgui.guiframe.dataFitting import Data1D
-# Real-space simulation import
-import sas.sascalc.realspace.VolumeCanvas as VolumeCanvas
-
-from sas.sascalc.data_util.calcthread import CalcThread
 from sas.guicomm.events import NewPlotEvent, StatusEvent    
+
+from . import SimCanvas
+from . import ShapeParameters
+from . import ShapeAdapter
 
 logger = logging.getLogger(__name__)
Index: src/sas/sasgui/plottools/LineModel.py
===================================================================
--- src/sas/sasgui/plottools/LineModel.py	(revision a26f67f1457e7e39b79d4c3800c03bcaa208dff7)
+++ src/sas/sasgui/plottools/LineModel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -83,5 +83,5 @@
         elif x.__class__.__name__ == 'tuple':
             msg = "Tuples are not allowed as input to BaseComponent models"
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             return self._line(x)
@@ -105,5 +105,5 @@
         elif x.__class__.__name__ == 'tuple':
             msg = "Tuples are not allowed as input to BaseComponent models"
-            raise ValueError, msg
+            raise ValueError(msg)
         else:
             return self._line(x)
Index: src/sas/sasgui/plottools/PlotPanel.py
===================================================================
--- src/sas/sasgui/plottools/PlotPanel.py	(revision 75313af5ff7a47b13075dd439585c592071aebaf)
+++ src/sas/sasgui/plottools/PlotPanel.py	(revision 75313af5ff7a47b13075dd439585c592071aebaf)
@@ -6,5 +6,12 @@
 import logging
 import traceback
+import math
+import os
+import operator
+import copy
+
 import wx
+import numpy as np
+
 # Try a normal import first
 # If it fails, try specifying a version
@@ -15,25 +22,20 @@
 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
 from matplotlib.figure import Figure
-import os
-import transform
-#TODO: make the plottables interactive
-from binder import BindArtist
 from matplotlib.font_manager import FontProperties
-DEBUG = False
-
-from plottables import Graph
-from TextDialog import TextDialog
-from LabelDialog import LabelDialog
-import operator
-
-import math
 import pylab
 DEFAULT_CMAP = pylab.cm.jet
-import copy
-import numpy as np
 
 from sas.sasgui.guiframe.events import StatusEvent
+
+#TODO: make the plottables interactive
+from . import transform
+from .TextDialog import TextDialog
+from .LabelDialog import LabelDialog
+from .binder import BindArtist
+from .convert_units import convert_unit
+from .plottables import Graph
 from .toolbar import NavigationToolBar, PlotPrintout, bind
 
+DEBUG = False
 logger = logging.getLogger(__name__)
 
@@ -44,5 +46,4 @@
         for a in obj.get_children(): show_tree(a, d + 1)
 
-from convert_units import convert_unit
 
 
@@ -116,5 +117,5 @@
         self.figure = Figure(None, dpi, linewidth=2.0)
         self.color = '#b3b3b3'
-        from canvas import FigureCanvas
+        from .canvas import FigureCanvas
         self.canvas = FigureCanvas(self, -1, self.figure)
         self.SetColor(color)
@@ -657,8 +658,8 @@
         else:
             plot_dict = plotlist
-        from fitDialog import LinearFit
+        from .fitDialog import LinearFit
 
         if len(plot_dict.keys()) > 0:
-            first_item = plot_dict.keys()[0]
+            first_item = list(plot_dict.keys())[0]
             dlg = LinearFit(parent=None, plottable=first_item,
                             push_data=self.onFitDisplay,
@@ -703,5 +704,5 @@
 
         """
-        from fitDialog import LinearFit
+        from .fitDialog import LinearFit
         if self._fit_dialog is not None:
             return
@@ -736,5 +737,5 @@
             first_item = plot_list.keys()[0]
             if first_item.x != []:
-                from PropertyDialog import Properties
+                from .PropertyDialog import Properties
                 dial = Properties(self, -1, 'Properties')
                 dial.setValues(self.prevXtrans, self.prevYtrans, self.viewModel)
@@ -1001,7 +1002,6 @@
         Remove legend for ax or the current axes.
         """
-        from pylab import gca
         if ax is None:
-            ax = gca()
+            ax = pylab.gca()
         ax.legend_ = None
 
@@ -1327,5 +1327,5 @@
         if id is None:
             id = name
-        from plottable_interactor import PointInteractor
+        from .plottable_interactor import PointInteractor
         p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
         if p.markersize is not None:
@@ -1344,5 +1344,5 @@
         if id is None:
             id = name
-        from plottable_interactor import PointInteractor
+        from .plottable_interactor import PointInteractor
         p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
         p.curve(x, y, dy=dy, color=color, symbol=symbol, zorder=zorder,
@@ -1744,5 +1744,5 @@
             self.graph.delete(self.fit_result)
             if hasattr(self, 'plots'):
-                if 'fit' in self.plots.keys():
+                if 'fit' in self.plots:
                     del self.plots['fit']
         self.ly = None
Index: src/sas/sasgui/plottools/__init__.py
===================================================================
--- src/sas/sasgui/plottools/__init__.py	(revision efe730d4bba7a05c203957e4495eece8359fcd77)
+++ src/sas/sasgui/plottools/__init__.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,2 +1,2 @@
-from PlotPanel import PlotPanel
-from plottables import Data1D, Theory1D
+from .PlotPanel import PlotPanel
+from .plottables import Data1D, Theory1D
Index: src/sas/sasgui/plottools/binder.py
===================================================================
--- src/sas/sasgui/plottools/binder.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/plottools/binder.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -28,12 +28,14 @@
         return self.artist is not other.artist
 
-    def __nonzero__(self):
+    def __bool__(self):
         return self.artist is not None
+
+    __nonzero__ = __bool__
 
 
 class BindArtist(object):
     """
+    Track keyboard modifiers for events.
     """
-    # Track keyboard modifiers for events.
     # TODO: Move keyboard modifier support into the backend.  We cannot
     # TODO: properly support it from outside the windowing system since there
@@ -65,5 +67,5 @@
             ]
         except:
-            print("bypassing scroll_event: wrong matplotlib version")
+            logger.warn("bypassing scroll_event: wrong matplotlib version")
             self._connections = [
                 canvas.mpl_connect('motion_notify_event', self._onMotion),
@@ -74,4 +76,11 @@
             ]
 
+        # Turn off picker if it hasn't already been done
+        try:
+            canvas.mpl_disconnect(canvas.button_pick_id)
+            canvas.mpl_disconnect(canvas.scroll_pick_id)
+        except Exception as exc:
+            logger.error(exc)
+
         self._current = None
         self._actions = {}
@@ -87,5 +96,4 @@
         Use clearall() to reset all connections.
         """
-
         for h in artists:
             for a in self.events:
@@ -123,7 +131,8 @@
         """
         try:
-            for cid in self._connections: self.canvas.mpl_disconnect(cid)
-        except:
-            logger.error("Error disconnection canvas: %s" % sys.exc_value)
+            for cid in self._connections:
+                self.canvas.mpl_disconnect(cid)
+        except Exception as exc:
+            logger.error("Error disconnection canvas: %s" % exc)
         self._connections = []
 
@@ -172,23 +181,22 @@
         sure it applies.  E.g., the callback for 'press' might be:
             if event.button == 1 and event.shift: process Shift-click
-
-        TODO: Only receive events with the correct modifiers (e.g., S-click,
-        TODO:   or *-click for any modifiers).
-        TODO: Only receive button events for the correct button (e.g., click1
-        TODO:   release3, or dclick* for any button)
-        TODO: Support virtual artist, so that and artist can be flagged as
-        TODO:   having a tag list and receive the correct events
-        TODO: Support virtual events for binding to button-3 vs shift button-1
-        TODO:   without changing callback code
-        TODO: Attach multiple callbacks to the same event?
-        TODO: Clean up interaction with toolbar modes
-        TODO: push/pushclear/pop context so that binding changes for
-             the duration
-        TODO:   e.g., to support ? context sensitive help
-        """
+        """
+        #TODO: Only receive events with the correct modifiers (e.g., S-click,
+        #TODO:   or *-click for any modifiers).
+        #TODO: Only receive button events for the correct button (e.g., click1
+        #TODO:   release3, or dclick* for any button)
+        #TODO: Support virtual artist, so that and artist can be flagged as
+        #TODO:   having a tag list and receive the correct events
+        #TODO: Support virtual events for binding to button-3 vs shift button-1
+        #TODO:   without changing callback code
+        #TODO: Attach multiple callbacks to the same event?
+        #TODO: Clean up interaction with toolbar modes
+        #TODO: push/pushclear/pop context so that binding changes for the duration
+        #TODO:   e.g., to support ? context sensitive help
+
         # Check that the trigger is valid
         if trigger not in self._actions:
-            raise ValueError, "%s invalid --- valid triggers are %s"\
-                 % (trigger, ", ".join(self.events))
+            raise ValueError("%s invalid --- valid triggers are %s"
+                % (trigger, ", ".join(self.events)))
 
         # Register the trigger callback
@@ -205,5 +213,5 @@
         """
         if action not in self.events:
-            raise ValueError, "Trigger expects " + ", ".join(self.events)
+            raise ValueError("Trigger expects " + ", ".join(self.events))
 
         # Tag the event with modifiers
@@ -214,5 +222,5 @@
         setattr(ev, 'prop', {})
 
-        # Fallback scheme.  If the event does not return false, pass to parent.
+        # Fallback scheme. If the event does not return false, pass to parent.
         processed = False
         artist, prop = actor.artist, actor.prop
@@ -237,5 +245,5 @@
         """
         # TODO: sort by zorder of axes then by zorder within axes
-        self._artists.sort(cmp=lambda x, y: cmp(y.zorder, x.zorder))
+        self._artists.sort(key=lambda x: x.zorder, reverse=True)
         found = Selection()
         for artist in self._artists:
@@ -276,5 +284,5 @@
 
             transform = self._hasclick.artist.get_transform()
-            # x,y = event.xdata,event.ydata
+            #x,y = event.xdata,event.ydata
             x, y = event.x, event.y
             try:
@@ -286,11 +294,11 @@
                     # #transform ="CompositeGenericTransform" crash
                     pass
-            except:
-                # # CRUFT matplotlib-0.91 support
+            except: # CRUFT: matplotlib-0.91 support
                 # # exception for transform ="CompositeGenericTransform"
                 # # crashes also here
                 x, y = transform.inverse_xy_tup((x, y))
 
-            # event.xdata, event.ydata = x, y
+            #TODO: why compute (x,y) if it isn't being used?
+            #event.xdata, event.ydata = x, y
             self.trigger(self._hasclick, 'drag', event)
         else:
Index: src/sas/sasgui/plottools/canvas.py
===================================================================
--- src/sas/sasgui/plottools/canvas.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/plottools/canvas.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -97,6 +97,6 @@
         try:
             dc.DrawBitmap(self.canvas.bitmap, (0, 0))
-        except:
-            logger.error(sys.exc_value)
+        except Exception as exc:
+            logger.error(exc)
 
     # restore original figure  resolution
@@ -208,6 +208,6 @@
             try:
                 fig.draw(self)
-            except ValueError:
-                logger.error(sys.exc_value)
+            except ValueError as exc:
+                logger.error(exc)
         else:
             self._isRendered = False
Index: src/sas/sasgui/plottools/convert_units.py
===================================================================
--- src/sas/sasgui/plottools/convert_units.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sasgui/plottools/convert_units.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -13,8 +13,8 @@
     """
     if power != 0:
-        if string.find(unit, "^") != -1:  # if the unit contains a powerer ^
-            toks = re.split("\^", unit)
-            if string.find(toks[0], "/") != -1 or \
-                string.find(toks[0], "-") != -1:
+        if unit.find("^") != -1:  # if the unit contains a powerer ^
+            toks = re.split(r"\^", unit)
+            if toks[0].find("/") != -1 or \
+                toks[0].find("-") != -1:
                 if power == 1:
                     unit = unit
@@ -22,11 +22,11 @@
                     unit = "(" + unit + ")" + "^{" + str(power) + "}"
             else:
-                if string.find(toks[1], "{") != -1:  # if found a {
+                if toks[1].find("{") != -1:  # if found a {
                     find_power_toks = re.split("{", toks[1])
-                    if string.find(find_power_toks[1], "}") != -1:  # found }
+                    if find_power_toks[1].find("}") != -1:  # found }
                         unit_toks = re.split("}", find_power_toks[1])
-                        if string.find(unit_toks[0], ".") != -1:
+                        if unit_toks[0].find(".") != -1:
                             powerer = float(unit_toks[0]) * power
-                        elif string.find(unit_toks[0], "/") != -1:
+                        elif unit_toks[0].find("/") != -1:
                             power_toks = re.split("/", unit_toks[0])
                             powerer = power * int(power_toks[0])\
@@ -44,10 +44,10 @@
                             unit = toks[0] + "^{" + str(powerer) + "}"
                 else:
-                    raise ValueError, "missing } in unit expression"
+                    raise ValueError("missing } in unit expression")
         else:  # no powerer
             if  power != 1:
                 unit = "(" + unit + ")" + "^{" + str(power) + "}"
     else:
-        raise ValueError, "empty unit ,enter a powerer different from zero"
+        raise ValueError("empty unit, enter a power different from zero")
     return unit
 
Index: src/sas/sasgui/plottools/fitDialog.py
===================================================================
--- src/sas/sasgui/plottools/fitDialog.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/plottools/fitDialog.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -1,9 +1,11 @@
+import sys
+import math
+
 import wx
-from plottables import Theory1D
-import math
 import numpy as np
-import fittings
-import transform
-import sys
+
+from . import fittings
+from . import transform
+from .plottables import Theory1D
 
 # Linear fit panel size
@@ -86,5 +88,5 @@
         self.layout()
         # Receives the type of model for the fitting
-        from LineModel import LineModel
+        from .LineModel import LineModel
         self.model = LineModel()
         # Display the fittings values
@@ -671,5 +673,5 @@
                 return x
             else:
-                raise ValueError, "cannot compute log of a negative number"
+                raise ValueError("cannot compute log of a negative number")
 
     def floatInvTransform(self, x):
@@ -734,5 +736,5 @@
         except:
             msg = "LinearFit.set_fit_region: fit range must be floats"
-            raise ValueError, msg
+            raise ValueError(msg)
         self.xminFit.SetValue(format_number(xmin))
         self.xmaxFit.SetValue(format_number(xmax))
Index: src/sas/sasgui/plottools/fittings.py
===================================================================
--- src/sas/sasgui/plottools/fittings.py	(revision a1b8fee73528559155793dd74cda07d0c32e9d72)
+++ src/sas/sasgui/plottools/fittings.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -101,5 +101,5 @@
     # Testing implementation
     # Fit a Line model
-    from LineModel import LineModel
+    from .LineModel import LineModel
     line = LineModel()
     cstA = Parameter(line, 'A', event.cstA)
Index: src/sas/sasgui/plottools/plottable_interactor.py
===================================================================
--- src/sas/sasgui/plottools/plottable_interactor.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/plottools/plottable_interactor.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -4,5 +4,5 @@
 from __future__ import print_function
 
-from BaseInteractor import _BaseInteractor
+from .BaseInteractor import _BaseInteractor
 
 
Index: src/sas/sasgui/plottools/plottables.py
===================================================================
--- src/sas/sasgui/plottools/plottables.py	(revision 2469df7fc9ffa047115c5bffe589ed307b12c363)
+++ src/sas/sasgui/plottools/plottables.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -389,5 +389,5 @@
 
         """
-        raise NotImplemented, "Not a valid transform"
+        raise NotImplemented("Not a valid transform")
 
     # Related issues
@@ -689,15 +689,15 @@
                 msg = "Plottable.View: Given x and dx are not"
                 msg += " of the same length"
-                raise ValueError, msg
+                raise ValueError(msg)
             # Check length of y array
             if not len(y) == len(x):
                 msg = "Plottable.View: Given y "
                 msg += "and x are not of the same length"
-                raise ValueError, msg
+                raise ValueError(msg)
 
             if dy is not None and not len(dy) == 0 and not len(y) == len(dy):
                 msg = "Plottable.View: Given y and dy are not of the same "
                 msg += "length: len(y)=%s, len(dy)=%s" % (len(y), len(dy))
-                raise ValueError, msg
+                raise ValueError(msg)
             self.x = []
             self.y = []
@@ -734,13 +734,13 @@
                 msg = "Plottable.View: transformed x "
                 msg += "and y are not of the same length"
-                raise ValueError, msg
+                raise ValueError(msg)
             if has_err_x and not (len(self.x) == len(self.dx)):
                 msg = "Plottable.View: transformed x and dx"
                 msg += " are not of the same length"
-                raise ValueError, msg
+                raise ValueError(msg)
             if has_err_y and not (len(self.y) == len(self.dy)):
                 msg = "Plottable.View: transformed y"
                 msg += " and dy are not of the same length"
-                raise ValueError, msg
+                raise ValueError(msg)
             # Check that negative values are not plot on x and y axis for
             # log10 transformation
@@ -812,7 +812,7 @@
                         tempy.append(self.y[i])
                         tempdy.append(self.dy[i])
-                except:
+                except Exception as exc:
                     logger.error("check_data_logX: skipping point x %g", self.x[i])
-                    logger.error(sys.exc_value)
+                    logger.error(exc)
             self.x = tempx
             self.y = tempy
@@ -842,7 +842,7 @@
                         tempy.append(self.y[i])
                         tempdy.append(self.dy[i])
-                except:
+                except Exception as exc:
                     logger.error("check_data_logY: skipping point %g", self.y[i])
-                    logger.error(sys.exc_value)
+                    logger.error(exc)
 
             self.x = tempx
@@ -996,5 +996,5 @@
             self.zmax = zmax
         else:
-            raise "zmin is greater or equal to zmax "
+            raise("zmin is greater or equal to zmax ")
 
     def render(self, plot, **kw):
@@ -1108,5 +1108,5 @@
         Plottable.__init__(self)
         msg = "Theory1D is no longer supported, please use Data1D and change symbol.\n"
-        raise DeprecationWarning, msg
+        raise DeprecationWarning(msg)
 
 class Fit1D(Plottable):
Index: src/sas/sasgui/plottools/toolbar.py
===================================================================
--- src/sas/sasgui/plottools/toolbar.py	(revision 20fa5fe0c3e314a850432d5e463d03ceed3e453a)
+++ src/sas/sasgui/plottools/toolbar.py	(revision cb64d86aa05427be27c71cc415727ce4d7fdce69)
@@ -39,5 +39,5 @@
     try: save_figure = NavigationToolbar2WxAgg.save
     except AttributeError: pass
-    
+
     def _init_toolbar(self):
         self._parent = self.canvas.GetParent()
@@ -65,9 +65,17 @@
                            'Forward', 'Forward navigation view')
         # todo: get new bitmap
-        self.AddCheckTool(self._NTB2_PAN, _load_bitmap('move.png'),
-                          shortHelp='Pan',
-                          longHelp='Pan with left, zoom with right')
-        self.AddCheckTool(self._NTB2_ZOOM, _load_bitmap('zoom_to_rect.png'),
-                          shortHelp='Zoom', longHelp='Zoom to rectangle')
+        # todo: update with code from matplotlib/backends/backend_wx.py
+        if 'phoenix' in wx.PlatformInfo: # wx phoenix >= 4.0.0b2
+            self.AddCheckTool(self._NTB2_PAN, "Pan", _load_bitmap('move.png'),
+                              shortHelp='Pan',
+                              longHelp='Pan with left, zoom with right')
+            self.AddCheckTool(self._NTB2_ZOOM, "Zoom", _load_bitmap('zoom_to_rect.png'),
+                              shortHelp='Zoom', longHelp='Zoom to rectangle')
+        else:
+            self.AddCheckTool(self._NTB2_PAN, _load_bitmap('move.png'),
+                              shortHelp='Pan',
+                              longHelp='Pan with left, zoom with right')
+            self.AddCheckTool(self._NTB2_ZOOM, _load_bitmap('zoom_to_rect.png'),
+                              shortHelp='Zoom', longHelp='Zoom to rectangle')
 
         self.AddSeparator()
Index: src/sas/sasgui/plottools/transform.py
===================================================================
--- src/sas/sasgui/plottools/transform.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/plottools/transform.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -24,5 +24,5 @@
     """
     if not x > 0:
-        raise ValueError, "Transformation only accepts positive values."
+        raise ValueError("Transformation only accepts positive values.")
     else:
         return x
@@ -50,5 +50,5 @@
     """
     if not x >= 0:
-        raise ValueError, "square root of a negative value "
+        raise ValueError("square root of a negative value ")
     else:
         return math.sqrt(x)
@@ -76,5 +76,5 @@
     """
     if not x >= 0:
-        raise ValueError, "double square root of a negative value "
+        raise ValueError("double square root of a negative value ")
     else:
         return math.sqrt(math.sqrt(x))
@@ -90,5 +90,5 @@
     """
     if not x > 0:
-        raise ValueError, "Log(x)of a negative value "
+        raise ValueError("Log(x)of a negative value ")
     else:
         return math.log(x)
@@ -100,5 +100,5 @@
         return 1 / x
     else:
-        raise ValueError, "cannot divide by zero"
+        raise ValueError("cannot divide by zero")
 
 
@@ -109,5 +109,5 @@
         return 1 / math.sqrt(y)
     else:
-        raise ValueError, "transform.toOneOverSqrtX: cannot be computed"
+        raise ValueError("transform.toOneOverSqrtX: cannot be computed")
 
 
@@ -118,5 +118,5 @@
         return math.log(y * (x ** 2))
     else:
-        raise ValueError, "transform.toLogYX2: cannot be computed"
+        raise ValueError("transform.toLogYX2: cannot be computed")
 
 
@@ -127,5 +127,5 @@
         return math.log(math.pow(x, 4) * y)
     else:
-        raise ValueError, "transform.toLogYX4: input error"
+        raise ValueError("transform.toLogYX4: input error")
 
 
@@ -149,5 +149,5 @@
     """
     if not (x * y) > 0:
-        raise ValueError, "Log(X*Y)of a negative value "
+        raise ValueError("Log(X*Y)of a negative value ")
     else:
         return math.log(x * y)
@@ -211,5 +211,5 @@
     else:
         msg = "transform.errFromX2: can't compute error of negative x"
-        raise ValueError, msg
+        raise ValueError(msg)
 
 
@@ -245,5 +245,5 @@
     else:
         msg = "transform.errFromX4: can't compute error of negative x"
-        raise ValueError, msg
+        raise ValueError(msg)
 
 
@@ -264,9 +264,9 @@
         msg = "Transformation does not accept"
         msg += " point that are consistent with zero."
-        raise ValueError, msg
+        raise ValueError(msg)
     if x != 0:
         dx = dx / (x * math.log(10))
     else:
-        raise ValueError, "errToLogX: divide by zero"
+        raise ValueError("errToLogX: divide by zero")
     return dx
 
@@ -287,5 +287,5 @@
         dx = dx / x
     else:
-        raise ValueError, "errToLogX: divide by zero"
+        raise ValueError("errToLogX: divide by zero")
     return dx
 
@@ -312,5 +312,5 @@
         msg = "Transformation does not accept point "
         msg += " that are consistent with zero."
-        raise ValueError, msg
+        raise ValueError(msg)
     if x != 0 and y != 0:
         if dx is None:
@@ -320,5 +320,5 @@
         err = (dx / x) ** 2 + (dy / y) ** 2
     else:
-        raise ValueError, "cannot compute this error"
+        raise ValueError("cannot compute this error")
 
     return math.sqrt(math.fabs(err))
@@ -335,5 +335,5 @@
         msg = "Transformation does not accept point"
         msg += " that are consistent with zero."
-        raise ValueError, msg
+        raise ValueError(msg)
     if x > 0 and y > 0:
         if dx is None:
@@ -343,5 +343,5 @@
         err = (2.0 * dx / x) ** 2 + (dy / y) ** 2
     else:
-        raise ValueError, "cannot compute this error"
+        raise ValueError("cannot compute this error")
     return math.sqrt(math.fabs(err))
 
@@ -357,5 +357,5 @@
         err = dx / x ** 2
     else:
-        raise ValueError, "Cannot compute this error"
+        raise ValueError("Cannot compute this error")
     return math.fabs(err)
 
@@ -371,5 +371,5 @@
         err = -1 / 2 * math.pow(x, -3.0 / 2.0) * dx
     else:
-        raise ValueError, "Cannot compute this error"
+        raise ValueError("Cannot compute this error")
     return math.fabs(err)
 
@@ -387,5 +387,5 @@
         msg = "Transformation does not accept point "
         msg += " that are consistent with zero."
-        raise ValueError, msg
+        raise ValueError(msg)
     if dx is None:
         dx = 0
Index: src/sas/sasview/sasview.py
===================================================================
--- src/sas/sasview/sasview.py	(revision 1270e3c4e7a4343f5a384a6bf3799b972b00c1d2)
+++ src/sas/sasview/sasview.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -42,5 +42,7 @@
 
         from sas.sasgui.guiframe.gui_manager import SasViewApp
+        from sas.sasgui.guiframe.gui_style import GUIFRAME_ICON
         self.gui = SasViewApp(0)
+        GUIFRAME_ICON.load_icons()
         if sys.platform == "darwin":
             self.check_sasmodels_compiler()
@@ -228,6 +230,11 @@
         logger.error("Wx version: error reading version")
 
-    from . import wxcruft
-    wxcruft.call_later_fix()
+    # TODO: Do we need the call later fix for wx 3? Or is it wx < 3 only?
+    if "phoenix" in wx.PlatformInfo:
+        #wx.NewId = wx.Window.NewControlId
+        pass
+    else:
+        from . import wxcruft
+        wxcruft.call_later_fix()
     #wxcruft.trace_new_id()
     #Always use private .matplotlib setup to avoid conflicts with other
Index: src/sas/sasview/welcome_panel.py
===================================================================
--- src/sas/sasview/welcome_panel.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
+++ src/sas/sasview/welcome_panel.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -2,10 +2,14 @@
     Welcome page
 """
-import wx
-import wx.aui
-import wx.lib.hyperlink
 import os.path
 import os, sys
 
+import wx
+import wx.aui
+try:
+    # CRUFT: wx 4 moved hyperlink to wx.lib.agw
+    from wx.lib import hyperlink
+except ImportError:
+    from wx.lib.agw import hyperlink
 from wx.lib.scrolledpanel import ScrolledPanel
 
@@ -115,5 +119,5 @@
         send_ticket += "help@sasview.org"
         self.hyperlink_paper = \
-            wx.lib.hyperlink.HyperLinkCtrl(self, -1,
+            hyperlink.HyperLinkCtrl(self, -1,
                                            send_ticket, URL=config._license)
 
Index: src/sas/sasview/wxcruft.py
===================================================================
--- src/sas/sasview/wxcruft.py	(revision efe730d4bba7a05c203957e4495eece8359fcd77)
+++ src/sas/sasview/wxcruft.py	(revision 5251ec6db8aae214addfa0a5a1da16820994958b)
@@ -4,5 +4,4 @@
 import wx
 from wx import Timer
-import wx._misc
 
 def call_later_fix():
@@ -16,4 +15,5 @@
 
 def NewId():
+    import wx._misc
     id = wx._misc.NewId()
     path, line, function = _get_caller()
