# Copyright 2014 M. A. Zentile, J. Keaveney, L. Weller, D. Whiting,
# C. S. Adams and I. G. Hughes.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""

ElecSus GUI

v3.0.7 (2019-10-22)
	-- Large speed improvement for electric field calculations.
	-- Fixed bug to allow data saving in python 3.
	-- Bug with relative paths fixed.
	-- Reduced number of initial points in RR fitting routine.
	-- Fixed compatibility issue with matplotlib versions > 3.
	-- Fixed the fitting test modules

v3.0.6 (2018-04-12)
	--	Bug in the data processing module (libs/data_proc.py) fixed
	-- Minor change to color cycler to support new version of matplotlib (v2.2)
	
v3.0.5 (2018-02-27)
	--	 Bug fix with some menu items not working properly
	
v3.0.4 (2018-02-19)
	-- Support for python 3.x added (maintains compatibility with python 2.7)
	
v3.0.3 (2017-12-06)
	-- Minor fixes to GUI for file input/output not working properly and an error that stopped fitting working

v3.0.2 (2017-11-14)
	-- Minor fix: changed a wx.OPEN to wx.FD_OPEN that affected newer versions of wx FileDialogs not opening properly

v3.0.1 (2017-09-25)
	-- Fix missing square-root in solve_dielectric.py
	-- Implement proper fine-structure constants properly for all alkalis
	
v3.0.0 (2017-08-18)
	-- MAJOR OVERHAUL to add many new program features and streamline a lot of others. This, unfortunately but necessarily, makes version 3 incompatible with previous versions. The changes are detailed below. The phyics are discussed in the new paper, a pre-print can be found at https://arxiv.org/abs/1708.05305
	
	-- First major change is to allow direct propagation of electric fields through the medium, which can enable simulating, for example: magnetic field gradients; polarising optical elements; birefringence or other optical imperfections.
	The consequence of this change is that polarisation is now defined in terms of the cartesian components of electric field and the phase difference between them, rather than the somewhat opaque notation in previous versions.
	
	-- Second major change is to allow arbitrary angles between the magnetic field axis and the light propagation axis (in 3D). This allows for a much broader set of physics to be investigated, since it removes a large constraint of previous versions. At zero degrees between the two fields we recover the standard Faraday geometry of previous ElecSus versions. At 90 degrees, we have the Voigt geometry (which also has analytic solutions, so is fast to run). We can also calculate for any angle, but BE WARNED, this is very computationally intensive, since the solutions for propagation are no longer analytic and must be calculated for each detuning point separately (with a corresponding slow-down in performance, typically of around a factor of 1000!).
	
	-- Finally, the fitting routines have been completely rewritten to use parameter dictionaries. We now utilise the lmfit module (https://lmfit.github.io/lmfit-py/). 
		Performance is broadly similar (since lmfit also runs over the top of scipy.optimize modules), but there are many advantages of this model: 
			Firstly, all parameters can now be selected to vary during a fit or be held constant, and bounds for these parameters can be given to prevent unphysical values from being returned. 
			In addition, the differential_evolution solver is now availablle, which is very efficient at finding the global minimum for multi-parameter fits 
			(we leave in random_restat and simulated_annealing for now, though these might disappear in future versions as it appears differential_evolution is better in all tested cases so far...).
	
	-- Other changes include better code documentation and minor bug fixes in many places.

	
v2.2.0 (2016-02-10)
	-- GUI version number and ElecSus version number are now the same
	-- Since Enthought Canopy now ships with wxPython version 3.0, GUI has been
		updated to work with this version of wxPython. All previous functionality should 
		remain the same, but a few things have changed:
			-- Theory/Fit plot selections are no longer Transient Popups - they are now Dialog Boxes
			-- Default plot scaling may look a bit odd when viewing on small resolution monitors -
				not sure what the real fix for this is, but as a workaround, resizing the GUI window
				should fix this
	-- Added ability to use experimental detuning axis on theory curve, 
		for direct comparison when outputting data (Menu > Edit > Use Experimental Detuning Axis)
	-- Added ability to turn off automatic scaling of axes (Menu > View > Autoscale)
	-- Fixed an issue where save files would not save correctly after performing a fit
	-- Minor fix for an issue where starting from the python interpreter would not exit properly on clicking the 'X'
	-- Corrected some incorrect tooltips
	-- Added show_versions() method, which shows the currently used version numbers of 
		modules important to running this program
		
v1.0.1 (2015-10-23)
	-- minor bug fix where the plot selection popups would not display the Phi plots
v1.0.0 (2015-09-03)


A GUI based on wxpython for ElecSus, intended to augment/replace the 
runcard method of calling ElecSus.

See https://arxiv.org/abs/1708.05305 for the publication and more details on the physics of ElecSus.

Requirements:
	python, matplotlib, numpy, scipy
	versions unknown, 
	
	tested on:
	Windows 8.1, Windows 10
		python 2.7 - 64-bit
		python 3.6 - 64-bit
		wxpython 2.8, wxpython 3.0, wxpython 4.0
		matplotlib 1.4.3, 1.5.1
		numpy 1.9.2
		scipy 0.15.1, 0.16.1
		lmfit 0.9.5
	
	Currently installed version numbers can be shown by running the show_versions() method in this module
	
	Requires
	--------
	
	wxpython version >2.8
		In newer versions, there are bugs with either matplotlib or wxpython that cause figures 
		not to fill the entire panel - a temporary solution is to resize the panel.
		
	
LICENSE info: APACHE version 2

Mark Zentile, James Keaveney and co-authors
2017/8/9
"""
# py 2.7 compatibility
from __future__ import (division, print_function, absolute_import)

__version__ = '3.0.7'

#!/usr/bin/env python
import matplotlib
matplotlib.use('WxAgg')
import pylab
pylab.ioff() # set interactive mode off

import os
import sys
import csv
import time
import pickle as pickle

import numpy as np
import scipy as sp
import matplotlib.pylab as plt
import matplotlib as mpl

import multiprocessing as mproc
import psutil ## future - to use lower priority for fitting

# use relative file paths
elecsus_dir = os.path.dirname(__file__)

# most important import - if wx doesn't work, the gui doesn't work!
try:
	import wx
except ImportError:
	print("wxPython cannot be imported")
	print("wxPython >=2.8 needs to be installed for this program to work! \n\
	It is not currently possible to install this automatically through pip/easy_install.\n")
	if os.name == 'posix':
		print("For Ubuntu/Debian, wxPython is not supported in Enthought Canopy.\n\
		Instead, use the system python distribution (/usr/bin/python) and install through apt:\n\n\
		>   (sudo) apt-get install python-wxgtk2.8 python-wxtools wx2.8-i18n libwxgtk2.8-dev libgtk2.0-dev")
	else:
		print("For Windows, recommended install is using Enthought Canopy")
	raise ImportError

#EXPERIMENTAL - advanced user interface
if 'phoenix' in wx.PlatformInfo:
    import wx.lib.agw.aui as aui
else:
    import wx.aui as aui

# Import lots of other wx stuff
wx.Log.SetLogLevel(0) # Ignore warnings
import wx.lib.scrolledpanel as scrolled
from wx.lib.agw.floatspin import FloatSpin, EVT_FLOATSPIN
from wx.lib.popupctl import PopupControl
# Matplotlib/wx integration
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg, NavigationToolbar2WxAgg as Toolbar
# define a new event type which will be triggered when fitting is completed
import wx.lib.newevent
FitCompleteEvent, EVT_FIT_COMPLETE = wx.lib.newevent.NewEvent()

## fits run in a separate thread so the GUI isn't blocked
import threading

# import elecsus modules
from elecsus.elecsus_methods import calculate, fit_data
from elecsus.libs import NOTICE
from elecsus.libs import data_proc
from elecsus.libs.durhamcolours import *
from elecsus.libs.durhamcolours import cols as durhamcols
from elecsus.libs import polarisation_animation_mpl as pol_ani

# fitting
try:
	import lmfit as lm
except:
	print('WARNING - LMfit could not be imported -fitting experimental data will not work!')

#replace default matplotlib text and color sequence with durham colours
from matplotlib import rc
#replace default matplotlib text and color sequence with durham colours
plt.rc('font',**{'family':'Serif','serif':['Times New Roman']})
params={'axes.labelsize':14,'xtick.labelsize':13,'ytick.labelsize':13,'legend.fontsize': 12,'mathtext.fontset':'cm','mathtext.rm':'serif'}
plt.rcParams.update(params)
rc('text', usetex=False)
rc('font',**{'family':'serif', 'size':14})
rc('lines', linewidth=2)
from cycler import cycler
rc('axes', prop_cycle=(cycler('color', durhamcols)))

# preamble.py includes tooltip text, default values, labels...
from libs.preamble import *

def show_versions():
	""" Shows installed version numbers """
	print('Packages required for GUI: (this displays currently installed version numbers)')
	print('\tElecSus: ', __version__)
	print('\tWxPython: ', wx.__version__)
	print('\tNumpy: ', np.__version__)
	print('\tMatplotlib: ', mpl.__version__)
	print('Required for fitting (in addition to above):')
	print('\tScipy: ', sp.__version__)
	print('\tPSUtil: ', psutil.__version__)
	print('\tLMfit: ', lm.__version__)
		
class PlotSelectionPopUp(wx.PopupTransientWindow):
	""" Popup box to handle which plots are displayed. """
	def __init__(self,parent,style,mainwin,plottype):
		wx.PopupTransientWindow.__init__(self,parent,style)

		self.mainwin = mainwin
		self.plottype = plottype

		win = wx.Panel(self) #,wx.ID_ANY,pos=(0,0),size=(180,200),style=0)

		self.selection = wx.CheckListBox(win, wx.ID_ANY, choices = OutputPlotTypes, size=(150,-1))#,pos=(0,0))
		#self.win.Bind(wx.EVT_CHECKLISTBOX, self.OnTicked, self.selection)
		self.selection.Bind(wx.EVT_CHECKLISTBOX, self.OnTicked)
		
		if plottype == 'Theory':
			display_curves = self.mainwin.display_theory_curves
		else:
			display_curves = self.mainwin.display_expt_curves

		checked_items = []
		for i in range(len(display_curves)):
			if display_curves[i]:
				checked_items.append(i)
		self.selection.SetChecked(checked_items)

		self.SetSize(self.selection.GetSize()+(10,10))
		self.SetMinSize(self.selection.GetSize()+(10,10))
		
		popup_sizer = wx.BoxSizer(wx.VERTICAL)
		popup_sizer.Add(self.selection,0,wx.ALL, 7)
		
		win.SetSizer(popup_sizer)
		popup_sizer.Fit(win)
		self.Layout()

	def OnDismiss(self):
		""" 
		Action to perform when the popup loses focus and is closed. 
		
		Gets the tick box values, updates the main plot and changes 
		the menu items to match the popup box
		"""
		# re=plot the figure
		#self.OnTicked(1)

		#self.mainwin.Refresh()

		self.Dismiss()

	def OnTicked(self,event):
		""" 
		Action to perform when tick boxes are ticked 
		
		Gets the tick box values, updates the main plot and changes 
		the menu items to match the popup box
		"""
		#print 'Ticked Event'
		
		if self.plottype == 'Theory':
			items = self.selection.GetChecked()
			self.mainwin.display_theory_curves = [False]*9
			for menuitem in self.mainwin.showTplotsSubMenu.GetMenuItems():
				menuitem.Check(False)
			for item in items:
				self.mainwin.display_theory_curves[item] = True
				self.mainwin.showTplotsSubMenu.GetMenuItems()[item].Check(True)
			print(self.mainwin.display_theory_curves)
		elif self.plottype == 'Fit':
			items = self.selection.GetChecked()
			self.mainwin.display_expt_curves = [False]*9
			for menuitem in self.mainwin.showEplotsSubMenu.GetMenuItems():
				menuitem.Check(False)
			for item in items:
				self.mainwin.display_expt_curves[item] = True
				self.mainwin.showEplotsSubMenu.GetMenuItems()[item].Check(True)
			print(self.mainwin.display_expt_curves)

		self.mainwin.OnCreateAxes(self.mainwin.figs[0],self.mainwin.canvases[0])

class PlotSelectionDialog(wx.Dialog):
	""" Popup box to handle which plots are displayed. """
	def __init__(self,parent,mainwin,title,plottype,pos):
		wx.Dialog.__init__(self,parent,wx.ID_ANY,title,size=(400,600),pos=pos)

		self.mainwin = mainwin
		self.plottype = plottype

		win = wx.Panel(self) #,wx.ID_ANY,pos=(0,0),size=(180,200),style=0)

		self.selection = wx.CheckListBox(win, wx.ID_ANY, choices = OutputPlotTypes, size=(120,-1))#,pos=(0,0))
		#self.win.Bind(wx.EVT_CHECKLISTBOX, self.OnTicked, self.selection)
		self.selection.Bind(wx.EVT_CHECKLISTBOX, self.OnTicked)
		
		if plottype == 'Theory':
			display_curves = self.mainwin.display_theory_curves
		else:
			display_curves = self.mainwin.display_expt_curves

		checked_items = []
		for i in range(len(display_curves)):
			if display_curves[i]:
				checked_items.append(i)

		self.selection.SetChecked(checked_items)
		#self.okbtn = wx.Button(self.win,wx.ID_OK,size=(120,BtnSize))
		#self.Bind(wx.EVT_BUTTON, self.OnOK, self.okbtn)

		self.SetSize(self.selection.GetSize()+(50,50))
		self.SetMinSize(self.selection.GetSize()+(50,50))
		
		popup_sizer = wx.BoxSizer(wx.VERTICAL)
		popup_sizer.Add(self.selection,0,wx.EXPAND|wx.ALL, 7)
		#popup_sizer.Add((-1,5),1,wx.EXPAND)
		#popup_sizer.Add(self.okbtn,0,wx.EXPAND)

		#sz = popup_sizer.GetBestSize()
		#self.win.SetSize((sz.width+20, sz.height+20))
		
		self.SetSizer(popup_sizer)
		wx.CallAfter(self.Refresh)
		
	def OnTicked(self,event):
		""" 
		Action to perform when tick boxes are ticked 
		
		Gets the tick box values, updates the main plot and changes 
		the menu items to match the popup box
		"""
		print('Ticked Event')
		
		if self.plottype == 'Theory':
			items = self.selection.GetChecked()
			self.mainwin.display_theory_curves = [False]*9
			#for menuitem in self.mainwin.showTplotsSubMenu.GetMenuItems():
			#	menuitem.Check(False)
			for item in items:
				self.mainwin.display_theory_curves[item] = True
				#self.mainwin.showTplotsSubMenu.GetMenuItems()[item].Check(True)
			print(self.mainwin.display_theory_curves)
		elif self.plottype == 'Fit':
			items = self.selection.GetChecked()
			self.mainwin.display_expt_curves = [False]*9
			#for menuitem in self.mainwin.showEplotsSubMenu.GetMenuItems():
			#	menuitem.Check(False)
			for item in items:
				self.mainwin.display_expt_curves[item] = True
				#self.mainwin.showEplotsSubMenu.GetMenuItems()[item].Check(True)
			print(self.mainwin.display_expt_curves)

		self.mainwin.OnCreateAxes(self.mainwin.figs[0],self.mainwin.canvases[0])
		
class FittingThread(threading.Thread):
	""" 
	Fitting takes quite a bit of system resources, so we run the fit in another thread (and the processes spawned are low priority), 
	leaving the GUI active in the meantime. When the fitting completes, this thread triggers an event in the 
	GUI that then completes the rest of the fitting routine - i.e. updates the plot, writes text ... etc
	
	child class of the main threading.Thread class
	"""
	def __init__(self,parent):
		threading.Thread.__init__(self)
		
		self.mainwin = parent
		#self.start() ## this calls the run() method - called from the GUI
	
	def run(self):
		""" Run fitting thread - called by <>.start() method """
		
		# mainwin is the top-level window (the ElecSus_GUI_Frame instance)
		mainwin = self.mainwin
		
		
		## crop x and y arrays sent to fit_data() method, based on fit_bounds (if it's not [None, None])
		if None in mainwin.detuning_fit_bounds:
			# use full arrays if fit bounds not specified (default)
			x_array, y_array = mainwin.x_fit_array, mainwin.y_fit_array
		else:
			## crop data to specified range
			
			#create aliases for ease
			fb = mainwin.detuning_fit_bounds
			xfa = mainwin.x_fit_array
			yfa = mainwin.y_fit_array
			#
			y_array = yfa[(xfa>fb[0]) & (xfa<fb[1])]
			x_array = xfa[(xfa>fb[0]) & (xfa<fb[1])]
			
			fb, xfa, yfa = None, None, None
		
		print('Fitting data in the detuning range (GHz):  ',x_array[0], ' to ',x_array[-1])
		mainwin.FitInformation.write('Fitting in the detuning range (GHz):  '+str(x_array[0])+' to '+str(x_array[-1]))
		mainwin.FitInformation.write('\n\n')

		# Check fit algorithms are ok
		if mainwin.fit_algorithm == 'Marquardt-Levenberg': fa = 'ML'
		elif mainwin.fit_algorithm == 'Random-Restart': fa = 'RR'
		elif mainwin.fit_algorithm == 'Simulated Annealing': fa = 'SA'
		elif mainwin.fit_algorithm == 'Differential Evolution': fa = 'DE'
		else:
			print("!! Fitting error - fit algorithm not defined")
			raise
		
		# rename data_type if S0:
		if mainwin.fit_datatype == 'Transmission (S0)':
			dt = 'S0'
		else:
			dt = mainwin.fit_datatype
			
		mainwin.opt_params, mainwin.rms, mainwin.fit_result = fit_data((x_array*1e3,y_array),\
								mainwin.params_dict, \
								mainwin.params_dict_bools, p_dict_bounds = mainwin.params_dict_bounds,\
								data_type = dt, fit_algorithm = fa) #,\
								#**mainwin.advanced_fitoptions)
		
		#post an event to the main panel to run the OnFitComplete() method
		evt = FitCompleteEvent()
		wx.PostEvent(mainwin, evt)
		## alternately, could use wx.CallAfter here ...		

class ProgressThread(threading.Thread):
	""" update the progress bar continually to show user something is happening ..."""
	def __init__(self,parent):
		""" Create the thread """
		threading.Thread.__init__(self)
		self.mainwin = parent
		self.pb = parent.fitting_dlg #.pb
		
	def run(self):
		""" Action to run when .start() method is called """
		while self.mainwin.already_fitting:
			for i in range(100):
				if not self.mainwin.already_fitting: break
				#print i,' ',
				#wx.CallAfter(self.pb.SetValue,i)
				wx.CallAfter(self.pb.Update,i)
				time.sleep(0.1)
		print('Quitting progress bar update')
	
class OptionsPanel(scrolled.ScrolledPanel):
	def __init__(self, parent, mainwin, size, paneltype):
		""" 
		Panel which holds most of the control elements in this program. 
		Most of the options are the same for theory and fitting, but there are some differences.
		Hence, the same panel class with an argument 'paneltype' which will set what is displayed.
		
		This ScrolledPanel class will automatically add horizontal and vertical scroll bars when required, and dynamically turns them on and off as window is resized. Neat!
		
		##
		Note - this class has got a bit messy in it's old age, and could do with a rewrite to make it more clear!
		Another one for the To-Do list...
		##
		
		# 'mainwin' is the main application frame so we can bind buttons to actions in the main frame
		# [ it's the parent (frame) of the parent (tab notebook) of the options panel (self) ]
		
		"""
		
		self.mainwin = mainwin
		
		scrolled.ScrolledPanel.__init__(self, parent)
		
		self.paneltype = paneltype
		
		self.all_floatspin_inputs = []
		
		# Fitting only:
		if self.paneltype == 'Fit':
			# Import data from csv
			
			# Booleans (check boxes) for fitting
			self.main_paramlist_bools = \
				[ wx.CheckBox(self, label='') for parameter in main_parameterlist ]
			self.magnet_paramlist_bools = \
				[ wx.CheckBox(self, label='') for parameter in magnet_parameterlist ]
				
			for box in self.main_paramlist_bools + self.magnet_paramlist_bools:
				self.Bind(wx.EVT_CHECKBOX,self.OnFloatTicked, box)
				
			self.main_paramlist_usebounds = \
				[ wx.CheckBox(self, label='') for parameter in main_parameterlist ]
			self.magnet_paramlist_usebounds = \
				[ wx.CheckBox(self, label='') for parameter in magnet_parameterlist ]
			for boundsbox in self.main_paramlist_usebounds + self.magnet_paramlist_usebounds:
				self.Bind(wx.EVT_CHECKBOX,self.OnBoundsTicked, boundsbox)
						
			# Fitting algorithm selection
			# ties in with menu items
			self.fit_types = [wx.RadioButton(self,label="Marquardt-Levenberg", style=wx.RB_GROUP), \
						 wx.RadioButton(self,label="Random-Restart"),\
						 wx.RadioButton(self,label="Simulated Annealing"),
						 wx.RadioButton(self,label="Differential Evolution") ]
			
			for fit_type, tt in zip(self.fit_types,fit_algorithm_tooltips):
				self.Bind(wx.EVT_RADIOBUTTON, mainwin.OnFitTypeChangePanel, fit_type)
				fit_type.SetToolTip(wx.ToolTip(tt))
			
			# Run Fit Button
			#RunFitButton = wx.Button(self,wx.ID_ANY, 'Run Fit', size=(140,1.5*BtnSize))
			#self.Bind(wx.EVT_BUTTON, mainwin.OnFitButton, RunFitButton)
			
			# Bounds - min/max boxes
			self.main_paramlist_mins = [ FloatSpin(self,value=str(defval),increment=definc,size=(60,-1),digits=3) for defval,definc in zip(main_paramlist_mindefaults,defaultvals_main_increments) ]
			self.main_paramlist_maxs = [ FloatSpin(self,value=str(defval),increment=definc,size=(60,-1),digits=3) for defval,definc in zip(main_paramlist_maxdefaults,defaultvals_main_increments) ]
			self.magnet_paramlist_mins = [ FloatSpin(self,value=str(defval),increment=definc,size=(60,-1),digits=3) for defval,definc in zip(magnet_paramlist_mindefaults,defaultvals_magnet_increments) ]
			self.magnet_paramlist_maxs = [ FloatSpin(self,value=str(defval),increment=definc,size=(60,-1),digits=3) for defval,definc in zip(magnet_paramlist_maxdefaults,defaultvals_magnet_increments) ]
			#self.polarisation_paramlist_mins = [ FloatSpin(self,value=str(defval),increment=definc,size=(80,-1),digits=3) for defval,definc in zip(detuning_defaults,detuning_increments) ]
			#self.polarisation_paramlist_maxs = [ FloatSpin(self,value=str(defval),increment=definc,size=(80,-1),digits=3) for defval,definc in zip(detuning_defaults,detuning_increments) ]
			
			self.fit_polarisation_checkbox = wx.CheckBox(self, label='Fit Polarisation?')
			self.fit_polarisation_checkbox.SetToolTip(wx.ToolTip(fit_polarisation_tooltip))
			self.constrain_linear_checkbox = wx.CheckBox(self, label='Constrain Linear?')
			self.constrain_linear_checkbox.SetToolTip(wx.ToolTip(constrain_polarisation_tooltip))
			self.Bind(wx.EVT_CHECKBOX, self.OnConstrainLinear, self.constrain_linear_checkbox)
			
			
		# Theory only:
		elif self.paneltype == 'Theory':
			# Detuning range selection
			Detuning_Labels = [ wx.StaticText(self,wx.ID_ANY,lab) for lab in ["Start [GHz]", "Stop [GHz]", "No. of points"]]
			self.DetuningCtrl = [ FloatSpin(self,value=str(defval),increment=definc,size=(70,-1),digits=3) for defval,definc in zip(detuning_defaults,detuning_increments) ]
			for ctrl in Detuning_Labels:
				ctrl.SetToolTip(wx.ToolTip(parameter_adjust_tooltip))
			for ctrl in self.DetuningCtrl:
				self.all_floatspin_inputs.append(ctrl)
			
			self.main_paramlist_bools = [0]*len(main_parameterlist)
			self.main_paramlist_usebounds = [0]*len(main_parameterlist)
			self.main_paramlist_mins = [0]*len(main_parameterlist)
			self.main_paramlist_maxs = [0]*len(main_parameterlist)
			self.magnet_paramlist_bools = [0]*len(magnet_parameterlist)
			self.magnet_paramlist_usebounds = [0]*len(magnet_parameterlist)
			self.magnet_paramlist_mins = [0]*len(magnet_parameterlist)
			self.magnet_paramlist_maxs = [0]*len(magnet_parameterlist)

		## FIXED PARAMETERS
		fixed_paramlist_labels = [ wx.StaticText(self,wx.ID_ANY,fixed_param) for 
											fixed_param in fixed_parameterlist ]
		# Add tooltips describing each parameter
		for label, tt in zip(fixed_paramlist_labels,fixed_parameter_tooltips):
			label.SetToolTip(wx.ToolTip(tt))
		# Add Combo Boxes + Check Box
		self.fixed_paramlist_inputs = [ \
		wx.ComboBox(self,wx.ID_ANY,choices=element_list,style=wx.CB_READONLY,size=(70,-1)), \
		wx.ComboBox(self,wx.ID_ANY,choices=D_line_list, style=wx.CB_READONLY,size=(70,-1)), \
		wx.CheckBox(self, label="")]
		self.fixed_paramlist_inputs[0].SetSelection(2) # default selections
		self.fixed_paramlist_inputs[1].SetSelection(1)
		self.fixed_paramlist_inputs[2].SetValue(True)
		
		self.Bind(wx.EVT_COMBOBOX, self.OnElementSelect, self.fixed_paramlist_inputs[0])

		## MAIN PARAMETERS
		# create list of parameters - labels and spin-control boxes
		main_paramlist_labels = [ wx.StaticText(self,wx.ID_ANY,fit_param+unit) for fit_param,unit in zip(
											main_parameterlist,mainunits_parameterlist) ]
		# Tooltips
		for label, tt in zip(main_paramlist_labels, main_parameter_tooltips):
			label.SetToolTip(wx.ToolTip(tt+parameter_adjust_tooltip))
		# Input boxes
		self.main_paramlist_inputs = [ \
		FloatSpin(self,value=str(defval),increment=definc,size=(70,-1),digits=3) for defval,definc in zip(defaultvals_main_parameterlist,defaultvals_main_increments) ]
		for item in self.main_paramlist_inputs:
				self.all_floatspin_inputs.append(item)
		# Don't need to bind SpinCtrl/ComboBox inputs to actions - the values are 
		# read whenever 'Compute' or 'Fit' buttons are pressed

		### Layout panel in sizers
		
		# main sizer for the panel
		panel_sizer = wx.BoxSizer(wx.VERTICAL)
		
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		label_sizer = wx.BoxSizer(wx.HORIZONTAL)
		label_sizer.Add((10,-1),0,wx.EXPAND)
		elem_label = wx.StaticText(self,wx.ID_ANY,"Select element and D-line")
		font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
		elem_label.SetFont(font)
		label_sizer.Add(elem_label)
		label_sizer.Add((10,-1),1,wx.EXPAND)
		panel_sizer.Add(label_sizer)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		# Add common elements first:
		
		for label, input in zip(fixed_paramlist_labels,self.fixed_paramlist_inputs):
			hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
			hor_sizer.Add((30,-1),0,wx.EXPAND)
			hor_sizer.Add(label,0,wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((20,-1),1,wx.EXPAND)
			hor_sizer.Add(input,0,wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((10,-1),0,wx.EXPAND)
			
			panel_sizer.Add(hor_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,3),0,wx.EXPAND)
		
		# vertical space
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		label_sizer = wx.BoxSizer(wx.HORIZONTAL)
		label_sizer.Add((10,-1),0,wx.EXPAND)
		parameter_label_text = 'Main parameters'
		mainparamlabeltextbox = wx.StaticText(self,wx.ID_ANY,parameter_label_text)
		font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
		mainparamlabeltextbox.SetFont(font)
		label_sizer.Add(mainparamlabeltextbox,0,wx.EXPAND)
		label_sizer.Add((10,-1),1,wx.EXPAND)
		if self.paneltype == 'Fit':
			floatlabel = wx.StaticText(self,wx.ID_ANY,'Initial Value')
			label_sizer.Add(floatlabel,0,wx.ALIGN_BOTTOM)
			label_sizer.Add((10,-1),0,wx.EXPAND)
			floatlabel = wx.StaticText(self,wx.ID_ANY,'Float?')
			label_sizer.Add(floatlabel,0,wx.ALIGN_BOTTOM)
			label_sizer.Add((8,-1),0,wx.EXPAND)
			boundslabel = wx.StaticText(self,wx.ID_ANY,'Bounds?')
			label_sizer.Add(boundslabel,0,wx.ALIGN_BOTTOM)
			label_sizer.Add((10,-1),0,wx.EXPAND)
			minmaxlabel = wx.StaticText(self,wx.ID_ANY,'Min Value')
			label_sizer.Add(minmaxlabel,0,wx.ALIGN_BOTTOM)
			label_sizer.Add((13,-1),0,wx.EXPAND)
			minmaxlabel = wx.StaticText(self,wx.ID_ANY,'Max Value')
			label_sizer.Add(minmaxlabel,0,wx.ALIGN_BOTTOM)
			label_sizer.Add((15,-1),0,wx.EXPAND)
		panel_sizer.Add(label_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		for label,input,boolbox,boundsbox,minFS,maxFS in \
				zip(main_paramlist_labels,self.main_paramlist_inputs,
						self.main_paramlist_bools,self.main_paramlist_usebounds,
						self.main_paramlist_mins, self.main_paramlist_maxs):
			hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
			hor_sizer.Add((30,-1),0,wx.EXPAND)
			hor_sizer.Add(label,0,wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((20,-1),1,wx.EXPAND)
			hor_sizer.Add(input,0,wx.ALIGN_CENTER_VERTICAL)
			if self.paneltype == 'Fit':
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(boolbox,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(boundsbox,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(minFS,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((10,-1),0,wx.EXPAND)
				hor_sizer.Add(maxFS,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((10,-1),0,wx.EXPAND)
			panel_sizer.Add(hor_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,3),0,wx.EXPAND)

		## MAGNET PARAMETERS
		# create list of magnetic parameters - labels and spin-control boxes
		magnet_paramlist_labels = [ wx.StaticText(self,wx.ID_ANY,param+unit) for param,unit in zip(
													magnet_parameterlist,magnetunits_parameterlist) ]
		# Add Tooltips
		for label, tt in zip(magnet_paramlist_labels, magnet_parameter_tooltips):
			label.SetToolTip(wx.ToolTip(tt+parameter_adjust_tooltip))
		# Input boxes
		self.magnet_paramlist_inputs = [ \
		FloatSpin(self,value=str(defval),increment=definc,size=(70,-1),digits=3) for defval,definc in zip(defaultvals_magnet_parameterlist,defaultvals_magnet_increments) ]
		for item in self.magnet_paramlist_inputs:
				self.all_floatspin_inputs.append(item)
				
		## Sizers
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		label_sizer = wx.BoxSizer(wx.HORIZONTAL)
		label_sizer.Add((10,-1),0,wx.EXPAND)
		parameter_label_text = 'Magnet parameters'
		magparamlabeltextbox = wx.StaticText(self,wx.ID_ANY,parameter_label_text)
		font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
		magparamlabeltextbox.SetFont(font)
		label_sizer.Add(magparamlabeltextbox,0,wx.EXPAND)
		label_sizer.Add((10,-1),1,wx.EXPAND)
		panel_sizer.Add(label_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		for label,input,boolbox,boundsbox,minFS,maxFS in \
					zip(magnet_paramlist_labels,self.magnet_paramlist_inputs,
					self.magnet_paramlist_bools, self.magnet_paramlist_usebounds,
					self.magnet_paramlist_mins, self.magnet_paramlist_maxs):
			hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
			hor_sizer.Add((30,-1),0,wx.EXPAND)
			hor_sizer.Add(label,0,wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((20,-1),1,wx.EXPAND)
			hor_sizer.Add(input,0,wx.ALIGN_CENTER_VERTICAL)
			if self.paneltype == 'Fit':
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(boolbox,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(boundsbox,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((20,-1),0,wx.EXPAND)
				hor_sizer.Add(minFS,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((10,-1),0,wx.EXPAND)
				hor_sizer.Add(maxFS,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				
			hor_sizer.Add((10,-1),0,wx.EXPAND)
			panel_sizer.Add(hor_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,3),0,wx.EXPAND)

		#### POLARISATION CONTROLS
		self.pol_radios = [wx.RadioButton(self,label="Linear", style=wx.RB_GROUP), \
						 wx.RadioButton(self,label="Left CP"),\
						 wx.RadioButton(self,label="Right CP"),\
						 wx.RadioButton(self,label="Elliptical")
						 ]
			
		for pol_radio, tt in zip(self.pol_radios,polarisation_tooltips):
				self.Bind(wx.EVT_RADIOBUTTON, self.OnPolarisationTypeChange, pol_radio)
				pol_radio.SetToolTip(wx.ToolTip(tt))
		
		pol_control_labels = [ wx.StaticText(self,wx.ID_ANY,pol_ctrl) for 
												pol_ctrl in polarisation_controls ]
		for label, tt in zip(pol_control_labels, polarisation_parameter_tooltips):
			label.SetToolTip(wx.ToolTip(tt+parameter_adjust_tooltip))
		
		self.pol_control_inputs = [FloatSpin(self,value=str(defval),increment=definc,size=(70,-1),digits=3) for defval,definc in zip(defaultvals_pol_parameterlist, defaultvals_pol_increments)]
		for item in self.pol_control_inputs:
				self.all_floatspin_inputs.append(item)
		
		self.Bind(EVT_FLOATSPIN,self.OnTheta0,self.pol_control_inputs[0])
		self.Bind(EVT_FLOATSPIN,self.OnExCtrl,self.pol_control_inputs[1])
		self.Bind(EVT_FLOATSPIN,self.OnEyCtrl,self.pol_control_inputs[2])
	
		visualise_btn = wx.Button(self,label='Visualise Polarisation')
		self.Bind(wx.EVT_BUTTON,self.OnVisualisePol,visualise_btn)
		
		# initially, all but the first input are disabled
		for ctrl in self.pol_control_inputs[1:]:
			ctrl.Disable()
			
		if self.paneltype == 'Fit':
			pol_sizerFit = wx.BoxSizer(wx.VERTICAL)
			pol_sizerFit.Add((-1,10),1,wx.EXPAND)
			pol_sizerFit.Add(self.fit_polarisation_checkbox,0,wx.EXPAND)
			pol_sizerFit.Add((-1,10),0,wx.EXPAND)
			pol_sizerFit.Add(self.constrain_linear_checkbox,0,wx.EXPAND)
			pol_sizerFit.Add((-1,10),1,wx.EXPAND)
		
		## Sizers
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		label_sizer = wx.BoxSizer(wx.HORIZONTAL)
		label_sizer.Add((10,-1),0,wx.EXPAND)
		parameter_label_text = 'Polarisation Parameters'
		polparamlabeltextbox = wx.StaticText(self,wx.ID_ANY,parameter_label_text)
		font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
		polparamlabeltextbox.SetFont(font)
		label_sizer.Add(polparamlabeltextbox,0,wx.EXPAND)
		label_sizer.Add((10,-1),1,wx.EXPAND)
		panel_sizer.Add(label_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		pol_sizer = wx.BoxSizer(wx.HORIZONTAL)
		pol_sizerL = wx.BoxSizer(wx.VERTICAL)
		for radiobox in self.pol_radios:
			pol_sizerL.Add(radiobox,0,wx.EXPAND)
			pol_sizerL.Add((-1,5),0,wx.EXPAND)
		pol_sizerL.Add((-1,5),0,wx.EXPAND)
		pol_sizerL.Add(visualise_btn,0,wx.EXPAND)
			
		pol_sizerR = wx.BoxSizer(wx.VERTICAL)
		for label,input in zip(pol_control_labels,self.pol_control_inputs):
			hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
			hor_sizer.Add((5,-1),0,wx.EXPAND)
			hor_sizer.Add(label,0,wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((20,-1),1,wx.EXPAND)
			hor_sizer.Add(input,0,wx.ALIGN_CENTER_VERTICAL)
			#if self.paneltype == 'Fit':
			#	hor_sizer.Add((20,-1),0,wx.EXPAND)
			#	hor_sizer.Add(boolbox,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
			hor_sizer.Add((10,-1),0,wx.EXPAND)
			pol_sizerR.Add(hor_sizer,0,wx.EXPAND)
			pol_sizerR.Add((-1,3),0,wx.EXPAND)
		
		pol_sizer.Add((30,-1),0,wx.EXPAND)
		pol_sizer.Add(pol_sizerL,0,wx.EXPAND)
		pol_sizer.Add((5,-1),1,wx.EXPAND)
		pol_sizer.Add(pol_sizerR,0,wx.EXPAND)
		if self.paneltype == 'Fit':
			pol_sizer.Add((5,-1),0,wx.EXPAND)
			pol_sizer.Add(pol_sizerFit,0,wx.EXPAND)
		
		panel_sizer.Add(pol_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		if self.paneltype == 'Fit':
			# vertical space
			panel_sizer.Add((-1,5),0,wx.EXPAND)
			label_sizer = wx.BoxSizer(wx.HORIZONTAL)
			label_sizer.Add((10,-1),0,wx.EXPAND)
			parameter_label_text = 'Fit Algorithm'
			paramlabeltextbox = wx.StaticText(self,wx.ID_ANY,parameter_label_text)
			font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
			paramlabeltextbox.SetFont(font)
			label_sizer.Add(paramlabeltextbox,0,wx.EXPAND)
			label_sizer.Add((20,-1),1,wx.EXPAND)
			panel_sizer.Add(label_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,10),0,wx.EXPAND)
		
			for radiobtn in self.fit_types:
				hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
				hor_sizer.Add((30,-1),0,wx.EXPAND)
				hor_sizer.Add(radiobtn,0,wx.EXPAND)
				hor_sizer.Add((20,-1),1,wx.EXPAND)
				panel_sizer.Add(hor_sizer,0,wx.EXPAND)
				panel_sizer.Add((-1,4),0,wx.EXPAND)		
				
		elif self.paneltype == 'Theory':
			# vertical space
			panel_sizer.Add((-1,5),0,wx.EXPAND)
			label_sizer = wx.BoxSizer(wx.HORIZONTAL)
			label_sizer.Add((10,-1),0,wx.EXPAND)
			parameter_label_text = 'Detuning Range'
			paramlabeltextbox = wx.StaticText(self,wx.ID_ANY,parameter_label_text)
			font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
			paramlabeltextbox.SetFont(font)
			label_sizer.Add(paramlabeltextbox,0,wx.EXPAND)
			label_sizer.Add((20,-1),1,wx.EXPAND)
			panel_sizer.Add(label_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,10),0,wx.EXPAND)
			
			for label, input in zip(Detuning_Labels, self.DetuningCtrl):
				hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
				hor_sizer.Add((30,-1),0,wx.EXPAND)
				hor_sizer.Add(label,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((20,-1),1,wx.EXPAND)
				hor_sizer.Add(input,0,wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
				hor_sizer.Add((10,-1),0,wx.EXPAND)
				panel_sizer.Add(hor_sizer,0,wx.EXPAND)
				panel_sizer.Add((-1,3),0,wx.EXPAND)				
				
		# vertical space
		panel_sizer.Add((-1,5),1,wx.EXPAND)				
		panel_sizer.Add((-1,2),0,wx.EXPAND)
		
		#init the right combination of enabled boxes
		self.OnElementSelect(1)
		if self.paneltype == 'Fit':
			self.OnBoundsTicked(1)
			self.OnFloatTicked(1)
		
		self.SetSizer(panel_sizer)
		self.SetupScrolling()
		self.Layout()	
		
	def OnConstrainLinear(self,event):
		if self.constrain_linear_checkbox.IsChecked():
			self.pol_control_inputs[3].SetValue(0)
	
	def OnElementSelect(self,event):
		""" Action when the element selection drop-down is clicked """
		selection = self.fixed_paramlist_inputs[0].GetValue()
		if selection in ('Cs', 'Na'):
			# disable Rb and K isotope selections
			for inputbox in self.main_paramlist_inputs[5:]:
				inputbox.Disable()
			if self.paneltype == 'Fit':
				for checkbox in self.main_paramlist_bools[5:]:
					checkbox.SetValue(False)
					checkbox.Disable()
		elif selection == 'Rb':
			# Enable Rb
			self.main_paramlist_inputs[5].Enable()
			if self.paneltype == 'Fit': self.main_paramlist_bools[5].Enable()
			#Disbale K
			for inputbox in self.main_paramlist_inputs[6:]:
				inputbox.Disable()
			if self.paneltype == 'Fit':
				for checkbox in self.main_paramlist_bools[6:]:
					checkbox.Disable()
		elif selection == 'K':
			# Disabel Rb
			self.main_paramlist_inputs[5].Disable()
			if self.paneltype == 'Fit': self.main_paramlist_bools[5].Disable()
			#Disbale K
			for inputbox in self.main_paramlist_inputs[6:]:
				inputbox.Disable()
			if self.paneltype == 'Fit':
				for checkbox in self.main_paramlist_bools[6:]:
					checkbox.Enable()			
		
	def OnFloatTicked(self,event):
		""" Action when any of the 'float?' tick boxes are clicked """
		# Enable / Disable controls for varying parameters
		for i,box in enumerate(self.main_paramlist_bools):
			if not box.IsChecked():
				self.main_paramlist_usebounds[i].SetValue(False)
				self.OnBoundsTicked(1)
				self.main_paramlist_usebounds[i].Disable()				
			else:
				self.main_paramlist_usebounds[i].Enable()

		for i,box in enumerate(self.magnet_paramlist_bools):
			if not box.IsChecked():
				self.magnet_paramlist_usebounds[i].SetValue(False)
				self.OnBoundsTicked(1)
				self.magnet_paramlist_usebounds[i].Disable()				
			else:
				self.magnet_paramlist_usebounds[i].Enable()
				
	def OnBoundsTicked(self,event):
		""" Action when any of the fit bounds tick boxes are clicked """
		# enable / disable bounds floatspin controls if checkboxes are ticked
		for i,box in enumerate(self.main_paramlist_usebounds):
			if not box.IsChecked():
				self.main_paramlist_mins[i].Disable()
				self.main_paramlist_maxs[i].Disable()
			else:
				self.main_paramlist_mins[i].Enable()
				self.main_paramlist_maxs[i].Enable()

		for i,box in enumerate(self.magnet_paramlist_usebounds):
			if not box.IsChecked():
				self.magnet_paramlist_mins[i].Disable()
				self.magnet_paramlist_maxs[i].Disable()
			else:
				self.magnet_paramlist_mins[i].Enable()
				self.magnet_paramlist_maxs[i].Enable()
			
	def OnPolarisationTypeChange(self,event):
		""" Action when any of the polarisation radio buttons are clicked """
		
		# if Linear selected, make Ex,Ey,Phase inactive, set theta0 active and set Phase to 0. On theta0 change, fill in Ex Ey as necessary
		if self.pol_radios[0].GetValue():
			for ctrl in self.pol_control_inputs[1:]:
				ctrl.Disable()
			self.pol_control_inputs[0].Enable()
			
			#set phase to 0 (linear)
			self.pol_control_inputs[3].SetValue(0)
				
		if self.pol_radios[1].GetValue():
			for ctrl in self.pol_control_inputs:
				ctrl.Disable()
			self.pol_control_inputs[1].SetValue(1./np.sqrt(2))
			self.pol_control_inputs[2].SetValue(1./np.sqrt(2))
			self.pol_control_inputs[3].SetValue(90)
			
		if self.pol_radios[2].GetValue():
			for ctrl in self.pol_control_inputs:
				ctrl.Disable()
			self.pol_control_inputs[1].SetValue(1./np.sqrt(2))
			self.pol_control_inputs[2].SetValue(1./np.sqrt(2))
			self.pol_control_inputs[3].SetValue(270)
			
		if self.pol_radios[3].GetValue():
			self.pol_control_inputs[0].Disable()
			for ctrl in self.pol_control_inputs[1:]:
				ctrl.Enable()
	
	def OnTheta0(self,event):
		""" Update values of Ex,Ey when theta0 is changed """
		self.pol_control_inputs[1].SetValue(np.cos(np.pi/180 * self.pol_control_inputs[0].GetValue()))
		self.pol_control_inputs[2].SetValue(np.sin(np.pi/180 * self.pol_control_inputs[0].GetValue()))
	
	def OnExCtrl(self,event):
		if self.pol_control_inputs[1].GetValue() <= 1:
			self.pol_control_inputs[2].SetValue(np.sqrt(1-self.pol_control_inputs[1].GetValue()**2))
		else:
			scaling = np.sqrt(self.pol_control_inputs[1].GetValue()**2 + self.pol_control_inputs[2].GetValue()**2)
			self.pol_control_inputs[2].SetValue(self.pol_control_inputs[2].GetValue() / scaling)
			self.pol_control_inputs[1].SetValue(self.pol_control_inputs[1].GetValue() / scaling)
		
		self.pol_control_inputs[0].SetValue( 180./np.pi * np.arctan(self.pol_control_inputs[2].GetValue() / self.pol_control_inputs[1].GetValue() ) )

	def OnEyCtrl(self,event):
		if self.pol_control_inputs[2].GetValue() <= 1:
			self.pol_control_inputs[1].SetValue(np.sqrt(1-self.pol_control_inputs[2].GetValue()**2))
		else:
			scaling = np.sqrt(self.pol_control_inputs[1].GetValue()**2 + self.pol_control_inputs[2].GetValue()**2)
			self.pol_control_inputs[2].SetValue(self.pol_control_inputs[2].GetValue() / scaling)
			self.pol_control_inputs[1].SetValue(self.pol_control_inputs[1].GetValue() / scaling)
		
		self.pol_control_inputs[0].SetValue( 180./np.pi * np.arctan(self.pol_control_inputs[2].GetValue() / self.pol_control_inputs[1].GetValue() ) )
	
	def OnVisualisePol(self,event):
		""" Run visualise polarisation animation in 3D plot"""
		Ex,Ey,phase = self.pol_control_inputs[1].GetValue(), self.pol_control_inputs[2].GetValue(), self.pol_control_inputs[3].GetValue()*np.pi/180
		
		plt.close(self.mainwin.figs[0])
		plt.close(self.mainwin.figs[1])
		
		pol_ani.animate_vectors(Ex,Ey,phase)
	
class PlotToolPanel(wx.Panel):
	""" Panel to hold the matplotlib figure/canvas and toolbar """
	def __init__(self, parent, mainwin, ID):
		""" mainwin is the main panel so we can bind buttons to actions in the main frame """
		wx.Panel.__init__(self, parent, id=-1)
				
		self.fig = plt.figure(ID,facecolor=(240./255,240./255,240./255),figsize=(12.9,9.75),dpi=80)
		
		#self.ax = self.fig.add_subplot(111)
		
		# create the wx objects to hold the figure
		self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
		self.toolbar = Toolbar(self.canvas) #matplotlib toolbar (pan, zoom, save etc)
		#self.toolbar.Realize()
		
		# Create vertical sizer to hold figure and toolbar - dynamically expand with window size
		plot_sizer = wx.BoxSizer(wx.VERTICAL)
		plot_sizer.Add(self.canvas, 1, flag = wx.EXPAND|wx.ALL) #wx.TOP|wx.LEFT|wx.GROW)
		plot_sizer.Add(self.toolbar, 0, wx.EXPAND)

		mainwin.figs.append(self.fig)
		mainwin.fig_IDs.append(ID) # use an ID number to keep track of figures
		mainwin.canvases.append(self.canvas)

		# display some text in the middle of the window to begin with
		self.fig.text(0.5,0.5,'ElecSus GUI\n\nVersion '+__version__+'\n\nTo get started, use the panel on the right\nto either Compute a spectrum or Import some data...', ha='center',va='center')
		#self.fig.hold(False)
		
		self.SetSizer(plot_sizer)
		#self.Layout() #Fit()

class StatusPanel(scrolled.ScrolledPanel):
	""" Panel to hold status information (contents of the tab) """
	def __init__(self, parent, mainwin, ID):
		""" mainwin is the main panel so we can bind buttons to actions in the main frame """
		scrolled.ScrolledPanel.__init__(self, parent)
	
		statusTitle = wx.StaticText(self, wx.ID_ANY, ID)
		font = wx.Font(12,wx.DEFAULT, wx.NORMAL,wx.NORMAL)
		statusTitle.SetFont(font)

		self.StatusTextBox = wx.TextCtrl(self,wx.ID_ANY,"",style=wx.TE_READONLY|wx.TE_MULTILINE)
		#self.StatusTextBox.Size.SetHeight(500)
		
		self.SaveBtn = wx.Button(self,wx.ID_ANY,"Save Text to file",size=(100,-1))
		self.Bind(wx.EVT_BUTTON,self.OnSave,self.SaveBtn)
		
		
		panel_sizer = wx.BoxSizer(wx.VERTICAL)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		panel_sizer.Add(statusTitle,0,wx.EXPAND|wx.LEFT,border=20)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		panel_sizer.Add(self.StatusTextBox,1,wx.EXPAND|wx.LEFT|wx.RIGHT,border=40)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		panel_sizer.Add(self.SaveBtn,0,wx.EXPAND|wx.LEFT|wx.RIGHT,border=40)
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		

		self.SetSizer(panel_sizer)
		self.SetupScrolling()
		self.Layout()
	
	def OnSave(self,event):
		""" 
		Save text file dump of all data in the panel - select filename, 
		check for overwrite and then pass to save_data() method
		"""
		SaveFileDialog = wx.FileDialog(self,"Save Output File", "./", "Outputs",
			"Text files (*.txt)|*.txt", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		
		if SaveFileDialog.ShowModal() == wx.ID_OK:
			
			output_filename = SaveFileDialog.GetPath()
			print(output_filename)
			#if output_filename[-4:] == exts[SaveFileDialog.GetFilterIndex()]:
			#	output_filename = output_filename[:-4]
			SaveFileDialog.Destroy()

			#check for overwrite current files
			if os.path.isfile(output_filename):
				OverwriteDialog = wx.MessageDialog(self,"Warning: file exists already! Overwrite?",\
					"Overwrite?",wx.YES_NO|wx.NO_DEFAULT)
				
				if OverwriteDialog.ShowModal() == wx.NO:
					OverwriteDialog.Destroy()
					return # exit without saving
				else:
					OverwriteDialog.Destroy()
			
			# do save
			self.save_data(output_filename)
		
	def save_data(self,filename):
		""" Save the data using wx.TextCtrl built-in method """
		success = self.StatusTextBox.SaveFile(filename) # returns true if no errors
		if not success:
			problem_dlg = wx.MessageDialog(self, "There was an error saving the data...", "Error saving", wx.OK|wx.ICON_ERROR)
			problem_dlg.ShowModal()

	def write(self,textstring):
		""" Append text to the text box """
		self.StatusTextBox.AppendText(textstring)
		
class DataProcessingDlg(wx.Dialog):
	def __init__(self,parent,title,id):
		""" Dialog box for smoothing and/or binning experimental data """
		
		wx.Dialog.__init__(self,parent,id,title,size=(360,450))
		
		self.SetMinSize((360,450))
		
		self.parent = parent
		
		# make copy of non-binned / smoothed data in case reset button is pressed
		self.original_xdata = parent.x_fit_array
		self.original_ydata = parent.y_expt_arrays

		print('Length before binning: ', len(self.parent.x_fit_array))
		
		panel_blurb = wx.StaticText(self,wx.ID_ANY,"Data smoothing and binning options.",size=(350,-1),style=wx.ALIGN_CENTRE_HORIZONTAL)
		panel_blurb.Wrap(350)
		
		bin_blurb = wx.StaticText(self,wx.ID_ANY,"Binning data is useful where the initial data size is very large. The output from this function is smaller by a specified factor n, which also removes some noise since every n data points are averaged into one.\n\n For fitting data, the number of experimental data points is a factor in how long the fit takes to run - making the array smaller makes the fit quicker.",size=(350,-1),style=wx.ALIGN_CENTRE_HORIZONTAL)
		bin_blurb.Wrap(350)
		
		self.cur_dsize_label = wx.StaticText(self,wx.ID_ANY,"Current data size:")
		self.cur_dsize = wx.TextCtrl(self,wx.ID_ANY,"",style=wx.TE_READONLY|wx.TE_CENTRE,size=(80,-1))
		self.cur_dsize.ChangeValue(str(len(parent.x_fit_array)))
		
		self.bin_size_label = wx.StaticText(self,wx.ID_ANY,"Bin size:")
		self.bin_size = wx.TextCtrl(self,wx.ID_ANY,"11",style=wx.TE_CENTRE,size=(80,-1))
		self.Bind(wx.EVT_TEXT, self.OnBinSizeChange, self.bin_size)
		
		self.new_dsize_label = wx.StaticText(self,wx.ID_ANY,"Data size after binning:")
		self.new_dsize = wx.TextCtrl(self,wx.ID_ANY,"",style=wx.TE_READONLY|wx.TE_CENTRE,size=(80,-1))
		self.new_dsize.ChangeValue(str(int(len(parent.x_fit_array)/float(self.bin_size.GetValue()))))
		
		bin_btn = wx.Button(self,label="Bin Data",size=(75,-1))
		self.Bind(wx.EVT_BUTTON,self.OnDoBin,bin_btn)
		
		smooth_blurb = wx.StaticText(self,wx.ID_ANY,"Smoothing is based on a moving average with a triangular weighting function of a specified width. For convenience, the output array is the same length as the input, but the smoothing is only applied over N - 2n data points (removing n from each end of the array), where N is the length of the data array and n is the width of the smoothing window.",size=(350,-1),style=wx.ALIGN_CENTRE_HORIZONTAL)
		smooth_blurb.Wrap(350)
		
		self.smooth_size_label = wx.StaticText(self,wx.ID_ANY,"Moving average window size:")
		self.smooth_size = wx.TextCtrl(self,wx.ID_ANY,"11",style=wx.TE_CENTRE,size=(80,-1))
				
		smooth_btn = wx.Button(self,label="Smooth Data")
		self.Bind(wx.EVT_BUTTON,self.OnDoSmooth,smooth_btn)
		
		reset_btn = wx.Button(self,label='Reset to original data')
		self.Bind(wx.EVT_BUTTON,self.OnReset,reset_btn)
		
		close_btn = wx.Button(self,wx.ID_OK,label='Close')
		
		## layout dialog box
		panel_sizer = wx.BoxSizer(wx.VERTICAL)
		
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		panel_sizer.Add(panel_blurb,0,wx.ALIGN_CENTRE_HORIZONTAL)
		
		bin_sizer = wx.BoxSizer(wx.HORIZONTAL)
		bin_sizer.Add((20,-1),0,wx.EXPAND)
		bin_sizer.Add(self.bin_size_label,0,wx.EXPAND)
		bin_sizer.Add((10,-1),1,wx.EXPAND)
		bin_sizer.Add(self.bin_size,0,wx.EXPAND)
		bin_sizer.Add((10,-1),0,wx.EXPAND)
		bin_sizer.Add(bin_btn,0,wx.EXPAND)
		bin_sizer.Add((10,-1),0,wx.EXPAND)

		cdata_sizer = wx.BoxSizer(wx.HORIZONTAL)
		cdata_sizer.Add((20,-1),0,wx.EXPAND)
		cdata_sizer.Add(self.cur_dsize_label,0,wx.EXPAND)
		cdata_sizer.Add((10,-1),1,wx.EXPAND)
		cdata_sizer.Add(self.cur_dsize,0,wx.EXPAND)
		cdata_sizer.Add((95,-1),0,wx.EXPAND)
		
		ndata_sizer = wx.BoxSizer(wx.HORIZONTAL)
		ndata_sizer.Add((20,-1),0,wx.EXPAND)
		ndata_sizer.Add(self.new_dsize_label,0,wx.EXPAND)
		ndata_sizer.Add((10,-1),1,wx.EXPAND)
		ndata_sizer.Add(self.new_dsize,0,wx.EXPAND)
		ndata_sizer.Add((95,-1),0,wx.EXPAND)
		
		smooth_sizer = wx.BoxSizer(wx.HORIZONTAL)
		smooth_sizer.Add((20,-1),0,wx.EXPAND)
		smooth_sizer.Add(self.smooth_size_label,0,wx.EXPAND)
		smooth_sizer.Add((10,-1),1,wx.EXPAND)
		smooth_sizer.Add(self.smooth_size,0,wx.EXPAND)
		smooth_sizer.Add((10,-1),0,wx.EXPAND)
		smooth_sizer.Add(smooth_btn,0,wx.EXPAND)
		smooth_sizer.Add((10,-1),0,wx.EXPAND)
		
		btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
		btn_sizer.Add((20,-1),1,wx.EXPAND)
		btn_sizer.Add(reset_btn,0,wx.EXPAND)
		btn_sizer.Add((20,-1),0,wx.EXPAND)
		btn_sizer.Add(close_btn,0,wx.EXPAND)
		btn_sizer.Add((20,-1),1,wx.EXPAND)
		
		
		panel_sizer.Add((-1,20),0,wx.EXPAND)		
		panel_sizer.Add(bin_blurb,0,wx.EXPAND)
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		panel_sizer.Add(cdata_sizer,0,wx.EXPAND)
		panel_sizer.Add(bin_sizer,0,wx.EXPAND)
		panel_sizer.Add(ndata_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		panel_sizer.Add(wx.StaticLine(self,-1,size=(-1,1),style=wx.LI_HORIZONTAL),
							0,wx.EXPAND|wx.LEFT|wx.RIGHT,border=20)
		panel_sizer.Add((-1,20),0,wx.EXPAND)	
		panel_sizer.Add(smooth_blurb,0,wx.EXPAND)
		panel_sizer.Add((-1,20),0,wx.EXPAND)		
		panel_sizer.Add(smooth_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,20),1,wx.EXPAND)		
		panel_sizer.Add(btn_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)		
		
		self.SetSizerAndFit(panel_sizer)
		
		self.Layout()
		
		
	def OnBinSizeChange(self,event):
		""" update ui elements for value of new data length """
		#try:
		self.cur_dsize.ChangeValue(str(int(len(self.parent.x_fit_array))))
		self.new_dsize.ChangeValue(str(int(len(self.parent.x_fit_array)/float(self.bin_size.GetValue()))))
		#except:
		#	pass
		
	def OnDoBin(self,event):
		""" Action when bin button is pressed """
		bin_amnt = int(self.bin_size.GetValue())
		
		## only bin/smooth the most recently loaded data set
		self.parent.x_fit_array,self.parent.y_fit_array,ye = data_proc.bin_data(self.parent.x_fit_array,self.parent.y_fit_array,bin_amnt)
		
		print('Length ater binning: ', len(self.parent.x_fit_array))

		#re-plot data
		self.update_plot()
		
		#update ui elements with new data size...
		self.OnBinSizeChange(1)
		
		
	def OnDoSmooth(self,event):
		""" Action when smooth button is pressed """
		smth_amnt = int(self.smooth_size.GetValue())
		
		self.parent.y_fit_array = data_proc.smooth_data(self.parent.y_fit_array,smth_amnt)
		
		#re-plot data
		self.update_plot()

	def OnReset(self,event):
		""" Reset to the original data, i.e. the data present when the dialog box was created """
		self.parent.x_fit_array = self.original_xdata
		self.parent.y_fit_array = self.original_ydata
		
		#re-plot data
		self.update_plot()
				
	def update_plot(self):
		""" Common method for updating the plot """
		#update plot arrays
		cindex = self.parent.choice_index
		self.parent.x_expt_arrays[cindex] = self.parent.x_fit_array
		self.parent.y_expt_arrays[cindex] = self.parent.y_fit_array

		self.parent.OnCreateAxes(self.parent.figs[0],self.parent.canvases[0],clear_current=True)
						
class AdvancedFitOptions(wx.Dialog):
	def __init__(self,parent,title,id):
		""" Dialog box for selecting advanced fit options which are passed through to lmfit optimisation routines """
		
		wx.Dialog.__init__(self,parent,id,title,size=(500,900))
				
		# main sizer for this dialog box
		panel_sizer = wx.BoxSizer(wx.VERTICAL)
		
		panel_blurb = wx.StaticText(self,wx.ID_ANY,"These are the options given to lmfit when fitting data. \n\n BLURB TO UPDATE LATER",size=(460,-1),style=wx.ALIGN_CENTRE_HORIZONTAL)
		panel_blurb.Wrap(460)
		
		#### UPDATE FOR DIFFERENTIAL EVOLUTION + BOUNDS ON FIT PARAMETERS ####
		
		self.fitopt_labels = ['Max Iterations (maxfev)', 'Max Tolerance [1e-8] (ftol)']
		self.fitopt_argnames = ['maxfev', 'ftol']
		fitopt_defaults = [1000, 15]
		fitopt_increments = [100, 1]
		self.fitopt_scaling = [1, 1e-9]
		fitopt_texts = [ wx.StaticText(self,wx.ID_ANY,label) for label in self.fitopt_labels ]
		self.fitopt_ctrl = [ wx.SpinCtrl(self,value=str(defval),size=(80,-1),min=0,max=10000,initial=defval) for defval,definc in zip(fitopt_defaults, fitopt_increments) ]
		
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		panel_sizer.Add(panel_blurb,0,wx.LEFT|wx.RIGHT,border=20)
		panel_sizer.Add((-1,15),0,wx.EXPAND)
		panel_sizer.Add(wx.StaticLine(self,-1,size=(-1,1),style=wx.LI_HORIZONTAL),0,wx.EXPAND|wx.LEFT|wx.RIGHT,border=20
		)
		panel_sizer.Add((-1,15),0,wx.EXPAND)

		for static,ctrl in zip(fitopt_texts, self.fitopt_ctrl):
			hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
			hor_sizer.Add(static,0,wx.EXPAND|wx.LEFT,border=20)
			hor_sizer.Add((10,-1),1,wx.EXPAND)
			hor_sizer.Add(ctrl,0,wx.EXPAND|wx.RIGHT,border=20)
			panel_sizer.Add(hor_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,5),0,wx.EXPAND)
			
		# ok and cancel buttons
		btnbar = self.CreateButtonSizer(wx.OK|wx.CANCEL)

		#panel_sizer.Add((-1,10),0,wx.EXPAND)
		#panel_sizer.Add(wx.StaticLine(self,-1,size=(-1,1),style=wx.LI_HORIZONTAL),0, wx.EXPAND|wx.LEFT|wx.RIGHT,border=20)

		panel_sizer.Add((-1,10),1,wx.EXPAND)
		panel_sizer.Add(btnbar,0,wx.ALIGN_CENTER)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		self.SetSizer(panel_sizer)
		self.Layout()
		
	def return_all_options(self):
		""" get list of all ctrl elements """
		opt_dict = dict([(label,input.GetValue()*scaling) for label,input,scaling \
			in zip(self.fitopt_argnames,self.fitopt_ctrl,self.fitopt_scaling)])
		#print 'Advanced fit options dictionary: ',opt_dict
		return opt_dict

class FitBoundsDialog(wx.Dialog):
	def __init__(self,parent,title,id):
		""" Dialog box for selecting advanced fit options which are passed through to curve_fit / leastsq optimisation routines... """
		
		wx.Dialog.__init__(self,parent,id,title,size=(300,300))
		
		self.parent = parent

		# main sizer for this dialog box
		panel_sizer = wx.BoxSizer(wx.VERTICAL)
		
		panel_blurb = wx.StaticText(self,wx.ID_ANY,
			"Set bounds to fit data. By default, all data is fitted, but here the fitted data can be limited to a certain detuning range",
			size=(260,-1),style=wx.ALIGN_CENTRE_HORIZONTAL)
		panel_blurb.Wrap(260)
		
		self.detuning_fit_bounds = [wx.RadioButton(self,label="Use Full Data Range", style=wx.RB_GROUP), \
						 wx.RadioButton(self,label="Cropped Data Range") ]
		for btn in self.detuning_fit_bounds:
			self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioSelector, btn)
		
		self.fit_range_ctrl = [ wx.TextCtrl(self,wx.ID_ANY,"-5",style=wx.TE_CENTRE,size=(80,-1)),
							wx.TextCtrl(self,wx.ID_ANY,"5",style=wx.TE_CENTRE,size=(80,-1)) ]
		self.OnRadioSelector(1)
	
		fit_range_labels = [ wx.StaticText(self,wx.ID_ANY,"Min: "), wx.StaticText(self,wx.ID_ANY,"Max: ") ]
		
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		panel_sizer.Add(panel_blurb,0,wx.ALIGN_CENTRE_HORIZONTAL)
		
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		panel_sizer.Add(wx.StaticLine(self,-1,size=(-1,1),style=wx.LI_HORIZONTAL),
							0,wx.EXPAND|wx.LEFT|wx.RIGHT,border=20)
		panel_sizer.Add((-1,20),0,wx.EXPAND)
		
		panel_sizer.Add(self.detuning_fit_bounds[0],0,wx.EXPAND|wx.LEFT,border=30)
		panel_sizer.Add((-1,5),0,wx.EXPAND)
		panel_sizer.Add(self.detuning_fit_bounds[1],0,wx.EXPAND|wx.LEFT,border=30)
		
		panel_sizer.Add((-1,20),0,wx.EXPAND)

		for i in range(2):
			ctrl_sizer = wx.BoxSizer(wx.HORIZONTAL)
			ctrl_sizer.Add((45,-1),1,wx.EXPAND)
			ctrl_sizer.Add(fit_range_labels[i],0,wx.EXPAND)
			ctrl_sizer.Add((15,-1),0,wx.EXPAND)
			ctrl_sizer.Add(self.fit_range_ctrl[i],0,wx.EXPAND)
			ctrl_sizer.Add((45,-1),1,wx.EXPAND)
			
			panel_sizer.Add(ctrl_sizer,0,wx.EXPAND)
			panel_sizer.Add((-1,5),0,wx.EXPAND)
			
		panel_sizer.Add((-1,15),1,wx.EXPAND)
		
		btnbar = self.CreateButtonSizer(wx.OK|wx.CANCEL)
		btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
		btn_sizer.Add((20,-1),1,wx.EXPAND)
		btn_sizer.Add(btnbar,0,wx.EXPAND)
		btn_sizer.Add((20,-1),1,wx.EXPAND)
		
		panel_sizer.Add(btn_sizer,0,wx.EXPAND)
		panel_sizer.Add((-1,10),0,wx.EXPAND)
		
		self.SetSizer(panel_sizer)
		self.Layout()	
		
	def OnRadioSelector(self,event):
		## enable / disbale the text input box
		enabled = self.detuning_fit_bounds[1].GetValue()
		for ctrl in self.fit_range_ctrl:
			ctrl.Enable(enabled)
		
	def OnUpdateRange(self):
		if self.detuning_fit_bounds[0].GetValue():
			# if using full data range
			try:
				return self.parent.x_fit_array[0], self.parent.x_fit_array[-1]
			except: #no data loaded
				return [None, None]
		else:
			return [float(self.fit_range_ctrl[0].GetValue()), float(self.fit_range_ctrl[1].GetValue())]
			
class ElecSus_GUI_Frame(wx.Frame):
	""" Main class for this program - top-level window """
	def __init__(self,parent,title):
		""" Initialise main frame """
		wx.Frame.__init__(self,None,title=title,size=(2000,900))
		
		## EXPERIMENTAL
		#self._mgr = aui.AuiManager()
		#
		## notify AUI which frame to use
		#self._mgr.SetManagedWindow(self)

		#ubuntu sizing:
		if os.name == 'posix':
			font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL)
			self.SetFont(font)
		
		# Set icons for top-left of frame, alt-tab window ...
		frame_icon = wx.IconBundle()
		try:
			frame_icon.AddIconFromFile(os.path.join(elecsus_dir,'images/elecsus_t_group.ico'), wx.BITMAP_TYPE_ANY)
		except:
			# new wx version
			frame_icon.AddIcon(os.path.join(elecsus_dir,'images/elecsus_t_group.ico'), wx.BITMAP_TYPE_ANY)
		
		self.SetIcons(frame_icon)

		#if the window is closed, exit
		self.Bind(wx.EVT_CLOSE,self.OnExit)

		self.panel = wx.Panel(self)
		
		self.panel.SetBackgroundColour(wx.Colour(240,240,240))
		
		self._init_default_values()	
		self._init_plot_defaults()
		self._init_menus()
		self._init_panels()
		
		## redirect stdout (command line text) to status box
		sys.stdout = self.StatusPanel
		sys.stderr = self.ErrorPanel
		
		# Create initially blank set of axes
		#self.OnCreateAxes(self.figs[0],self.canvases[0])
		
		## Bind the event EVT_FIT_COMPLETE to function
		## This executes in the main thread once the fitting thread 
		## (separate from the main thread) completes
		self.Bind(EVT_FIT_COMPLETE,self.OnFitCompleted)
								
	def _init_default_values(self):
		""" Initialise default values for various things ... """
		
		self.figs = []
		self.canvases = []
		self.fig_IDs = []
		
		self.hold = False
		
		self.fit_algorithm = 'Marquardt-Levenberg'
		self.already_fitting = False
		self.warnings = True

		# initialise advanced fit options dictionary
		dlg = AdvancedFitOptions(self,"Advanced Fit Options",wx.ID_ANY)
		self.advanced_fitoptions = dlg.return_all_options()
		dlg.Destroy()		
						
	def _init_plot_defaults(self):
		""" 
		List of default values for all plots. 
		Theory plots can be up to 8 panels, each of which can be turned on/off 
		"""
		
		self.plot_outputs =  ['Transmission (S0)', 'S1', 'S2', 'S3', 'Ix, Iy', 'I+45 / I-45', 'Ircp / Ilcp', 'Alpha Plus/Minus/Z']
		self.plot_ylabels = ['Transmission, $S_0$', '$S_1$', '$S_2$', '$S_3$', '$I_x$, $I_y$', '$I_{+45}$, $I_{-45}$', r'$I_{RCP}, I_{LCP}$', r'$\alpha^{+,-,Z}$']
		self.plot_output_indices = [OutputPlotTypes.index(po) for po in self.plot_outputs]
		
		self.xrange = [detuning_defaults[0],detuning_defaults[1]] # detuning range, in GHz
		self.npoints = detuning_defaults[2] # number of detuning points to calculate
		
		# default data for plots - blank lists
		self.x_array = np.linspace(self.xrange[0],self.xrange[1],self.npoints)
		self.y_arrays = [None]*len(self.plot_outputs)
		self.x_expt_arrays = [None]*len(self.plot_outputs)
		self.y_expt_arrays = [None]*len(self.plot_outputs)
		self.x_fit_array, self.y_fit_array = [], []
		
		# x and y-limits
		self.xlim = None
		self.ylim = [None]*len(OutputPlotTypes)
		
		# plot data visible when set to True - default = view transmission (S0)
		self.display_theory_curves = [False]*len(OutputPlotTypes)
		self.display_theory_curves[0] = True
		self.display_expt_curves = [False]*len(OutputPlotTypes)
		
		# live plotting
		self.LiveEventsBound = False

		# residual plot settings
		self.normalised_residuals =  False
		self.residual_histogram = False
		
		#fit bounds
		self.detuning_fit_bounds = [None, None]
		
		# Theory curves are calculated with same detuning axis as experimental data
		self.UseExpDetuning = False
		
		# autoscale
		self.Autoscale = True
	
	def _init_menus(self):
		""" Initialise menu bar items """
		
		# Create menuBar object
		menuBar = wx.MenuBar()
		
		# File
		fileMenu = wx.Menu()
		fM_open = fileMenu.Append(wx.ID_OPEN, "&Open Experimental Data (csv)\tCtrl+O", "Open file for plotting and/or fitting.")
		self.Bind(wx.EVT_MENU, self.OnFileOpen, fM_open)
		fileMenu.AppendSeparator()
		fm_saveplot = fileMenu.Append(wx.ID_ANY, "Save Plot as Image", "Save Data")
		self.Bind(wx.EVT_MENU, self.OnSaveFig, fm_saveplot)
		fm_savecsv = fileMenu.Append(wx.ID_SAVE, "E&xport CSV Data\tCtrl+S", "Save CSV Data")
		self.Bind(wx.EVT_MENU, self.OnSaveCSVData, fm_savecsv)
		#fm_saveconfig = fileMenu.Append(wx.ID_ANY, "Save Current Configuration", "Save Config")
		#self.Bind(wx.EVT_MENU, self.OnSaveConfig, fm_saveconfig)
		fileMenu.AppendSeparator()
		fM_exit = fileMenu.Append(wx.ID_EXIT, "E&xit\tCtrl+Q", "Close window and exit program.")
		self.Bind(wx.EVT_MENU, self.OnExit, fM_exit)
		#
		
		# Edit
		editMenu = wx.Menu()
		eM_CopyTtoF = editMenu.Append(wx.ID_ANY, "Copy Parameters: &Theory to Fit", "Copy - T to F")
		eM_CopyFtoT = editMenu.Append(wx.ID_ANY, "Copy Parameters: &Fit to Theory", "Copy - F to T")
		self.Bind(wx.EVT_MENU, self.OnCopyParamsTtoF, eM_CopyTtoF)
		self.Bind(wx.EVT_MENU, self.OnCopyParamsFtoT, eM_CopyFtoT)
		
		self.eM_UseExpDetuning = editMenu.Append(wx.ID_ANY,"&Use Experimental Detuning Axis", "Use Exp Axis",kind=wx.ITEM_CHECK)
		self.Bind(wx.EVT_MENU, self.OnUseExpDetuning, self.eM_UseExpDetuning)
		
		# About
		aboutMenu = wx.Menu()
		aM_aboutthis = aboutMenu.Append(wx.ID_ABOUT, "&About this program", "About this program.")
		self.Bind(wx.EVT_MENU, self.OnAboutThis, aM_aboutthis)
		aM_aboutelecsus = aboutMenu.Append(wx.ID_ANY, "About &ElecSus", "About ElecSus")
		self.Bind(wx.EVT_MENU, self.OnAboutElecSus, aM_aboutelecsus)

		# View
		viewMenu = wx.Menu()
		vM_liveplot = viewMenu.Append(wx.ID_ANY, "&Live Plot", "Update Plot in real-time",kind=wx.ITEM_CHECK)
		self.Bind(wx.EVT_MENU, self.OnLivePlotting, vM_liveplot)
		vM_autoscale = viewMenu.Append(wx.ID_ANY, "&Autoscale when updating axes", "Autoscale",kind=wx.ITEM_CHECK)
		vM_autoscale.Check(True)
		self.Bind(wx.EVT_MENU, self.OnAutoscale, vM_autoscale)
		
		# Select plot types to display
		
		# Theory Plot
		theoryplotMenu = wx.Menu()
		tpM_plotholdon = theoryplotMenu.Append(wx.ID_ANY, 
			"&Hold data on plot update", 
			"Select whether to hold or clear current plot data on updating the figure", 
			kind=wx.ITEM_CHECK)
		tpM_plotholdon.Check(self.hold)
		self.Bind(wx.EVT_MENU, self.OnPlotHold, tpM_plotholdon)
		#tpM_clearplot = theoryplotMenu.Append(wx.ID_ANY, "&Clear current plot data", "Clear current plot data on all axes")
		#self.Bind(wx.EVT_MENU, self.OnClearPlot, tpM_clearplot)
		
		tpM_grid = theoryplotMenu.Append(wx.ID_ANY, "&Grid on axes", "Grid", kind=wx.ITEM_CHECK)
		self.Bind(wx.EVT_MENU, self.OnGridToggleMain, tpM_grid)

		 
		'''
		## Plot selection on menus - superceded by panel buttons ##
		self.showTplotsSubMenu = wx.Menu()
		id_S0 = 2000
		id_S1 = 2001
		id_S2 = 2002
		id_S3 = 2003
		id_IxIy = 2004
		id_alpha = 2005
		id_n = 2006
		id_phi = 2007
		show_S0 = self.showTplotsSubMenu.AppendCheckItem(id_S0,"&Transmission (S0)")
		show_S1 = self.showTplotsSubMenu.AppendCheckItem(id_S1,"S&1")
		show_S2 = self.showTplotsSubMenu.AppendCheckItem(id_S2,"S&2")
		show_S3 = self.showTplotsSubMenu.AppendCheckItem(id_S3,"S&3")
		show_IxIy = self.showTplotsSubMenu.AppendCheckItem(id_IxIy,"&Ix/Iy")
		show_alpha = self.showTplotsSubMenu.AppendCheckItem(id_alpha,"&Alpha +/-")
		show_n = self.showTplotsSubMenu.AppendCheckItem(id_n,"&Refractive Index +/-")
		show_ng = self.showTplotsSubMenu.AppendCheckItem(id_n,"&Group Index +/-")
		show_phi = self.showTplotsSubMenu.AppendCheckItem(id_phi,"&Phi (Rotation Angle)")
		# bind event to check box selections
		for checkitem in [show_S0, show_S1, show_S2, show_S3, show_IxIy, show_alpha, show_n, show_ng, show_phi]:
			self.Bind(wx.EVT_MENU, self.OnShowTplots, checkitem)
		# add to parent menu
		tpM_showTplots = theoryplotMenu.AppendMenu(wx.ID_ANY,"&Theory Curves to Show", self.showTplotsSubMenu)
		'''
		
		self.showEplotsSubMenu = wx.Menu()
		id_S0 = 3000
		id_S1 = 3001
		id_S2 = 3002
		id_S3 = 3003
		id_IxIy = 3004
		id_alpha = 3005
		id_n = 3006
		id_phi = 3007
		show_S0 = self.showEplotsSubMenu.AppendCheckItem(id_S0,"&Transmission (S0)")
		show_S1 = self.showEplotsSubMenu.AppendCheckItem(id_S1,"S&1")
		show_S2 = self.showEplotsSubMenu.AppendCheckItem(id_S2,"S&2")
		show_S3 = self.showEplotsSubMenu.AppendCheckItem(id_S3,"S&3")
		show_IxIy = self.showEplotsSubMenu.AppendCheckItem(id_IxIy,"&Ix/Iy")
		show_alpha = self.showEplotsSubMenu.AppendCheckItem(id_alpha,"&Alpha +/-")
		show_n = self.showEplotsSubMenu.AppendCheckItem(id_n,"&Refractive Index +/-")
		show_ng = self.showEplotsSubMenu.AppendCheckItem(id_n,"&Group Index +/-")
		show_phi = self.showEplotsSubMenu.AppendCheckItem(id_phi,"&Phi (Rotation Angle)")
		# bind event to check box selections
		for checkitem in [show_S0, show_S1, show_S2, show_S3, show_IxIy, show_alpha, show_n, show_ng, show_phi]:
			self.Bind(wx.EVT_MENU, self.OnShowEplots, checkitem)
		# add to parent menu
		tpM_showEplots = theoryplotMenu.AppendMenu(wx.ID_ANY,"&Experimental Curves to Show", self.showEplotsSubMenu)
		

		
		# Experimental Plot
		exptplotMenu = wx.Menu()
		epM_plotholdon = exptplotMenu.Append(wx.ID_ANY, "&Hold data on plot update", "Select whether to hold or clear current plot data on updating the figure", kind=wx.ITEM_CHECK)
		#epM_clearplot = exptplotMenu.Append(wx.ID_ANY, "&Clear current plot data", "Clear current plot data on all axes")
		#self.Bind(wx.EVT_MENU, self.OnClearPlot,epM_clearplot)
		
		epM_grid = exptplotMenu.Append(wx.ID_ANY, "&Grid on axes", "Grid", kind=wx.ITEM_CHECK)
		self.Bind(wx.EVT_MENU, self.OnGridToggleRes, epM_grid)

		epM_reshist = exptplotMenu.Append(wx.ID_ANY, "Show &Histogram of Residuals", "Res Hist", kind=wx.ITEM_CHECK)
		self.Bind(wx.EVT_MENU, self.OnResHist, epM_reshist)
		
	
		# Fit
		fitMenu = wx.Menu()
		#fitM_dummy = fitMenu.Append(wx.ID_ANY, "&Dummy", "Dummy")
		
		'''
		# Select fitting type
		self.fittypeSubMenu = wx.Menu()
		#define some id tags that we will use when items are selected
		id_ML = 1001
		id_RR = 1002
		id_SA = 1003
		self.fit_ids = [id_ML,id_RR,id_SA]
		fit_ML = self.fittypeSubMenu.AppendRadioItem(id_ML, "&Marquardt-Levenberg", "Use ML Fitting")
		fit_RR = self.fittypeSubMenu.AppendRadioItem(id_RR, "&Random Restart", "Use RR Fitting")
		fit_SA = self.fittypeSubMenu.AppendRadioItem(id_SA, "&Simulated Annealing", "Use SA Fitting")
		
		#Bind action to each item
		self.Bind(wx.EVT_MENU, self.OnFitTypeChangeMenu, fit_ML)
		self.Bind(wx.EVT_MENU, self.OnFitTypeChangeMenu, fit_RR)
		self.Bind(wx.EVT_MENU, self.OnFitTypeChangeMenu, fit_SA)
		
		# by default, select ML fitting
		self.fittypeSubMenu.Check(id_RR, True)
		self.OnFitTypeChangeMenu(1)

		# Add sub-menu to fit menu
		fitM_fittype = fitMenu.AppendMenu(wx.ID_ANY, "Fit &Type", self.fittypeSubMenu)
		'''

		fitM_dataproc = fitMenu.Append(wx.ID_ANY, "&Data Processing...", "Data Processing Options")
		self.Bind(wx.EVT_MENU, self.OnDataProcessing, fitM_dataproc)
		
		fitM_fitbounds = fitMenu.Append(wx.ID_ANY, "&Set Fit Bounds", "Set detuning bounds for fitting")
		self.Bind(wx.EVT_MENU, self.OnFitBounds, fitM_fitbounds)

		#fitM_advanced = fitMenu.Append(wx.ID_ANY, "&Advanced Fit Options...", "Advanced Fit Options")
		#self.Bind(wx.EVT_MENU, self.OnAdvancedOptions, fitM_advanced)
		
		fitM_warnings = fitMenu.Append(wx.ID_ANY, "&Warn about fit settings", "Warn about possible bad fit settings", kind=wx.ITEM_CHECK)
		
		self.Bind(wx.EVT_MENU, self.OnFitWarnings, fitM_warnings)
		fitM_warnings.Check(True)
		
			
		#
		# ... other menu items to add...
		# 
		# Residual analysis - fit gaussian to residuals etc
		#		-- Use normalised/raw residuals (requires importing data with errorbars.. to do..)
		# Main plot - set axes limits
		#
		
		# Add menu items to menu bar
		menuBar.Append(fileMenu, "&File")
		menuBar.Append(editMenu, "&Edit")
		menuBar.Append(viewMenu, "&View")
		menuBar.Append(theoryplotMenu, "&Main Plot")
		menuBar.Append(exptplotMenu, "&Residuals Plot")
		menuBar.Append(fitMenu, "F&it")
		menuBar.Append(aboutMenu, "&About")
	
		# Add Menu Bar to the main panel
		self.SetMenuBar(menuBar)
		
	def _init_panels(self):
		""" Initialise panel with matplotlib window, buttons, text boxes etc. Doesn't really need to be in a separate function, but makes it easier to find stuff... """
		
		# Sizer constructs are used for placement of all GUI elements. 
		# This makes the whole window scalable in a predictable way.
		
		## Create plot part of the window
		
		## create plot in a notebook-style for adding more tabs later on
		PlotTabs = wx.Notebook(self.panel)
		#PlotTabs = aui.AuiNotebook(self.panel)
		
		# The plot panels
		self.T_Panel = PlotToolPanel(PlotTabs,self,1)
		self.E_Panel = PlotToolPanel(PlotTabs,self,2)
		
		# Text tabs - for stdout, stderr messages and fitting information generated after fitting data (optimum parameters etc)
		self.StatusPanel = StatusPanel(PlotTabs,self,'Status Information')
		self.FitInformation = StatusPanel(PlotTabs,self,'Fitting Information')
		self.ErrorPanel = StatusPanel(PlotTabs,self, 'Error Information')
		self.ErrorPanel.write("If this is the only thing displayed here, the program is working well...\n\n")
		
		# Add all tabs to the tab bar
		PlotTabs.AddPage(self.T_Panel, "Main Plot")
		PlotTabs.AddPage(self.E_Panel, "Fit / Residuals Plot")
		PlotTabs.AddPage(self.StatusPanel, "Status Panel")
		PlotTabs.AddPage(self.FitInformation, "Fitting Information")
		PlotTabs.AddPage(self.ErrorPanel, "Error Information")
		
		# Add the Tab bar to the main panel sizer
		plot_sizer = wx.BoxSizer(wx.VERTICAL)
		plot_sizer.Add(PlotTabs,1, wx.EXPAND)
		
		## Create button part of the window
		
		# elecsus and JQC logos at the top
		#elecsuslogo = wx.Image('images/elecsus.ico',wx.BITMAP_TYPE_ANY)
		#elecsuslogo.Rescale(108/2,138/2)
		#elecsus_bmp = wx.StaticBitmap(self.panel,wx.ID_ANY,wx.BitmapFromImage(elecsuslogo),size=(108/2,-1))
		jqclogo = wx.Image(os.path.join(elecsus_dir,'images/jqc-logo.png'),wx.BITMAP_TYPE_ANY)
		#jqc_bmp = wx.StaticText(self.panel,wx.ID_ANY,"image") 
		jqc_bmp = wx.StaticBitmap(self.panel,wx.ID_ANY,wx.BitmapFromImage(jqclogo),size=(191,-1))
		
		spacer = 167
		image_sizer = wx.BoxSizer(wx.HORIZONTAL)
		image_sizer.Add((10,-1),1,wx.EXPAND)
		image_sizer.Add((spacer,-1),0,wx.EXPAND)
		#image_sizer.Add(elecsus_bmp,0,wx.EXPAND)
		#image_sizer.Add((10,-1),0,wx.EXPAND)
		image_sizer.Add(jqc_bmp,0,wx.EXPAND)
		image_sizer.Add((spacer,-1),0,wx.EXPAND)
		image_sizer.Add((10,-1),1,wx.EXPAND)
		
		## Menus are tabbed into theory and fitting sections
		## use wx.Notebook for this
		TabPanel = wx.Notebook(self.panel)

		
		# add tabs
		
		self.ThyPanel = wx.Panel(TabPanel)
		self.FitPanel = wx.Panel(TabPanel)

		self.ThyOptions = OptionsPanel(self.ThyPanel,self,(600,-1),'Theory')
		self.FitOptions = OptionsPanel(self.FitPanel,self,(600,-1),'Fit')

		#Run Fit Button
		RunFitButton = wx.Button(self.FitPanel,wx.ID_ANY, 'Run Fit', size=(140,1.5*BtnSize))
		self.Bind(wx.EVT_BUTTON, self.OnFitButton, RunFitButton)
		# Calculate spectrum
		ComputeButton = wx.Button(self.ThyPanel,wx.ID_ANY, 'Compute Spectrum', size=(140,1.5*BtnSize))
		self.Bind(wx.EVT_BUTTON, self.OnComputeButton, ComputeButton)
		# Common options to both experiment and theory
		ImportButtonThy = wx.Button(self.ThyPanel,wx.ID_OPEN,label="Import Data", size=(140,1.5*BtnSize))
		self.Bind(wx.EVT_BUTTON,self.OnFileOpen,ImportButtonThy)
		ImportButtonFit = wx.Button(self.FitPanel,wx.ID_OPEN,label="Import Data", size=(140,1.5*BtnSize))
		self.Bind(wx.EVT_BUTTON,self.OnFileOpen,ImportButtonFit)



		ThyBtn_sizer = wx.BoxSizer(wx.HORIZONTAL)
		ThyBtn_sizer.Add((30,-1),1,wx.EXPAND)
		ThyBtn_sizer.Add(ImportButtonThy,0,wx.EXPAND)
		ThyBtn_sizer.Add((30,-1),0,wx.EXPAND)
		ThyBtn_sizer.Add(ComputeButton,0,wx.EXPAND)
		ThyBtn_sizer.Add((30,-1),1,wx.EXPAND)
		
		FitBtn_sizer = wx.BoxSizer(wx.HORIZONTAL)
		FitBtn_sizer.Add((30,-1),1,wx.EXPAND)
		FitBtn_sizer.Add(ImportButtonFit,0,wx.EXPAND)
		FitBtn_sizer.Add((30,-1),0,wx.EXPAND)
		FitBtn_sizer.Add(RunFitButton,0,wx.EXPAND)
		FitBtn_sizer.Add((30,-1),1,wx.EXPAND)

		self.ThySizer = wx.BoxSizer(wx.VERTICAL)
		self.ThySizer.Add(self.ThyOptions,1,wx.EXPAND)
		self.ThySizer.Add((-1,10),0,wx.EXPAND)
		self.ThySizer.Add(ThyBtn_sizer,0,wx.EXPAND)
		
		self.FitSizer = wx.BoxSizer(wx.VERTICAL)
		self.FitSizer.Add(self.FitOptions,1,wx.EXPAND)
		self.FitSizer.Add((-1,10),0,wx.EXPAND)
		self.FitSizer.Add(FitBtn_sizer,0,wx.EXPAND)
		
		self.ThyPanel.SetSizer(self.ThySizer)
		self.FitPanel.SetSizer(self.FitSizer)
		
		TabPanel.AddPage(self.ThyPanel, "Theory Settings")
		TabPanel.AddPage(self.FitPanel, "Fit Settings")
		
		tab_sizer = wx.BoxSizer(wx.HORIZONTAL)
		#tab_sizer.Add((40,-1),0,wx.EXPAND)
		tab_sizer.Add(TabPanel, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, border=5)
		#tab_sizer.Add((40,-1),0,wx.EXPAND)
		
		
		
		## Plot selection buttons
		plot_selection_text = wx.StaticText(self.panel,wx.ID_ANY,"Data types to show:")

		Tplot_selection_list = wx.Button(self.panel,wx.ID_ANY, 'Theory Plots', size=(100,BtnSize))
		self.Bind(wx.EVT_BUTTON, self.OnTheoryPlotSelection, Tplot_selection_list)

		Eplot_selection_list = wx.Button(self.panel,wx.ID_ANY, 'Fit Plots', size=(100,BtnSize))
		self.Bind(wx.EVT_BUTTON, self.OnFitPlotSelection, Eplot_selection_list)
		
		#self.Bind(EVT_)

		#for checkitem in [show_S0, show_S1, show_S2, show_S3, show_IxIy, show_alpha, show_n]:
		#	self.Bind(wx.EVT_MENU, self.OnShowTplots, checkitem)
		## add to parent menu
		#tpM_showTplots = theoryplotMenu.AppendMenu(wx.ID_ANY,"&Theory Curves to Show", self.showTplotsSubMenu)


		plot_selection_sizer = wx.BoxSizer(wx.HORIZONTAL)
		plot_selection_sizer.Add((10,-1),0,wx.EXPAND)
		plot_selection_sizer.Add(plot_selection_text,0, wx.ALIGN_CENTER_VERTICAL)
		plot_selection_sizer.Add((10,-1),1,wx.EXPAND)
		plot_selection_sizer.Add(Tplot_selection_list,0, wx.EXPAND)
		plot_selection_sizer.Add((20,-1),0,wx.EXPAND)
		plot_selection_sizer.Add(Eplot_selection_list,0, wx.EXPAND)
		plot_selection_sizer.Add((20,-1),1,wx.EXPAND)


		## Create sizer for right-hand side of panel
		button_sizer = wx.BoxSizer(wx.VERTICAL)
		button_sizer.Add((-1,10),0,wx.EXPAND)
		button_sizer.Add(image_sizer,0,wx.EXPAND)
		button_sizer.Add((-1,5),0,wx.EXPAND)
		button_sizer.Add(plot_selection_sizer,0,wx.EXPAND)#,0,wx.EXPAND)
		button_sizer.Add((-1,5),0,wx.EXPAND)
		button_sizer.Add(tab_sizer,1,wx.EXPAND)
		button_sizer.Add((-1,5),0,wx.EXPAND)
		
		
		## Put plot part and button part together
		main_sizer = wx.BoxSizer(wx.HORIZONTAL)
		main_sizer.Add(plot_sizer,1,wx.EXPAND)
		main_sizer.Add(wx.StaticLine(self.panel,-1,size=(1,-1),style=wx.LI_VERTICAL),0,wx.EXPAND)
		main_sizer.Add(button_sizer,0,wx.EXPAND)

		self.panel.SetSizerAndFit(main_sizer)
		self.panel.Layout()
		self.SetSize((1300,900))
		self.SetSize((1300,850))
		
		#wx.CallAfter(self.canvases[0].SetSize,(1200,900))
		
		
#		
## Actions for events
#
	
##
#### General Actions - Close window, Save Data etc...
##
				
	def _draw_fig(self,fig,canvas):
		""" shortcut method for redrawing figure and rearranging subplots to fill space """
		try:
			fig.tight_layout()
		except:
			pass
	
		for can in self.canvases:
			can.draw()
		#self.SendSizeEvent()
		
	def Call_ElecSus(self,calc_or_fit):
		""" Call elecsus with passed arguments - single calculation or fit, depending on option """
		
		#get parameter list, xrange from either theory or fit panel
		if calc_or_fit == 'Theory':
			panel = self.ThyOptions
			if not self.UseExpDetuning:
				# get detuning range from GUI controls
				xmin,xmax,npts = [input.GetValue() for input in panel.DetuningCtrl]
				self.x_array = np.linspace(xmin,xmax,npts)
			else:
				# get detuning from experimental data
				self.x_array = self.x_fit_array
		elif calc_or_fit == 'Fit':
			panel = self.FitOptions
			
		
		## Dictionary of parameters, bools (floating or not) and bounds ((min,max) pairs)
		self.params_dict = {}
		self.params_dict_bools = {}
		self.params_dict_bounds = {}
		
		# get parameter list
		const_params = panel.fixed_paramlist_inputs
		const_param_labels = ['Elem','Dline','Constrain']
		self.params_dict['Elem'] = const_params[0].GetStringSelection()
		self.params_dict['Dline'] = const_params[1].GetStringSelection()
		self.params_dict['Constrain'] = const_params[2].IsChecked()		
		
		self.main_param_labels = ['T','lcell','shift','GammaBuf','DoppTemp','rb85frac','K40frac','K41frac'] ## strings of dictionary entries as used by elecsus
		self.main_params = [input.GetValue() for input in panel.main_paramlist_inputs]
		for label, value in zip(self.main_param_labels,self.main_params):
			self.params_dict[label] = value
			
		# modfiy cell length - convert from mm to m
		self.params_dict['lcell'] *= 1e-3
		
		self.magnet_param_labels = ['Bfield','Btheta','Bphi']
		self.magnet_params = [input.GetValue() for input in panel.magnet_paramlist_inputs]
		for label, value in zip(self.magnet_param_labels,self.magnet_params):
			self.params_dict[label] = value
		# convert from degree to radians
		self.params_dict['Btheta'] *= np.pi/180
		self.params_dict['Bphi'] *= np.pi/180
		
		self.polarisation_param_labels = ['E_x','E_y','E_phase']
		self.polarisation_params = [input.GetValue() for input in panel.pol_control_inputs[1:]] # theta-0 is not needed in general
		for label, value in zip(self.polarisation_param_labels,self.polarisation_params):
			self.params_dict[label] = value
		
		# get Efield vector
		self.params_dict['E_phase'] *= np.pi/180
		E_vec = np.array([self.params_dict['E_x'],self.params_dict['E_y']*np.exp(1.j*self.params_dict['E_phase']),0])
		print(self.params_dict)
		
		
		if calc_or_fit=='Fit':
			# Append floating parameters to params_dict_bools
			for i,checkbox in enumerate(panel.main_paramlist_bools):
				if checkbox.IsChecked():
					self.params_dict_bools[self.main_param_labels[i]] = True
			
			for i,checkbox in enumerate(panel.magnet_paramlist_bools):
				if checkbox.IsChecked():
					self.params_dict_bools[self.magnet_param_labels[i]] = True
		
			# Append parameter bounds to params_dict_bounds
			for i,checkbox in enumerate(panel.main_paramlist_usebounds):
				if checkbox.IsChecked():
					minn = self.FitOptions.main_paramlist_mins[i].GetValue()
					maxx = self.FitOptions.main_paramlist_maxs[i].GetValue() 
					self.params_dict_bounds[self.main_param_labels[i]] = (minn,maxx)
			
			for i,checkbox in enumerate(panel.magnet_paramlist_usebounds):
				if checkbox.IsChecked():
					minn = self.FitOptions.magnet_paramlist_mins[i].GetValue()
					maxx = self.FitOptions.magnet_paramlist_maxs[i].GetValue() 
					self.params_dict_bounds[self.magnet_param_labels[i]] = (minn,maxx)
		
			## POLARISATION FIT OPTIONS TO ADD
			
			if self.FitOptions.fit_polarisation_checkbox.IsChecked():
				print('Fitting Polarisation...')
				
				self.params_dict_bools['E_x'] = True
				self.params_dict_bools['E_y'] = True

				self.params_dict_bounds['E_x'] = [-1.,1.]
				self.params_dict_bounds['E_y'] = [-1.,1.]

				if not self.FitOptions.constrain_linear_checkbox.IsChecked():
					print('Fitting Pol. Phase...')
					self.params_dict_bools['E_phase'] = True
				#	self.params_dict_bounds['E_phase'] = (0.,np.pi)
				else:
					self.params_dict_bools['E_phase'] = False
				
			print(self.params_dict_bools)
			
			
		if calc_or_fit == 'Theory':
			print('\n\n')
			print(time.ctime())
			print('Calling ElecSus for single calculation with parameters:')
			print(self.params_dict)
			
			spectrum_data = calculate(self.x_array*1e3,E_vec,self.params_dict,
									outputs = ['S0','S1','S2','S3','Ix','Iy','I_P45', 'I_M45', 'Ir', 'Il', 'alphaPlus', 'alphaMinus', 'alphaZ'])
			self.y_arrays[0:4] = spectrum_data[0:4] #S0,S1,S2,S3
			self.y_arrays[4] = [spectrum_data[4],spectrum_data[5]] # Ix,Iy
			self.y_arrays[5] = [spectrum_data[6],spectrum_data[7]] # I+45,I-45
			self.y_arrays[6] = [spectrum_data[8],spectrum_data[9]] # I_RCP, I_LCP
			self.y_arrays[7] = [spectrum_data[10],spectrum_data[11],spectrum_data[12]] # alpha +/-/z
			
			#print self.y_arrays
			#for i, array in enumerate(self.y_arrays):
			#	print i, type(array)
			
			self.GetCurrentAxesLimits(self.figs[0])
			self.OnCreateAxes(self.figs[0],self.canvases[0],clear_current=True)
			
		elif calc_or_fit == 'Fit':
			### Use another thread for running elecsus fitting so the main panel doesn't stop responding
			
			#Check if fit is already running, only proceed if not fitting already
			if not self.already_fitting:
				
				#Verbose output
				print('\n\n')
				print(time.ctime())
				print('Calling ElecSus for fitting data with initial parameters:')
				for key in self.params_dict:
					print(key, self.params_dict[key])
				print('\nVarying the following parameters:')
				for key in self.params_dict_bools:
					print(key)
				print('\nSubject to the following boundaries:')
				for key in self.params_dict_bounds:
					val = self.params_dict_bounds[key]
					print(key, val[0], 'to ', val[1]) 
				
				
				## log time, data set to be fitted, initial parameters on the 'Fit Info' notebook:
				font1 = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL)#, False, u'Consolas')
				self.FitInformation.StatusTextBox.SetFont(font1)
				self.FitInformation.write('Fit started at:'.ljust(25)+time.ctime()+'\n')
				self.FitInformation.write('Data set to be fitted:'.ljust(25)+os.path.join(self.dirname,self.filename)+'\n')
				self.FitInformation.write('Experimental Data Type:'.ljust(25)+self.fit_datatype+'\n')
				self.FitInformation.write('Initial parameters (Floated):\n')
				for key in self.params_dict:
					val = self.params_dict[key]
					self.FitInformation.write('\t'+key.ljust(30)+str(val).ljust(10))
					if key in self.params_dict_bools:
						self.FitInformation.write(' (Floating')
						if key in self.params_dict_bounds:
							self.FitInformation.write(', bounded between '+str(self.params_dict_bounds[key][0])+' and'+str(self.params_dict_bounds[key][1])+' )')
						else:
							self.FitInformation.write(', unbounded)')
							
				self.FitInformation.write('Using algorithm: '+self.fit_algorithm)

				
				##
				#### Run Fitting algorithm
				##
			
				#initialise thread for fitting
				fitThread = FittingThread(self)
				
				# start thread - thread grabs all information from main window, so start() is all that's needed
				fitThread.start()
				# After the fit completes, the thread triggers an EVT_FIT_COMPLETE which 
				# calls (in the main thread) the OnFitCompleted() method
				
				# set flag to only allow one fit to run at any time
				self.already_fitting = True
				
				# show dialog box with constantly cycling status bar
				self.fitting_dlg = wx.ProgressDialog("Fit in progress","Fitting in progress. Please wait for fitting to complete. This may take a while.",maximum=100,style=wx.PD_ELAPSED_TIME)
				self.fitting_dlg.Show(True)
				fitProgressThread = ProgressThread(self)
				fitProgressThread.start()
				
			else:
				dlg = wx.MessageDialog(self,"Fit already in progress. Only one fit may be run at a time.", "Patience required...",style=wx.OK|wx.ICON_ERROR)
				dlg.ShowModal()
				## dialog box for already fitting...
				
	def CopyParams(self,order):
		""" Copy current parameter values, either between theory and fit tabs, or other way around """
		F_params = self.FitOptions.fixed_paramlist_inputs + self.FitOptions.main_paramlist_inputs + self.FitOptions.magnet_paramlist_inputs + self.FitOptions.pol_control_inputs

		T_params = self.ThyOptions.fixed_paramlist_inputs + self.ThyOptions.main_paramlist_inputs + self.ThyOptions.magnet_paramlist_inputs + self.ThyOptions.pol_control_inputs
		
		for fp, tp in zip(F_params,T_params):
			if order==1:
				# Theory to Fit
				fp.SetValue(tp.GetValue())
			elif order==2:
				tp.SetValue(fp.GetValue())
				
		print('Parameters Copied')
			
	def GetCurrentAxesLimits(self,fig):
		""" Get current axes limits (doesn't return, but updates self.<> variables"""
		displayed_axes = [i|j for i,j in \
			zip(self.display_theory_curves,self.display_expt_curves)]
		n_axes = displayed_axes.count(True)
		
		# get current axes limits
		if len(fig.axes) > 0:
			self.xlim = fig.axes[0].get_xlim()
			j = 0
			for i,displayed_ax in enumerate(displayed_axes):
				if displayed_ax:
					self.ylim[i] = fig.axes[j].get_ylim()
					j += 1
		

	def OnAboutElecSus(self,event):
		""" Show a message box about ElecSus """
		dlg = wx.MessageDialog(self, NOTICE.noticestring, "About", wx.OK)
		if dlg.ShowModal() == wx.ID_OK:
			dlg.Destroy()

	def OnAboutThis(self,event):
		""" Show a message box about the program """
		dlg = wx.MessageDialog(self, "ElecSus GUI\n\nA graphical interface for ElecSus, a program to calculate the weak-probe electric susceptibility of alkali atoms.\n\n Written in python using wxPython and matplotlib.\n\nCopyright 2016/7 James Keaveney and co-authors", "About", wx.OK)
		if dlg.ShowModal() == wx.ID_OK:
			dlg.Destroy()
					
	def OnAdvancedOptions(self,event):
		""" Open the dialog box for adjusting advanced fit options """
		dlg = AdvancedFitOptions(self,"Advanced Fit Options",wx.ID_ANY)
		
		# Show() rather than ShowModal() - doesn't halt program flow
		if dlg.ShowModal() == wx.ID_OK:
			self.advanced_fitoptions = dlg.return_all_options()
			print(self.advanced_fitoptions)

	def OnAutoscale(self,event):
		""" Set axes auto-scaling """
		self.Autoscale = bool(event.IsChecked())

	def OnComputeButton(self,event):
		""" Call elecsus to compute spectrum """
		self.Call_ElecSus('Theory')

	def OnCopyParamsTtoF(self,event):
		""" Copy parameters from theory tab to fitting tab """
		self.CopyParams(1)

	def OnCopyParamsFtoT(self,event):
		""" Copy parameters from fitting tab to theory tab """
		self.CopyParams(2)

	def OnCreateAxes(self,fig,canvas,clear_current=True):
		""" (Re)Create as many sets of axes in the main figure as are needed, and label them all """

		# calculate how many axes should be displayed - count if any elements of display_expt/theory_curves are true
		displayed_axes = [i|j for i,j in \
			zip(self.display_theory_curves,self.display_expt_curves)]
		n_axes = displayed_axes.count(True)
		
		#if not self.Autoscale:
		#	# get current axes limits
		#	self.xlim = fig.axes[0].get_xlim()
		#	for i,displayed_ax in enumerate(displayed_axes):
		#		if displayed_ax:
		#			self.ylim[i] = 0
			
		# clear figure and start again from nothing if number of axes has changed, or if hold is off
		if clear_current:
			fig.clf()
		if n_axes != len(fig.axes):
			fig.clf()
		
		# create bare axes, all equal sizes
		if n_axes == 0:
			n_axes = 1
		
		fig.add_subplot(n_axes,1,1)
		i=0
		for i in range(1,n_axes):
			fig.add_subplot(n_axes,1,i+1,sharex=fig.axes[0])
		
		
		# Testing:
		#print self.display_expt_curves
		#print self.y_expt_arrays
		
		## PLOT BASED ON BOOL DISPLAY_x_CURVE
		i=0
		for displayT,displayE,yt,xe,ye,ylabel in zip(self.display_theory_curves,self.display_expt_curves,\
								self.y_arrays,self.x_expt_arrays,self.y_expt_arrays,self.plot_ylabels):
			try:
				#print i
				ax = fig.axes[i]
			except:
				pass
			if displayE and ye is not None:
				#if isinstance(ye, (list,tuple)):
				#	for xi,yi in zip(xe,ye):
				#		ax.plot(xi,yi,color=d_grey)
				#else:
				ax.plot(xe,ye,color=d_olive)
			if displayT and yt is not None:
				if isinstance(yt, (list,tuple)):
					for yi in yt:
						ax.plot(self.x_array,yi)
				else:
					ax.plot(self.x_array,yt)
			if displayT or displayE:
				ax.set_ylabel(ylabel)
				i += 1
			
		# set x axis label and rescale all axes to fit data
		fig.axes[-1].set_xlabel('Detuning (GHz)')
		if self.Autoscale:
			for ax in fig.axes:
				ax.autoscale_view(tight=True)
		else:
			fig.axes[0].set_xlim(self.xlim)
			j = 0
			for i,displayed_ax in enumerate(displayed_axes):
				if displayed_ax:
					fig.axes[j].set_ylim(self.ylim[i])
					j += 1

		#fig.axes[-1].set_xlim(self.xrange)
		
		# remove the rest of the x tick labels from all but the bottom panel
		for ax in fig.axes[:-1]:
			plt.setp(ax.get_xticklabels(),visible=False)
		
		#print 'Created '+str(n_axes)+' axes'
		
		# update the plot window
		self._draw_fig(fig,canvas)
	
	def OnCreateResidualPlot(self,fig,canvas):
		"""
		Create a plot with main data, residuals and (optionally) histogram of residuals, using
		subplot2grid in matplotlib 
		"""
		
		fig.clf()
		
		print('Debugging...')
		print(self.fit_datatype)
		print(self.y_optimised)
		
		#normalised_residuals = False
		if self.normalised_residuals:
			### not done yet! -- requires error bars in imported data
			residuals = 100*(self.y_fit_array-self.y_optimised)
		else:
			residuals = 100*(self.y_fit_array-self.y_optimised)
		
		fig = plt.figure(2)
		yy = 4
		xx = 6
		if self.residual_histogram:
			ax_main = plt.subplot2grid((yy,xx),(0,0),colspan=xx-1,rowspan=yy-1)
			ax_residual = plt.subplot2grid((yy,xx),(yy-1,0),colspan=xx-1,sharex=ax_main)
			ax_hist = plt.subplot2grid((yy,xx), (yy-1,xx-1), sharey=ax_residual)

			plt.setp(ax_hist.get_yticklabels(),visible=False)
			ax_hist.set_xticklabels([])

		else:
			ax_main = plt.subplot2grid((yy,xx),(0,0),colspan=xx,rowspan=yy-1)
			ax_residual = plt.subplot2grid((yy,xx),(yy-1,0),colspan=xx,sharex=ax_main)
			
		plt.setp(ax_main.get_xticklabels(),visible=False)
		
		ax_residual.set_xlabel('Detuning (GHz)')
		ax_residual.set_ylabel('Residuals (%)')

		ax_main.set_ylabel(self.expt_type)
		
		ax_main.plot(self.x_fit_array,self.y_fit_array,color=d_olive)
		print(len(self.x_fit_array), len(self.y_optimised))
		ax_main.plot(self.x_fit_array,self.y_optimised)
		ax_residual.plot(self.x_fit_array,residuals,lw=1.25)
		ax_residual.axhline(0,color='k',linestyle='dashed')
		
		if self.residual_histogram:
			bins = 25
			ax_hist.hist(residuals, bins=bins, orientation='horizontal')
			ax_hist.axhline(0,color='k', linestyle='dashed')

		ax_main.autoscale_view(tight=True)
		
		self._draw_fig(fig,canvas)

	def OnDataProcessing(self,event):
		""" Open the dialog box for binning / smoothing data """
		if self.x_fit_array is not None:
			dlg = DataProcessingDlg(self, "Data Processing", wx.ID_ANY)
			if dlg.Show() == wx.ID_OK:
				dlg.Destroy()	
		else:
			dlg = wx.MessageDialog(self,"Can't process data that hasn't been loaded...", "Nope.", wx.OK|wx.ICON_INFORMATION)
			dlg.ShowModal()
			
	def OnExit(self,event):
		""" What to do when the window is closed """
		## explicitly close all figures (bug with matplotlib and wx??)
		plt.close('all')
		self.Destroy()
		
	def OnFileOpen(self,event):
		""" 
		Open a csv data file and plot the data. Detuning is assumed to be in GHz. 
		Vertical units are assumed to be the same as in the theory curves 
		"""
		self.dirname= ''
		dlg_choice = wx.SingleChoiceDialog(self,"Choose type of data to be imported","Data import",choices=OutputTypes)
		
		# wait for OK to be clicked
		if dlg_choice.ShowModal() == wx.ID_OK:
			choice = dlg_choice.GetSelection()
			#print 'Choice:', choice
			self.expt_type = OutputTypes[choice]
			# use the choice index to select which axes the data appears on	- may be different 
			# if axes order is rearranged later?
			self.choice_index = OutputTypes_index[choice]
			#print self.choice_index
			
			dlg_choice.Destroy()
		
			dlg_open = wx.FileDialog(self,"Choose 2-column csv file (Detuning, Transmission)",
									self.dirname,"","*.csv",wx.FD_OPEN)
			
			# if OK button clicked, open and read file
			if dlg_open.ShowModal() == wx.ID_OK:
				#set experimental display on, and update menus
				self.display_expt_curves[self.choice_index] = True
				#self.showEplotsSubMenu.GetMenuItems()[self.choice_index].Check(True)
			
				self.filename = dlg_open.GetFilename()
				self.dirname = dlg_open.GetDirectory()
				#call read
				self.x_expt_arrays[self.choice_index],self.y_expt_arrays[self.choice_index] = np.loadtxt(os.path.join(self.dirname,self.filename),delimiter=',',usecols=[0,1]).T
				
				#overwrite fit_array data - i.e. last data to be loaded
				self.x_fit_array = self.x_expt_arrays[self.choice_index]
				self.y_fit_array = self.y_expt_arrays[self.choice_index]
				
				# implicit that the fit type is the same as last data imported
				self.fit_datatype = self.expt_type
				
				## create main plot				
				self.OnCreateAxes(self.figs[0],self.canvases[0],clear_current=True)
				
				
			dlg_open.Destroy()

	def OnFitBounds(self,event):
		""" Set fit bounds (for detuning-axis range) """
		dlg = FitBoundsDialog(self,"Fit Bounds", wx.ID_ANY)
		
		if dlg.ShowModal() == wx.ID_OK:
			self.detuning_fit_bounds = dlg.OnUpdateRange()
			#print self.detuning_fit_bounds	
			
	def OnFitButton(self,event):
		""" Call elecsus to fit data. Some sanity checking takes place first. """

		## check for things that will prevent fitting from working, e.g. no data loaded - halt fitting if found
		if len(self.y_fit_array) == 0:
			#warn about no data present
			dlg = wx.MessageDialog(self, "No experimental data has been loaded, cannot proceed with fitting...", "No no no", wx.OK|wx.ICON_EXCLAMATION)
			dlg.ShowModal()
			return
		
		self.fit_bools = [checkbox.IsChecked() for checkbox in self.FitOptions.main_paramlist_bools]
		self.fit_bools += [checkbox.IsChecked() for checkbox in self.FitOptions.magnet_paramlist_bools]
		self.fit_bools += [self.FitOptions.fit_polarisation_checkbox.IsChecked()]
		if self.fit_bools.count(True) == 0:
			dlg = wx.MessageDialog(self, "No fit parameters are floating, cannot proceed with fitting...", "No no no", wx.OK|wx.ICON_EXCLAMATION)
			dlg.ShowModal()
			return
		
		## check for non-optimal conditions and warn user
		if self.warnings:
			## if number of booleans > 3 and ML fitting selected, warn about fitting methods
			if self.fit_bools.count(True) > 3 and self.fit_algorithm=='Marquardt-Levenberg':
				dlg = wx.MessageDialog(self, "The number of fitted parameters is large and the Marquardt-Levenberg algorithm is selected. There is a high probability that the fit will return a local minimum, rather than the global minimum. \n\nTo find the global minimum more reliably, consider changing to either Random-Restart or Simulated Annealing algorithms.\n\n Continue with fitting anyway?", "Warning", wx.YES|wx.NO|wx.ICON_WARNING)
				
				if dlg.ShowModal() == wx.ID_NO:
					return
				
			## if large number of data points
			if len(self.x_fit_array) > 5000:
				dlg = wx.MessageDialog(self, "The number of data points is quite high and fitting may be very slow, especially with Random-Restart or Simulated Annealing methods. \n\nConsider reducing the number of data points by binning data (Menu Fit --> Data Processing... --> Bin Data).\n\n Continue with fitting anyway?", "Warning", wx.YES|wx.NO|wx.ICON_WARNING)

				if dlg.ShowModal() == wx.ID_NO:
					return
		
		# Bounds on fit parameters must be enabled for differential evolution
		if self.fit_algorithm=='Differential Evolution':
			# status of checkboxes for floated params must equal checkboxes ticked for bounds
			ticked_bools = [i for i, box in enumerate(self.FitOptions.main_paramlist_bools+self.FitOptions.magnet_paramlist_bools) if box.IsChecked()]
			print(ticked_bools)
			ticked_bounds = [i for i, box in enumerate(self.FitOptions.main_paramlist_usebounds+self.FitOptions.magnet_paramlist_usebounds) if box.IsChecked()]
			print(ticked_bounds)
			
			if ticked_bools != ticked_bounds:
				dlg = wx.MessageDialog(self,"Bounds on fit parameters must be specified to use Differential Evolution algorithm","Bounds not specified",style=wx.OK|wx.CENTRE|wx.ICON_ERROR)
				dlg.ShowModal()
				return
						
		# If all conditions satisfied, start the fitting process
		self.Call_ElecSus('Fit')
		
	def OnFitCompleted(self,event):
		""" Task to complete after fitting completed """
		panel = self.FitOptions
		
		self.FitInformation.write('\n============== Fit Completed ============ \n\n')
			
		#self.opt_params = self.opt_params_new
		
		## Add information to the Fitting text box
		self.FitInformation.write('\nRMS error between theory and experiment (%): '+str(round(self.rms*100,2))+'\n')
		self.FitInformation.write('\n-------------------------------------------------------------------------------------\n')
		
		self.FitInformation.write('Fit report from LM Fit:\n')
		#[ self.FitInformation.write('\t'+param_name.ljust(30)+str(param_value).ljust(10)+'('+str(boo)+')\n') for param_name, param_value,boo in zip(fittable_parameterlist, self.opt_params_new, self.fit_bools) ]
		self.FitInformation.write(self.fit_result.fit_report())
		
		# Add RMS info to the Fitting text box
				
		# use fitted parameters to calculate new theory arrays
		print('\n\n')
		print(time.ctime())
		print('Calling ElecSus for single calculation with optimised parameters')
		print(self.opt_params)

		self.y_optimised = self.fit_result.best_fit
		#E_in_opt = [self.opt_params['Ex'],[self.opt_params['Ey'],self.opt_params['Phase']]]
		E_in_opt = np.array([self.opt_params['E_x'],self.opt_params['E_y']*np.exp(1.j*self.opt_params['E_phase']),0])
		spectrum_data = calculate(self.x_array*1e3,E_in_opt,self.opt_params, 
					outputs = ['S0','S1','S2','S3','Ix','Iy','I_P45', 'I_M45', 'Ir', 'Il', 'alphaPlus', 'alphaMinus', 'alphaZ'] )
					
		self.y_arrays[0:4] = spectrum_data[0:4] #S0,S1,S2,S3
		self.y_arrays[4] = [spectrum_data[4],spectrum_data[5]] # Ix,Iy
		self.y_arrays[5] = [spectrum_data[6],spectrum_data[7]] # I+45,I-45
		self.y_arrays[6] = [spectrum_data[8],spectrum_data[9]] # I_RCP, I_LCP
		self.y_arrays[7] = [spectrum_data[10],spectrum_data[11],spectrum_data[12]] # alpha +/-/z
				
		# update main plot
		# turn on theory curve for the type of plot we just fitted..
		index_fit_datatype = OutputTypes_index[OutputTypes.index(self.fit_datatype)]
		self.display_theory_curves[index_fit_datatype] = True
		
		# Reset the 'already fitting' flag
		self.already_fitting = False
		
		# close the 'fitting in progress' dialog box
		self.fitting_dlg.Show(False)
		self.fitting_dlg.Destroy()

		# update the figure
		self.OnCreateAxes(self.figs[0],self.canvases[0])			
		print('Updating main plot...')
		
		# ask whether to update the initial fitting parameters
		
		dlg = wx.MessageDialog(self, 
			"Fit completed successfully. Details can be found in the Fitting Information Tab.\
			\n\nReplace initial fit parameters with optimised parameters?", 
			"Fit Complete",
			style=wx.YES|wx.NO)
		if dlg.ShowModal() == wx.ID_YES:
						
			# set optimised parameters into GUI control boxes
		
			self.main_param_labels = ['T','lcell','shift','GammaBuf','DoppTemp','rb85frac','K40frac','K41frac'] ## strings of 	dictionary entries as used by elecsus
			
			self.opt_params['lcell'] *= 1e3
			# main parameters
			for i in range(len(self.main_param_labels)):
				panel.main_paramlist_inputs[i].SetValue(self.opt_params[self.main_param_labels[i]])
			
			
			# magnet parameters
			self.opt_params['Btheta'] /= (np.pi/180)
			self.opt_params['Bphi'] /= (np.pi/180)
			self.opt_params['E_phase'] /= (np.pi/180)
			for i in range(len(self.magnet_param_labels)):
				panel.magnet_paramlist_inputs[i].SetValue(self.opt_params[self.magnet_param_labels[i]])
		
			# polarisation parameters
			for i in range(len(self.polarisation_param_labels)):
				panel.pol_control_inputs[i+1].SetValue(self.opt_params[self.polarisation_param_labels[i]])
				
		dlg.Destroy()
				
		# create/update residuals plot
		self.OnCreateResidualPlot(self.figs[1],self.canvases[1])	

	def OnFitPlotSelection(self,event):
		""" Select plots to show, via the popup button on the main panel """
		btn = event.GetEventObject()
		pos = btn.ClientToScreen( (0,0) )
		dlg = PlotSelectionDialog(self.panel,self,'Fit Plots Shown:','Fit',pos)
 		
		if dlg.Show() == wx.ID_OK:
			dlg.Destroy()
			
	def OnFitTypeChangePanel(self,event):
		""" Action to perform when fit type is changed """
		#print self.fittypeSubMenu.GetMenuItems()
		print('')
		for panelitem in self.FitOptions.fit_types:
			if panelitem.GetValue():
				self.fit_algorithm = panelitem.GetLabel()
				#self.fittypeSubMenu.Check(idfit,True)
				#self.OnFitTypeChangeMenu(1)
				print('Fit Algorithm changed to:', self.fit_algorithm)

			#self.fittypeSubMenu.Check(idfit,False)
			#print menuitem.IsChecked()

	def OnFitWarnings(self,event):
		""" 
		Warn about bad fitting practices - e.g. when there are many data points to fit, or many
		fit parameters where the fit algorithm could be improved.
		"""
		self.warnings = bool(event.IsChecked())

	def OnGridToggleMain(self,event):
		""" Toggle axes grid on main plot """
		for ax in self.figs[0].axes:
			ax.grid(bool(event.IsChecked()))
		self._draw_fig(self.figs[0],self.canvases[0])
					
	def OnGridToggleRes(self,event):
		""" Toggle axes grid on residuals plot """
		for ax in self.figs[1].axes:
			ax.grid(bool(event.IsChecked()))
		self._draw_fig(self.figs[1],self.canvases[1])

	def OnLivePlotting(self,event):
		""" 
		Turn on/off live plotting when parameters are changed. 
		Main plot updates in pseudo-realtime, rather than having to click 'compute' every time
		
		This method binds or unbinds a call to the compute button each time a control is changed.
		"""
		LivePlotOn = bool(event.IsChecked())
		
		if LivePlotOn:
			for ctrl in self.ThyOptions.all_floatspin_inputs:
				self.Bind(EVT_FLOATSPIN, self.OnComputeButton, ctrl)
			
			for ctrl in self.ThyOptions.fixed_paramlist_inputs[0:2]:
				self.Bind(wx.EVT_COMBOBOX, self.OnComputeButton, ctrl)
			
			self.LiveEventsBound = True
		else:
			# unbind the events, if currently bound
			if self.LiveEventsBound:
				# unbind
				for ctrl in self.ThyOptions.all_floatspin_inputs:
					self.Unbind(EVT_FLOATSPIN, ctrl)

				for ctrl in self.ThyOptions.fixed_paramlist_inputs[0:2]:
					self.Unbind(EVT_FLOATSPIN, ctrl)
				
			self.LiveEventsBound = False

	def OnLoadConfig(self,event):
		"""
		Load previous configuration settings from pickle file, for picking up where you left off...
		"""
		dlg = wx.MessageDialog(self, "Load previous configuration of the program - theory/fit parameters, plot settings etc...\n\nNot implemented yet...", "No no no", wx.OK)
		dlg.ShowModal()
		
		## do the opposite of save config ...

	def OnPlotHold(self,event):
		""" 
		Toggle plot hold (keep data on updating figure) on/off
		Allows multiple data sets to be shown on same plot
		"""
		#self.PlotHold = bool(event.IsChecked())
		#self.figs[0].hold(self.PlotHold)
		dlg = wx.MessageDialog(self, "Not implemented yet...", "No no no", wx.OK)
		dlg.ShowModal()
			
	def OnPlotLegend(self,event):
		""" Toggle plot legend on/off """
		self.legendOn = bool(event.IsChecked())
	
	def OnResHist(self,event):
		""" Turn histogram of residuals on/off """
		self.residual_histogram = bool(event.IsChecked())
		self.OnCreateResidualPlot(self.figs[1],self.canvases[1])
							
	def OnSaveConfig(self,event):
		""" 
		Save present configuration settings - plot types, data ranges, parameters ... for faster repeating of common tasks
		"""
		
		dlg = wx.MessageDialog(self, "Save current configuration of the program - theory/fit parameters, plot settings etc...\n\nNot implemented yet...", "No no no", wx.OK)
		dlg.ShowModal()

		data_dump = [0]
		
		# get plot settings
		# ...
		# get theory tab settings
		# ...
		# get fit tab settings
		# ...
		# get mist settings
		# ...
		
		## filedialog window for selecting filename/location
		filename = './elecsus_gui_config.dat'
		
		# save data in python-readable (binary) format using pickle module
		with open(filename,'wb') as file_obj:
			pickle.dump(data_dump, file_obj)
	
	def OnSaveCSVData(self,event):
		""" 
		Method to save the main plot data traces as a n-column csv file. 
		
		*All* data is saved, whether or not it is displayed 
		
		This method selects the file name, then passes that to SaveTheoryCurves()
		"""
		
		SaveFileDialog = wx.FileDialog(self,"Save Output File", "./", "Outputs",
			"CSV files (*.csv)|*.csv", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		
		if SaveFileDialog.ShowModal() == wx.ID_OK:
			output_filename = SaveFileDialog.GetPath()
			SaveFileDialog.Destroy()
			
			print(output_filename)
			
			# don't need this - FD_OVERWRITE_PROMPT does the same job
			#check for overwrite current files
			#if os.path.isfile(output_filename):
			#	OverwriteDialog = wx.MessageDialog(self,"Warning: file exists already! Overwrite?",\
			#		"Overwrite?",wx.YES_NO|wx.NO_DEFAULT)
			#	
			#	if OverwriteDialog.ShowModal() == wx.NO:
			#		OverwriteDialog.Destroy()
			#		return # exit without saving
			#	else:
			#		OverwriteDialog.Destroy()
			
			## do save
			self.SaveTheoryCurves(output_filename)

	def OnSaveFig(self,event):
		""" Basically the same as saving the figure by the toolbar button. """
		#widcards for file type selection
		wilds = "PDF (*.pdf)|*.pdf|" \
				"PNG (*.png)|*.png|" \
				"EPS (*.eps)|*.eps|" \
				"All files (*.*)|*.*"
		exts = ['.pdf','.png','.eps','.pdf'] # default to pdf
		SaveFileDialog = wx.FileDialog(self,message="Save Figure", defaultDir="./", defaultFile="figure",
			wildcard=wilds, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		SaveFileDialog.SetFilterIndex(0)
		
		if SaveFileDialog.ShowModal() == wx.ID_OK:
			output_filename = SaveFileDialog.GetPath()
			if output_filename[-4:] == exts[SaveFileDialog.GetFilterIndex()]:
				output_filename = output_filename[:-4]
			
			#save all current figures
			for fig, id in zip(self.figs, self.fig_IDs):
				fig.savefig(output_filename+'_'+str(id)+exts[SaveFileDialog.GetFilterIndex()])
				
		SaveFileDialog.Destroy()
	
	def OnShowEplots(self,event):
		""" Action when plot type is changed from the menu """
		for ii, item in enumerate(self.showEplotsSubMenu.GetMenuItems()):
			if item.IsChecked():
				self.display_expt_curves[ii] = True
			else:
				self.display_expt_curves[ii] = False
		
		#redraw plot
		self.OnCreateAxes(self.figs[0],self.canvases[0])

	def OnShowTplots(self,event):
		""" Action when plot type is changed from the menu """
		for ii, item in enumerate(self.showTplotsSubMenu.GetMenuItems()):
			if item.IsChecked():
				self.display_theory_curves[ii] = True
			else:
				self.display_theory_curves[ii] = False
		
		#redraw plot
		self.OnCreateAxes(self.figs[0],self.canvases[0])			

	def OnTheoryPlotSelection(self,event):
		""" Select plots to show, via the popup button on the main panel """
 		
		btn = event.GetEventObject()
		pos = btn.ClientToScreen( (0,0) )
		dlg = PlotSelectionDialog(self.panel,self,'Theory Plots Shown:','Theory',pos)
        
		if dlg.Show() == wx.ID_OK:
			dlg.Destroy()

	def OnUseExpDetuning(self,event):
		""" Action when Menu Item 'Use Experimental Detuning' is clicked """
		if self.x_fit_array is not None:
			# only do anything if data has been loaded already
			self.UseExpDetuning = bool(event.IsChecked())
			self.OnComputeButton(1)
		else:
			problem_dlg = wx.MessageDialog(self, "No experimental data has been loaded yet", "No data to use", wx.OK|wx.ICON_ERROR)
			problem_dlg.ShowModal()
			self.eM_UseExpDetuning.Check(False)
			
	def SaveTheoryCurves(self,filename):
		""" 
		Method to actually do the saving of xy data to csv file. 
		Separate to the OnSaveCSVData method in case a filename is automatically chosen
		- i.e. when fitting data, there is an option to autosave the results which calls this method
		"""
		#print len(self.y_arrays)
		#print self.y_arrays		
					
		xy_data = list(zip(self.x_array,self.y_arrays[0].real,self.y_arrays[1].real,self.y_arrays[2].real,self.y_arrays[3].real,\
			self.y_arrays[4][0].real, self.y_arrays[4][1].real,\
			self.y_arrays[5][0].real, self.y_arrays[5][1].real, \
			self.y_arrays[6][0].real, self.y_arrays[6][1].real,\
			self.y_arrays[7][0].real, self.y_arrays[7][1].real, self.y_arrays[7][2].real))
		print('Here fine1')
		success = write_CSV(xy_data, filename, titles=['Detuning']+OutputTypes)
		if not success:
			problem_dlg = wx.MessageDialog(self, "There was an error saving the data...", "Error saving", wx.OK|wx.ICON_ERROR)
			problem_dlg.ShowModal()

##############

''' DEPRECATED - using numpy loadtxt instead ::
def read_CSV(filename,spacing=0,columns=2):
	""" Read an n-column csv file. Return data as n numpy arrays """
	f=open(filename,'U')
	fid=[]
	for line in f:
		fid.append(line)
	f.close()
	# -1: ignore last (blank) line in csv file (lecroy)
	fid = fid[spacing:-1]
	inData=csv.reader(fid,delimiter=',')
	# spacing : skips lines if needed (e.g., on oscilloscope files)
	
	data=[]
	for i in range(0,columns): data.append([])
	
	for row in inData:
		for i in range(0,columns):
			data[i].append(float(row[i]))
	
	for i in range(0,columns):
		data[i] = np.array(data[i])
		
	return data		
'''

def write_CSV(xy,filename,titles=None):
	""" Method for writing csv data with arbitrary
		number of columns to filename.
		takes in xy, which should be of the form [[x1,y1],[x2,y2] ...]
		this can be done by zipping arrays, e.g.
			xy = zip(x,y,z)
			where x, y and z are 1d arrays
		This creates a csv file that looks like
		x1, y1, z1, ...
		x2, y2, z2, ...
		...
		xN, yN, zN, ...
		
		The optional titles writes a single header row at the top of the csv file.
	"""	
	try:
		if sys.version_info[0] < 3:
			wfile = open(filename, 'wb')
		else:
			wfile = open(filename, 'w', newline='')
		with wfile as csvfile:
			csv_writer = csv.writer(csvfile,delimiter=',')
			if titles is not None:
				csv_writer.writerow(titles)
			for xy_line in xy:
				csv_writer.writerow(xy_line)
		return True
	except:
		return False
	
## Run the thing...
def start():
	""" Start the GUI """
	print('Starting ElecSus GUI...')
	global app
	app = wx.App(redirect=False)
	frame = ElecSus_GUI_Frame(None,"ElecSus GUI - General Magneto Optics")
				
	frame.SetSize((1600,900))
	frame.Centre()
	frame.Show()
	app.MainLoop()
	print('...Closed ElecSus GUI')

if __name__ == '__main__':
	""" Called when program started with 'python elecsus_gui.py' from the command line """
	show_versions()
	start()