"""
Base Page for fitting
"""
from __future__ import print_function
import sys
import os
import time
import copy
import math
import json
import logging
import traceback
from Queue import Queue
from threading import Thread
from collections import defaultdict
import numpy as np
import wx
from wx.lib.scrolledpanel import ScrolledPanel
from sasmodels.sasview_model import MultiplicationModel
from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
from sasmodels.weights import GaussianDispersion
from sas.sascalc.dataloader.data_info import Detector
from sas.sascalc.dataloader.data_info import Source
from sas.sascalc.fit.pagestate import PageState
from sas.sascalc.fit.models import PLUGIN_NAME_BASE
from sas.sasgui.guiframe.panel_base import PanelBase
from sas.sasgui.guiframe.report_image_handler import ReportImageHandler
from sas.sasgui.guiframe.utils import format_number, check_float, IdList, \
check_int
from sas.sasgui.guiframe.events import PanelOnFocusEvent
from sas.sasgui.guiframe.events import StatusEvent
from sas.sasgui.guiframe.events import AppendBookmarkEvent
from sas.sasgui.guiframe.dataFitting import Data2D
from sas.sasgui.guiframe.dataFitting import Data1D
from sas.sasgui.guiframe.dataFitting import check_data_validity
from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
from sas.sasgui.guiframe.documentation_window import DocumentationWindow
from .report_dialog import ReportDialog
from .utils import get_weight
logger = logging.getLogger(__name__)
(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
(PreviousStateEvent, EVT_PREVIOUS_STATE) = wx.lib.newevent.NewEvent()
(NextStateEvent, EVT_NEXT_STATE) = wx.lib.newevent.NewEvent()
_BOX_WIDTH = 76
_QMIN_DEFAULT = 0.0005
_QMAX_DEFAULT = 0.5
_NPTS_DEFAULT = 50
# Control panel width
if sys.platform.count("win32") > 0:
PANEL_WIDTH = 450
FONT_VARIANT = 0
ON_MAC = False
else:
PANEL_WIDTH = 500
FONT_VARIANT = 1
ON_MAC = True
CUSTOM_MODEL = 'Plugin Models'
[docs]class BasicPage(ScrolledPanel, PanelBase):
"""
This class provide general structure of the fitpanel page
"""
# Internal name for the AUI manager
window_name = "Fit Page"
# Title to appear on top of the window
window_caption = "Fit Page "
# These two buttons have specific IDs since they seem to be created more
# frequently than they need to. In particular, set_dispers_sizer() is
# called by _on_select_model
ID_BOOKMARK = wx.NewId()
ID_DISPERSER_HELP = wx.NewId()
_id_pool = IdList()
def __init__(self, parent, color='blue', **kwargs):
"""
"""
ScrolledPanel.__init__(self, parent, **kwargs)
PanelBase.__init__(self, parent)
self.SetupScrolling()
# Set window's font size
self.SetWindowVariant(variant=FONT_VARIANT)
self.SetBackgroundColour(color)
self._ids = iter(self._id_pool)
# parent of the page
self.parent = parent
# manager is the fitting plugin
# owner of the page (fitting plugin)
self.event_owner = None
# current model
self.model = None
self.m_name = None
self.index_model = None
self.panel = None
# data
self.data = None
# list of available data
self.data_list = []
self.mask = None
self.uid = wx.NewId()
self.graph_id = None
# Q range for data set
self.qmin_data_set = np.inf
self.qmax_data_set = None
self.npts_data_set = 0
# Q range
self.qmin = None
self.qmax = None
self.qmax_x = _QMAX_DEFAULT
self.qmin_x = _QMIN_DEFAULT
self.npts_x = _NPTS_DEFAULT
# total number of point: float
self.npts = None
self.num_points = None
# smear default
self.current_smearer = None
# 2D smear accuracy default
self.smear2d_accuracy = 'Low'
# slit smear:
self.dxl = None
self.dxw = None
# pinhole smear
self.dx_percent = None
# smear attrbs
self.enable_smearer = None
self.disable_smearer = None
self.pinhole_smearer = None
self.slit_smearer = None
# weight attrbs
self.dI_noweight = None
self.dI_didata = None
self.dI_sqrdata = None
self.dI_idata = None
# other attrbs
self.dq_l = None
self.dq_r = None
self.tcChi = None
self.disp_box = None
self.model_disp = None
self.Npts_fit = None
self.Npts_total = None
self.theory_qmin = None
self.theory_qmax = None
self.theory_qmin_x = None
self.theory_qmax_x = None
self.btEditMask = None
self.btFit = None
self.sld_axes = None
self.multi_factor = None
self.disp_cb_dict = {}
# self.state = PageState()
# dictionary containing list of models
self.model_list_box = {}
# Data member to store the dispersion object created
self._disp_obj_dict = {}
# selected parameters to apply dispersion
self.disp_cb_dict = {}
# smearer object
self.enable2D = False
self._has_magnetic = False
self.magnetic_on = False
self.is_mac = ON_MAC
self.formfactorbox = None
self.structurebox = None
self.categorybox = None
# list of model parameters. each item must have same length
# each item related to a given parameters
# [cb state, name, value, "+/-", error of fit, min, max , units]
self.parameters = []
# non-fittable parameter whose value is astring
self.str_parameters = []
# list of parameters to fit , must be like self.parameters
self.param_toFit = []
# list of looking like parameters but with non fittable parameters info
self.fixed_param = []
# list of looking like parameters but with fittable parameters info
self.fittable_param = []
# list of dispersion parameters
self.disp_list = []
self.disp_name = ""
# list of orientation parameters
self.orientation_params = []
self.orientation_params_disp = []
# Self.model should ALWAYS be None here. It was set to none above in
# this long init setting. no obvious function call in between setting
# and this - commenting out on 4/8/2014 by PDB. Remove once clear
# it is pointless.
# if self.model is not None:
# self.disp_list = self.model.getDispParamList()
self.temp_multi_functional = False
# enable model 2D draw
self.enable2D = False
# check that the fit range is correct to plot the model again
self.fitrange = True
# Create memento to save the current state
self.state = PageState(model=self.model, data=self.data)
# flag to determine if state has change
self.state_change = False
# save customized array
self.values = {} # type: Dict[str, List[float, ...]]
self.weights = {} # type: Dict[str, List[float, ...]]
# retrieve saved state
self.number_saved_state = 0
# dictionary of saved state
self.saved_states = {}
# Create context menu for page
self.popUpMenu = wx.Menu()
wx_id = self._ids.next()
self._keep = wx.MenuItem(self.popUpMenu, wx_id, "Add bookmark",
" Keep the panel status to recall it later")
self.popUpMenu.AppendItem(self._keep)
self._keep.Enable(False)
self._set_bookmark_flag(False)
self._set_save_flag(False)
wx.EVT_MENU(self, wx_id, self.on_bookmark)
self.popUpMenu.AppendSeparator()
# Default locations
self._default_save_location = os.getcwd()
# save initial state on context menu
# self.onSave(event=None)
self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
# bind key event
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
# create the basic structure of the panel with empty sizer
self.define_page_structure()
# drawing Initial dispersion parameters sizer
self.set_dispers_sizer()
# layout
self.set_layout()
# Setting up a thread for the fitting
self.threaded_draw_queue = Queue()
self.draw_worker_thread = Thread(target = self._threaded_draw_worker,
args = (self.threaded_draw_queue,))
self.draw_worker_thread.setDaemon(True)
self.draw_worker_thread.start()
# And a home for the thread submission times
self.last_time_fit_submitted = 0.00
[docs] def set_index_model(self, index):
"""
Index related to this page
"""
self.index_model = index
[docs] def create_default_data(self):
"""
Given the user selection, creates a 1D or 2D data
Only when the page is on theory mode.
"""
if not hasattr(self, "model_view"):
return
toggle_mode_on = self.model_view.IsEnabled() or self.data is None
if toggle_mode_on:
if self.enable2D and not check_data_validity(self.data):
self._create_default_2d_data()
else:
if self.pointsbox.GetValue():
self._create_log_1d_data()
else:
self._create_default_1d_data()
if self.model is not None:
if not self.data.is_data:
self._manager.page_finder[self.uid].set_fit_data(
data=[self.data])
self.on_smear_helper(update=True)
self.state.enable_smearer = self.enable_smearer.GetValue()
self.state.disable_smearer = self.disable_smearer.GetValue()
self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
self.state.slit_smearer = self.slit_smearer.GetValue()
def _create_default_1d_data(self):
"""
Create default data for fitting perspective
Only when the page is on theory mode.
:warning: This data is never plotted.
"""
x = np.linspace(start=self.qmin_x, stop=self.qmax_x,
num=self.npts_x, endpoint=True)
self.data = Data1D(x=x)
self.data.xaxis('\\rm{Q}', "A^{-1}")
self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
self.data.is_data = False
self.data.id = str(self.uid) + " data"
self.data.group_id = str(self.uid) + " Model1D"
def _create_log_1d_data(self):
"""
Create log-spaced data for fitting perspective
Only when the page is on theory mode.
:warning: This data is never plotted.
"""
if self.qmin_x >= 1.e-10:
qmin = np.log10(self.qmin_x)
else:
qmin = -10.
if self.qmax_x <= 1.e10:
qmax = np.log10(self.qmax_x)
else:
qmax = 10.
x = np.logspace(start=qmin, stop=qmax,
num=self.npts_x, endpoint=True, base=10.0)
self.data = Data1D(x=x)
self.data.xaxis('\\rm{Q}', "A^{-1}")
self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
self.data.is_data = False
self.data.id = str(self.uid) + " data"
self.data.group_id = str(self.uid) + " Model1D"
def _create_default_2d_data(self):
"""
Create 2D data by default
Only when the page is on theory mode.
:warning: This data is never plotted.
"""
self.data = Data2D()
qmax = self.qmax_x / math.sqrt(2)
self.data.xaxis('\\rm{Q_{x}}', 'A^{-1}')
self.data.yaxis('\\rm{Q_{y}}', 'A^{-1}')
self.data.is_data = False
self.data.id = str(self.uid) + " data"
self.data.group_id = str(self.uid) + " Model2D"
# Default values
self.data.detector.append(Detector())
index = len(self.data.detector) - 1
self.data.detector[index].distance = 8000 # mm
self.data.source.wavelength = 6 # A
self.data.detector[index].pixel_size.x = 5 # mm
self.data.detector[index].pixel_size.y = 5 # mm
self.data.detector[index].beam_center.x = qmax
self.data.detector[index].beam_center.y = qmax
xmax = qmax
xmin = -qmax
ymax = qmax
ymin = -qmax
qstep = self.npts_x
x = np.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
y = np.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
# use data info instead
new_x = np.tile(x, (len(y), 1))
new_y = np.tile(y, (len(x), 1))
new_y = new_y.swapaxes(0, 1)
# all data reuire now in 1d array
qx_data = new_x.flatten()
qy_data = new_y.flatten()
q_data = np.sqrt(qx_data * qx_data + qy_data * qy_data)
# set all True (standing for unmasked) as default
mask = np.ones(len(qx_data), dtype=bool)
# store x and y bin centers in q space
x_bins = x
y_bins = y
self.data.source = Source()
self.data.data = np.ones(len(mask))
self.data.err_data = np.ones(len(mask))
self.data.qx_data = qx_data
self.data.qy_data = qy_data
self.data.q_data = q_data
self.data.mask = mask
self.data.x_bins = x_bins
self.data.y_bins = y_bins
# max and min taking account of the bin sizes
self.data.xmin = xmin
self.data.xmax = xmax
self.data.ymin = ymin
self.data.ymax = ymax
[docs] def on_set_focus(self, event):
"""
On Set Focus, update guimanger and menu
"""
if self._manager is not None:
wx.PostEvent(self._manager.parent, PanelOnFocusEvent(panel=self))
self.on_tap_focus()
[docs] def on_tap_focus(self):
"""
Update menu1 on cliking the page tap
"""
if self._manager.menu1 is not None:
chain_menu = self._manager.menu1.FindItemById(
self._manager.id_reset_flag)
chain_menu.Enable(self.batch_on)
sim_menu = self._manager.menu1.FindItemById(self._manager.id_simfit)
flag = self.data.is_data\
and (self.model is not None)
sim_menu.Enable(not self.batch_on and flag)
batch_menu = \
self._manager.menu1.FindItemById(self._manager.id_batchfit)
batch_menu.Enable(self.batch_on and flag)
[docs] def onUndo(self, event):
"""
Cancel the previous action
"""
event = PreviousStateEvent(page=self)
wx.PostEvent(self.parent, event)
[docs] def onRedo(self, event):
"""
Restore the previous action cancelled
"""
event = NextStateEvent(page=self)
wx.PostEvent(self.parent, event)
[docs] def define_page_structure(self):
"""
Create empty sizer for a panel
"""
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.sizer0 = wx.BoxSizer(wx.VERTICAL)
self.sizer1 = wx.BoxSizer(wx.VERTICAL)
self.sizer2 = wx.BoxSizer(wx.VERTICAL)
self.sizer3 = wx.BoxSizer(wx.VERTICAL)
self.sizer4 = wx.BoxSizer(wx.VERTICAL)
self.sizer5 = wx.BoxSizer(wx.VERTICAL)
self.sizer6 = wx.BoxSizer(wx.VERTICAL)
self.sizer0.SetMinSize((PANEL_WIDTH, -1))
self.sizer1.SetMinSize((PANEL_WIDTH, -1))
self.sizer2.SetMinSize((PANEL_WIDTH, -1))
self.sizer3.SetMinSize((PANEL_WIDTH, -1))
self.sizer4.SetMinSize((PANEL_WIDTH, -1))
self.sizer5.SetMinSize((PANEL_WIDTH, -1))
self.sizer6.SetMinSize((PANEL_WIDTH, -1))
self.vbox.Add(self.sizer0)
self.vbox.Add(self.sizer1)
self.vbox.Add(self.sizer2)
self.vbox.Add(self.sizer3)
self.vbox.Add(self.sizer4)
self.vbox.Add(self.sizer5)
self.vbox.Add(self.sizer6)
[docs] def set_layout(self):
"""
layout
"""
self.vbox.Layout()
self.vbox.Fit(self)
self.SetSizer(self.vbox)
self.Centre()
[docs] def set_owner(self, owner):
"""
set owner of fitpage
:param owner: the class responsible of plotting
"""
self.event_owner = owner
self.state.event_owner = owner
[docs] def get_state(self):
"""
return the current page state
"""
return self.state
[docs] def get_data(self):
"""
return the current data
"""
return self.data
[docs] def get_data_list(self):
"""
return the current data
"""
return self.data_list
[docs] def set_manager(self, manager):
"""
set panel manager
:param manager: instance of plugin fitting
"""
self._manager = manager
self.state.manager = manager
[docs] def populate_box(self, model_list_box):
"""
Store list of model
:param model_list_box: dictionary containing categorized models
"""
self.model_list_box = model_list_box
self.initialize_combox()
[docs] def set_model_dictionary(self, model_dictionary):
"""
Store a dictionary linking model name -> model object
:param model_dictionary: dictionary containing all models
"""
self.model_dictionary = model_dictionary
[docs] def initialize_combox(self):
"""
put default value in the combo box
"""
if self.model_list_box:
self._populate_box(self.structurebox,
self.model_list_box["Structure Factors"])
self.structurebox.Insert("None", 0, None)
self.structurebox.SetSelection(0)
self.structurebox.Hide()
self.text2.Hide()
self.structurebox.Disable()
self.text2.Disable()
[docs] def set_dispers_sizer(self):
"""
fill sizer containing dispersity info
"""
# print "==== entering set_dispers_sizer ==="
self.sizer4.Clear(True)
name = "Polydispersity and Orientational Distribution"
box_description = wx.StaticBox(self, wx.ID_ANY, name)
box_description.SetForegroundColour(wx.BLUE)
boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
# ----------------------------------------------------
self.disable_disp = wx.RadioButton(self, wx.ID_ANY, 'Off', (10, 10),
style=wx.RB_GROUP)
self.enable_disp = wx.RadioButton(self, wx.ID_ANY, 'On', (10, 30))
# best size for MAC and PC
if ON_MAC:
size_q = (30, 20)
else:
size_q = (20, 15)
self.disp_help_bt = wx.Button(self, self.ID_DISPERSER_HELP, '?',
style=wx.BU_EXACTFIT,
size=size_q)
self.disp_help_bt.Bind(wx.EVT_BUTTON, self.on_pd_help_clicked,
id=self.disp_help_bt.GetId())
self.disp_help_bt.SetToolTipString("Help for polydispersion.")
self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
id=self.disable_disp.GetId())
self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
id=self.enable_disp.GetId())
# MAC needs SetValue
self.disable_disp.SetValue(True)
sizer_dispersion = wx.BoxSizer(wx.HORIZONTAL)
sizer_dispersion.Add((20, 20))
name = "" # Polydispersity and \nOrientational Distribution "
sizer_dispersion.Add(wx.StaticText(self, wx.ID_ANY, name))
sizer_dispersion.Add(self.enable_disp)
sizer_dispersion.Add((20, 20))
sizer_dispersion.Add(self.disable_disp)
sizer_dispersion.Add((25, 20))
sizer_dispersion.Add(self.disp_help_bt)
# fill a sizer for dispersion
boxsizer1.Add(sizer_dispersion, 0,
wx.TOP|wx.BOTTOM|wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE,
border=5)
self.sizer4_4 = wx.GridBagSizer(6, 5)
boxsizer1.Add(self.sizer4_4)
# -----------------------------------------------------
self.sizer4.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
self.sizer4_4.Layout()
self.sizer4.Layout()
self.Layout()
self.Refresh()
# saving the state of enable dispersity button
self.state.enable_disp = self.enable_disp.GetValue()
self.state.disable_disp = self.disable_disp.GetValue()
self.SetupScrolling()
[docs] def onResetModel(self, event):
"""
Reset model state
"""
menu = event.GetEventObject()
# post help message for the selected model
msg = menu.GetHelpString(event.GetId())
msg += " reloaded"
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self.Show(False)
name = menu.GetLabel(event.GetId())
self._on_select_model_helper()
if self.model is not None:
self.m_name = self.model.name
if name in self.saved_states.keys():
previous_state = self.saved_states[name]
# reset state of checkbox,textcrtl and regular parameters value
self.reset_page(previous_state)
self.state.m_name = self.m_name
self.Show(True)
[docs] def on_preview(self, event):
"""
Report the current fit results
"""
# Get plot image from plotpanel
images, canvases = self.get_images()
imgRAM, images, refs = self._build_plots_for_report(images, canvases)
# get the strings for report
report_str, text_str = self.state.report(fig_urls=refs)
# Show the dialog
report_list = [report_str, text_str, images]
dialog = ReportDialog(report_list, imgRAM, refs, None, wx.ID_ANY, "")
dialog.Show()
def _build_plots_for_report(self, figs, canvases):
"""
Build image state that wx.html understand
by plotting, putting it into wx.FileSystem image object
"""
bitmaps = []
for canvas in canvases:
bitmaps.append(canvas.bitmap)
imgs, refs = ReportImageHandler.set_figs(figs, bitmaps, 'fit')
return ReportImageHandler.instance, imgs, refs
[docs] def on_save(self, event):
"""
Save the current state into file
"""
self.save_current_state()
new_state = self.state.clone()
# Ask the user the location of the file to write to.
path = None
if self.parent is not None:
self._default_save_location = \
self._manager.parent._default_save_location
dlg = wx.FileDialog(self, "Choose a file", self._default_save_location,
self.window_caption, "*.fitv", wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self._default_save_location = os.path.dirname(path)
self._manager.parent._default_save_location = \
self._default_save_location
else:
return None
# MAC always needs the extension for saving
extens = ".fitv"
# Make sure the ext included in the file name
fName = os.path.splitext(path)[0] + extens
# the manager write the state into file
self._manager.save_fit_state(filepath=fName, fitstate=new_state)
return new_state
[docs] def on_copy(self, event):
"""
Copy Parameter values to the clipboad
"""
if event is not None:
event.Skip()
# It seems MAC needs wxCallAfter
if event.GetId() == GUIFRAME_ID.COPYEX_ID:
print("copy excel")
wx.CallAfter(self.get_copy_excel)
elif event.GetId() == GUIFRAME_ID.COPYLAT_ID:
print("copy latex")
wx.CallAfter(self.get_copy_latex)
else:
wx.CallAfter(self.get_copy)
[docs] def on_paste(self, event):
"""
Paste Parameter values to the panel if possible
"""
# if event is not None:
# event.Skip()
# It seems MAC needs wxCallAfter for the setvalues
# for multiple textctrl items, otherwise it tends to crash once a while
wx.CallAfter(self.get_paste)
# messages depending on the flag
# self._copy_info(True)
def _copy_info(self, flag):
"""
Send event depending on flag
: Param flag: flag that distinguishes the event
"""
# messages depending on the flag
if flag is None:
msg = " Parameter values are copied to the clipboard..."
infor = 'warning'
elif flag:
msg = " Parameter values are pasted from the clipboard..."
infor = "warning"
else:
msg = "Error occurred: "
msg += "No valid parameter values to paste from the clipboard..."
infor = "warning"
# inform msg to wx
wx.PostEvent(self._manager.parent,
StatusEvent(status=msg, info=infor))
def _get_time_stamp(self):
"""
return time and date stings
"""
# date and time
year, month, day, hour, minute, second, _, _, _ = time.localtime()
current_time = str(hour) + ":" + str(minute) + ":" + str(second)
current_date = str(month) + "/" + str(day) + "/" + str(year)
return current_time, current_date
[docs] def on_bookmark(self, event):
"""
save history of the data and model
"""
if self.model is None:
msg = "Can not bookmark; Please select Data and Model first..."
wx.MessageBox(msg, 'Info')
return
self.save_current_state()
new_state = self.state.clone()
# Add model state on context menu
self.number_saved_state += 1
current_time, current_date = self._get_time_stamp()
# name= self.model.name+"[%g]"%self.number_saved_state
name = "Fitting: %g]" % self.number_saved_state
name += self.model.__class__.__name__
name += "bookmarked at %s on %s" % (current_time, current_date)
self.saved_states[name] = new_state
# Add item in the context menu
msg = "Model saved at %s on %s" % (current_time, current_date)
# post help message for the selected model
msg += " Saved! right click on this page to retrieve this model"
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self.popUpMenu.Append(self.ID_BOOKMARK, name, str(msg))
wx.EVT_MENU(self, self.ID_BOOKMARK, self.onResetModel)
wx.PostEvent(self._manager.parent,
AppendBookmarkEvent(title=name,
hint=str(msg),
handler=self._back_to_bookmark))
def _back_to_bookmark(self, event):
"""
Back to bookmark
"""
self._manager.on_perspective(event)
self.onResetModel(event)
self._draw_model()
[docs] def onSetFocus(self, evt):
"""
highlight the current textcrtl and hide the error text control shown
after fitting
"""
return
[docs] def read_file(self, path):
"""
Read two columns file
:param path: the path to the file to read
"""
try:
if path is None:
status = " Selected Distribution was not loaded: %s" % path
wx.PostEvent(self._manager.parent,
StatusEvent(status=status))
return None, None
input_f = open(path, 'r')
buff = input_f.read()
lines = buff.split('\n')
input_f.close()
angles = []
weights = []
for line in lines:
toks = line.split()
try:
angle = float(toks[0])
weight = float(toks[1])
angles.append(angle)
weights.append(weight)
except Exception:
# Skip non-data lines
logger.error(traceback.format_exc())
return np.array(angles), np.array(weights)
except:
raise
[docs] def createMemento(self):
"""
return the current state of the page
"""
return self.state.clone()
[docs] def save_current_state(self):
"""
Store current state
"""
# save model option
if self.model is not None:
self.disp_list = self.model.getDispParamList()
self.state.disp_list = copy.deepcopy(self.disp_list)
self.state.model = self.model.clone()
# model combobox: complex code because of mac's silent error
if self.structurebox is not None:
if self.structurebox.IsShown():
self.state.structurecombobox = 'None'
s_select = self.structurebox.GetSelection()
if s_select > 0:
self.state.structurecombobox = \
self.structurebox.GetString(s_select)
if self.formfactorbox is not None:
f_select = self.formfactorbox.GetSelection()
if f_select > 0:
self.state.formfactorcombobox = \
self.formfactorbox.GetString(f_select)
if self.categorybox is not None:
cb_select = self.categorybox.GetSelection()
if cb_select > 0:
self.state.categorycombobox = \
self.categorybox.GetString(cb_select)
self.state.enable2D = copy.deepcopy(self.enable2D)
self.state.values = copy.deepcopy(self.values)
self.state.weights = copy.deepcopy(self.weights)
# save data
self.state.data = copy.deepcopy(self.data)
self.state.qmax_x = self.qmax_x
self.state.qmin_x = self.qmin_x
self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
self.state.dq_l = self.dq_l
self.state.dq_r = self.dq_r
if hasattr(self, "enable_disp"):
self.state.enable_disp = self.enable_disp.GetValue()
self.state.disable_disp = self.disable_disp.GetValue()
if hasattr(self, "enable_smearer"):
self.state.enable_smearer = \
copy.deepcopy(self.enable_smearer.GetValue())
self.state.disable_smearer = \
copy.deepcopy(self.disable_smearer.GetValue())
self.state.pinhole_smearer = \
copy.deepcopy(self.pinhole_smearer.GetValue())
self.state.dx_percent = copy.deepcopy(self.dx_percent)
self.state.dxl = copy.deepcopy(self.dxl)
self.state.dxw = copy.deepcopy(self.dxw)
self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
if len(self._disp_obj_dict) > 0:
for k, v in self._disp_obj_dict.iteritems():
self.state.disp_obj_dict[k] = v.type
self.state.values = copy.deepcopy(self.values)
self.state.weights = copy.deepcopy(self.weights)
# save plotting range
self._save_plotting_range()
self.state.orientation_params = []
self.state.orientation_params_disp = []
self.state.parameters = []
self.state.fittable_param = []
self.state.fixed_param = []
self.state.str_parameters = []
# save checkbutton state and txtcrtl values
self._copy_parameters_state(self.str_parameters,
self.state.str_parameters)
self._copy_parameters_state(self.orientation_params,
self.state.orientation_params)
self._copy_parameters_state(self.orientation_params_disp,
self.state.orientation_params_disp)
self._copy_parameters_state(self.parameters, self.state.parameters)
self._copy_parameters_state(self.fittable_param,
self.state.fittable_param)
self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
# save chisqr
self.state.tcChi = self.tcChi.GetValue()
[docs] def save_current_state_fit(self):
"""
Store current state for fit_page
"""
# save model option
if self.model is not None:
self.disp_list = self.model.getDispParamList()
self.state.disp_list = copy.deepcopy(self.disp_list)
self.state.model = self.model.clone()
self.state.enable2D = copy.deepcopy(self.enable2D)
self.state.values = copy.deepcopy(self.values)
self.state.weights = copy.deepcopy(self.weights)
# save data
self.state.data = copy.deepcopy(self.data)
if hasattr(self, "enable_disp"):
self.state.enable_disp = self.enable_disp.GetValue()
self.state.disable_disp = self.disable_disp.GetValue()
if hasattr(self, "enable_smearer"):
self.state.enable_smearer = \
copy.deepcopy(self.enable_smearer.GetValue())
self.state.disable_smearer = \
copy.deepcopy(self.disable_smearer.GetValue())
self.state.pinhole_smearer = \
copy.deepcopy(self.pinhole_smearer.GetValue())
self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
if hasattr(self, "disp_box") and self.disp_box is not None:
self.state.disp_box = self.disp_box.GetCurrentSelection()
if len(self.disp_cb_dict) > 0:
for k, v in self.disp_cb_dict.iteritems():
if v is None:
self.state.disp_cb_dict[k] = v
else:
try:
self.state.disp_cb_dict[k] = v.GetValue()
except Exception:
self.state.disp_cb_dict[k] = None
if len(self._disp_obj_dict) > 0:
for k, v in self._disp_obj_dict.iteritems():
self.state.disp_obj_dict[k] = v.type
self.state.values = copy.deepcopy(self.values)
self.state.weights = copy.deepcopy(self.weights)
# save plotting range
self._save_plotting_range()
# save checkbutton state and txtcrtl values
self._copy_parameters_state(self.orientation_params,
self.state.orientation_params)
self._copy_parameters_state(self.orientation_params_disp,
self.state.orientation_params_disp)
self._copy_parameters_state(self.parameters, self.state.parameters)
self._copy_parameters_state(self.fittable_param,
self.state.fittable_param)
self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
[docs] def check_invalid_panel(self):
"""
check if the user can already perform some action with this panel
"""
if self.data is None:
self.disable_smearer.SetValue(True)
self.disable_disp.SetValue(True)
msg = "Please load Data and select Model to start..."
wx.MessageBox(msg, 'Info')
return True
[docs] def set_model_state(self, state):
"""
reset page given a model state
"""
self.disp_cb_dict = state.disp_cb_dict
self.disp_list = state.disp_list
# fill model combobox
self._show_combox_helper()
# select the current model
try:
# to support older version
category_pos = int(state.categorycombobox)
except Exception:
category_pos = 0
for ind_cat in range(self.categorybox.GetCount()):
if self.categorycombobox.GetString(ind_cat) == \
state.categorycombobox:
category_pos = int(ind_cat)
break
self.categorybox.Select(category_pos)
try:
# to support older version
formfactor_pos = int(state.formfactorcombobox)
except Exception:
formfactor_pos = 0
for ind_form in range(self.formfactorbox.GetCount()):
if self.formfactorbox.GetString(ind_form) == \
state.formfactorcombobox:
formfactor_pos = int(ind_form)
break
self.formfactorbox.Select(formfactor_pos)
try:
# to support older version
structfactor_pos = int(state.structurecombobox)
except Exception:
structfactor_pos = 0
for ind_struct in range(self.structurebox.GetCount()):
if self.structurebox.GetString(ind_struct) == \
state.structurecombobox:
structfactor_pos = int(ind_struct)
break
self.structurebox.SetSelection(structfactor_pos)
if state.multi_factor is not None:
self.multifactorbox.SetSelection(state.multi_factor)
# reset state of checkbox,textcrtl and regular parameters value
self._reset_parameters_state(self.orientation_params_disp,
state.orientation_params_disp)
self._reset_parameters_state(self.orientation_params,
state.orientation_params)
self._reset_parameters_state(self.str_parameters,
state.str_parameters)
self._reset_parameters_state(self.parameters, state.parameters)
# display dispersion info layer
self.enable_disp.SetValue(state.enable_disp)
self.disable_disp.SetValue(state.disable_disp)
if hasattr(self, "disp_box") and self.disp_box is not None:
self.disp_box.SetSelection(state.disp_box)
n = self.disp_box.GetCurrentSelection()
dispersity = self.disp_box.GetClientData(n)
name = dispersity.__name__
self._set_dipers_Param(event=None)
if name == "ArrayDispersion":
for item in self.disp_cb_dict.keys():
if hasattr(self.disp_cb_dict[item], "SetValue"):
self.disp_cb_dict[item].SetValue(
state.disp_cb_dict[item])
# Create the dispersion objects
disp_model = POLYDISPERSITY_MODELS['array']()
if hasattr(state, "values") and \
self.disp_cb_dict[item].GetValue():
if len(state.values) > 0:
self.values = state.values
self.weights = state.weights
disp_model.set_weights(self.values,
state.weights)
else:
self._reset_dispersity()
self._disp_obj_dict[item] = disp_model
# Set the new model as the dispersion object
# for the selected parameter
self.model.set_dispersion(item, disp_model)
self.model._persistency_dict[item] = \
[state.values, state.weights]
else:
keys = self.model.getParamList()
for item in keys:
if item in self.disp_list and \
item not in self.model.details:
self.model.details[item] = ["", None, None]
self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
# smearing info restore
if hasattr(self, "enable_smearer"):
# set smearing value whether or not the data
# contain the smearing info
self.enable_smearer.SetValue(state.enable_smearer)
self.disable_smearer.SetValue(state.disable_smearer)
self.onSmear(event=None)
self.pinhole_smearer.SetValue(state.pinhole_smearer)
self.slit_smearer.SetValue(state.slit_smearer)
self.dI_noweight.SetValue(state.dI_noweight)
self.dI_didata.SetValue(state.dI_didata)
self.dI_sqrdata.SetValue(state.dI_sqrdata)
self.dI_idata.SetValue(state.dI_idata)
# we have two more options for smearing
if self.pinhole_smearer.GetValue():
self.onPinholeSmear(event=None)
elif self.slit_smearer.GetValue():
self.onSlitSmear(event=None)
# reset state of checkbox,textcrtl and dispersity parameters value
self._reset_parameters_state(self.fittable_param, state.fittable_param)
self._reset_parameters_state(self.fixed_param, state.fixed_param)
# draw the model with previous parameters value
self._onparamEnter_helper()
self.select_param(event=None)
# Save state_fit
self.save_current_state_fit()
self._lay_out()
self.Refresh()
[docs] def get_cat_combo_box_pos(self, state):
"""
Iterate through the categories to find the structurefactor
:return: combo_box_position
"""
for key, value in self.master_category_dict.iteritems():
formfactor = state.formfactorcombobox.split(":")
if isinstance(formfactor, list):
formfactor = formfactor[0]
for list_item in value:
if formfactor in list_item:
return self.categorybox.Items.index(key)
return 0
[docs] def reset_page_helper(self, state):
"""
Use page_state and change the state of existing page
:precondition: the page is already drawn or created
:postcondition: the state of the underlying data changes as well as the
state of the graphic interface
"""
if state is None:
return
# set data, etc. from the state
# reset page between theory and fitting from bookmarking
data = state.data
if data is None:
data_min = state.qmin
data_max = state.qmax
self.qmin_x = data_min
self.qmax_x = data_max
self.qmin.SetValue(str(data_min))
self.qmax.SetValue(str(data_max))
self.state.data = data
self.state.qmin = self.qmin_x
self.state.qmax = self.qmax_x
else:
self.set_data(data)
self.enable2D = state.enable2D
try:
self.magnetic_on = state.magnetic_on
except:
# Backward compatibility (for older state files)
self.magnetic_on = False
self.disp_cb_dict = state.disp_cb_dict
self.disp_list = state.disp_list
# fill model combobox
self._show_combox_helper()
# select the current model
state._convert_to_sasmodels()
state.categorycombobox = unicode(state.categorycombobox)
if state.categorycombobox in self.categorybox.Items:
category_pos = self.categorybox.Items.index(
state.categorycombobox)
else:
# Look in master list for model name (model.lower)
category_pos = self.get_cat_combo_box_pos(state)
self.categorybox.Select(category_pos)
self._show_combox(None)
if (self.categorybox.GetValue() == CUSTOM_MODEL
and PLUGIN_NAME_BASE not in state.formfactorcombobox):
state.formfactorcombobox = \
PLUGIN_NAME_BASE + state.formfactorcombobox
formfactor_pos = 0
for ind_form in range(self.formfactorbox.GetCount()):
if (self.formfactorbox.GetString(ind_form)
== state.formfactorcombobox):
formfactor_pos = int(ind_form)
break
self.formfactorbox.Select(formfactor_pos)
structfactor_pos = 0
if state.structurecombobox is not None:
state.structurecombobox = unicode(state.structurecombobox)
for ind_struct in range(self.structurebox.GetCount()):
if (self.structurebox.GetString(ind_struct)
== state.structurecombobox):
structfactor_pos = int(ind_struct)
break
self.structurebox.SetSelection(structfactor_pos)
if state.multi_factor is not None:
self.multifactorbox.SetSelection(state.multi_factor)
# draw the panel according to the new model parameter
self._on_select_model(event=None)
# take care of 2D button
if data is None and self.model_view.IsEnabled():
if self.enable2D:
self.model_view.SetLabel("2D Mode")
else:
self.model_view.SetLabel("1D Mode")
# reset state of checkbox,textcrtl and regular parameters value
self._reset_parameters_state(self.orientation_params_disp,
state.orientation_params_disp)
self._reset_parameters_state(self.orientation_params,
state.orientation_params)
self._reset_parameters_state(self.str_parameters,
state.str_parameters)
self._reset_parameters_state(self.parameters, state.parameters)
# display dispersion info layer
self.enable_disp.SetValue(state.enable_disp)
self.disable_disp.SetValue(state.disable_disp)
# If the polydispersion is ON
if state.enable_disp:
# reset dispersion according the state
self._set_dipers_Param(event=None)
self._reset_page_disp_helper(state)
# plotting range restore
self._reset_plotting_range(state)
# smearing info restore
if hasattr(self, "enable_smearer"):
# set smearing value whether or not the data
# contain the smearing info
self.enable_smearer.SetValue(state.enable_smearer)
self.disable_smearer.SetValue(state.disable_smearer)
self.onSmear(event=None)
self.pinhole_smearer.SetValue(state.pinhole_smearer)
self.slit_smearer.SetValue(state.slit_smearer)
try:
self.dI_noweight.SetValue(state.dI_noweight)
self.dI_didata.SetValue(state.dI_didata)
self.dI_sqrdata.SetValue(state.dI_sqrdata)
self.dI_idata.SetValue(state.dI_idata)
except Exception:
# to support older state file formats
self.dI_noweight.SetValue(False)
self.dI_didata.SetValue(True)
self.dI_sqrdata.SetValue(False)
self.dI_idata.SetValue(False)
# we have two more options for smearing
if self.pinhole_smearer.GetValue():
self.dx_percent = state.dx_percent
if self.dx_percent is not None:
if state.dx_old:
self.dx_percent = 100 * (self.dx_percent / self.data.x[0])
self.smear_pinhole_percent.SetValue("%.2f" % self.dx_percent)
self.onPinholeSmear(event=None)
elif self.slit_smearer.GetValue():
self.dxl = state.dxl
self.dxw = state.dxw
if self.dxl is not None:
self.smear_slit_height.SetValue(str(self.dxl))
if self.dxw is not None:
self.smear_slit_width.SetValue(str(self.dxw))
else:
self.smear_slit_width.SetValue('')
self.onSlitSmear(event=None)
# reset state of checkbox,textcrtl and dispersity parameters value
self._reset_parameters_state(self.fittable_param, state.fittable_param)
self._reset_parameters_state(self.fixed_param, state.fixed_param)
# draw the model with previous parameters value
self._onparamEnter_helper()
# reset the value of chisqr when not consistent with the value computed
self.tcChi.SetValue(str(self.state.tcChi))
# reset context menu items
self._reset_context_menu()
# set the value of the current state to the state given as parameter
self.state = state.clone()
self.state.m_name = self.m_name
def _reset_page_disp_helper(self, state):
"""
Help to rest page for dispersions
"""
keys = self.model.getParamList()
for item in keys:
if item in self.disp_list and \
item not in self.model.details:
self.model.details[item] = ["", None, None]
# for k,v in self.state.disp_cb_dict.iteritems():
self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
self.values = copy.deepcopy(state.values)
self.weights = copy.deepcopy(state.weights)
for key, disp_type in state.disp_obj_dict.iteritems():
# disp_model = disp
disp_model = POLYDISPERSITY_MODELS[disp_type]()
self._disp_obj_dict[key] = disp_model
param_name = key.split('.')[0]
# Try to set dispersion only when available
# for eg., pass the orient. angles for 1D Cal
try:
self.model.set_dispersion(param_name, disp_model)
self.model._persistency_dict[key] = \
[state.values, state.weights]
except Exception:
logger.error(traceback.format_exc())
index, selection = self._find_polyfunc_selection(disp_model)
for list in self.fittable_param:
if list[1] == key and list[7] is not None:
list[7].SetSelection(index)
# For the array disp_model, set the values and weights
if selection == 'array':
disp_model.set_weights(self.values[key],
self.weights[key])
try:
# Diables all fittable params for array
list[0].SetValue(False)
list[0].Disable()
list[2].Disable()
list[5].Disable()
list[6].Disable()
except Exception:
logger.error(traceback.format_exc())
# For array, disable all fixed params
if selection == 'array':
for item in self.fixed_param:
if item[1].split(".")[0] == key.split(".")[0]:
# try it and pass it for the orientation for 1D
try:
item[2].Disable()
except Exception:
logger.error(traceback.format_exc())
def _selectDlg(self):
"""
open a dialog file to select the customized polydispersity function
"""
if self.parent is not None:
self._default_save_location = \
self._manager.parent.get_save_location()
dlg = wx.FileDialog(self, "Choose a weight file",
self._default_save_location, "",
"*.*", wx.OPEN)
path = None
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
return path
def _reset_context_menu(self):
"""
reset the context menu
"""
ids = iter(self._id_pool) # Reusing ids for context menu
for name, _ in self.state.saved_states.iteritems():
self.number_saved_state += 1
# Add item in the context menu
wx_id = ids.next()
msg = 'Save model and state %g' % self.number_saved_state
self.popUpMenu.Append(wx_id, name, msg)
wx.EVT_MENU(self, wx_id, self.onResetModel)
def _reset_plotting_range(self, state):
"""
Reset the plotting range to a given state
"""
self.qmin.SetValue(str(state.qmin))
self.qmax.SetValue(str(state.qmax))
def _save_typeOfmodel(self):
"""
save radiobutton containing the type model that can be selected
"""
# self.state.shape_rbutton = self.shape_rbutton.GetValue()
# self.state.shape_indep_rbutton = self.shape_indep_rbutton.GetValue()
# self.state.struct_rbutton = self.struct_rbutton.GetValue()
# self.state.plugin_rbutton = self.plugin_rbutton.GetValue()
self.state.structurecombobox = self.structurebox.GetValue()
self.state.formfactorcombobox = self.formfactorbox.GetValue()
self.state.categorycombobox = self.categorybox.GetValue()
# post state to fit panel
event = PageInfoEvent(page=self)
wx.PostEvent(self.parent, event)
def _save_plotting_range(self):
"""
save the state of plotting range
"""
self.state.qmin = self.qmin_x
self.state.qmax = self.qmax_x
self.state.npts = self.npts_x
def _onparamEnter_helper(self, is_modified=False):
"""
check if values entered by the user are changed and valid to replot
model
"""
# Flag to register when a parameter has changed.
# is_modified = False
self.fitrange = True
is_2Ddata = False
# self._undo.Enable(True)
# check if 2d data
if self.data.__class__.__name__ == "Data2D":
is_2Ddata = True
if self.model is not None:
# Either we get a is_modified = True passed in because
# _update_paramv_on_fit() has been called already or
# we need to check here ourselves.
if not is_modified:
is_modified = self._check_value_enter(self.fittable_param)
is_modified = self._check_value_enter(
self.fixed_param) or is_modified
is_modified = self._check_value_enter(
self.parameters) or is_modified
# Here we should check whether the boundaries have been modified.
# If qmin and qmax have been modified, update qmin and qmax and
# set the is_modified flag to True
if self._validate_qrange(self.qmin, self.qmax):
tempmin = float(self.qmin.GetValue())
if tempmin != self.qmin_x:
self.qmin_x = tempmin
is_modified = True
tempmax = float(self.qmax.GetValue())
if tempmax != self.qmax_x:
self.qmax_x = tempmax
is_modified = True
if is_2Ddata:
is_modified = self._validate_Npts()
else:
is_modified = self._validate_Npts_1D()
else:
self.fitrange = False
# if any value is modify draw model with new value
if not self.fitrange:
# self.btFit.Disable()
if is_2Ddata:
self.btEditMask.Disable()
else:
if is_2Ddata and self.data.is_data and not self.batch_on:
self.btEditMask.Enable(True)
if is_modified and self.fitrange:
# Theory case: need to get npts value to draw
self.npts_x = float(self.Npts_total.GetValue())
self.Npts_fit.SetValue(str(self.Npts_total.GetValue()))
self._save_plotting_range()
self.create_default_data()
self.state_change = True
self._draw_model()
self.Refresh()
# logger.info("is_modified flag set to %g",is_modified)
return is_modified
def _update_paramv_on_fit(self):
"""
make sure that update param values just before the fitting
"""
# flag for qmin qmax check values
flag = True
self.fitrange = True
is_modified = False
# wx.PostEvent(self._manager.parent, StatusEvent(status=" \
# updating ... ",type="update"))
# So make sure that update param values on_Fit.
# self._undo.Enable(True)
if self.model is not None:
if self.Npts_total.GetValue() != self.Npts_fit.GetValue():
if not self.data.is_data:
self._manager.page_finder[self.uid].set_fit_data(
data=[self.data])
# Check the values
is_modified = self._check_value_enter(self.fittable_param)
is_modified = self._check_value_enter(self.fixed_param) or is_modified
is_modified = self._check_value_enter(self.parameters) or is_modified
# If qmin and qmax have been modified, update qmin and qmax and
# Here we should check whether the boundaries have been modified.
# If qmin and qmax have been modified, update qmin and qmax and
# set the is_modified flag to True
self.fitrange = self._validate_qrange(self.qmin, self.qmax)
if self.fitrange:
tempmin = float(self.qmin.GetValue())
if tempmin != self.qmin_x:
self.qmin_x = tempmin
tempmax = float(self.qmax.GetValue())
if tempmax != self.qmax_x:
self.qmax_x = tempmax
if tempmax == tempmin:
flag = False
temp_smearer = None
if not self.disable_smearer.GetValue():
temp_smearer = self.current_smearer
if self.slit_smearer.GetValue():
flag = self.update_slit_smear()
elif self.pinhole_smearer.GetValue():
flag = self.update_pinhole_smear()
else:
enable_smearer = not self.disable_smearer.GetValue()
self._manager.set_smearer(smearer=temp_smearer,
uid=self.uid,
fid=self.data.id,
qmin=float(self.qmin_x),
qmax=float(self.qmax_x),
enable_smearer=enable_smearer,
draw=False)
elif not self._is_2D():
enable_smearer = not self.disable_smearer.GetValue()
self._manager.set_smearer(smearer=temp_smearer,
qmin=float(self.qmin_x),
uid=self.uid,
fid=self.data.id,
qmax=float(self.qmax_x),
enable_smearer=enable_smearer,
draw=False)
if self.data is not None:
index_data = ((self.qmin_x <= self.data.x) &
(self.data.x <= self.qmax_x))
val = str(len(self.data.x[index_data]))
self.Npts_fit.SetValue(val)
else:
# No data in the panel
try:
self.npts_x = float(self.Npts_total.GetValue())
except Exception:
flag = False
return flag
flag = True
if self._is_2D():
# only 2D case set mask
flag = self._validate_Npts()
if not flag:
return flag
else:
flag = False
else:
flag = False
# For invalid q range, disable the mask editor and fit button, vs.
if not self.fitrange:
if self._is_2D():
self.btEditMask.Disable()
else:
if self._is_2D() and self.data.is_data and not self.batch_on:
self.btEditMask.Enable(True)
if not flag:
msg = "Cannot Plot or Fit :Must select a "
msg += " model or Fitting range is not valid!!! "
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
try:
self.save_current_state()
except Exception:
logger.error(traceback.format_exc())
return flag, is_modified
def _reset_parameters_state(self, listtorestore, statelist):
"""
Reset the parameters at the given state
"""
if len(statelist) == 0 or len(listtorestore) == 0:
return
for item_page in listtorestore:
for param in statelist:
if param[1] == item_page[1]:
item_page_info = param
if (item_page_info[1] == "theta" or item_page_info[1] ==
"phi") and not self._is_2D():
break
# change the state of the check box for simple parameters
if item_page[0] is not None:
item_page[0].SetValue(item_page_info[0])
if item_page[2] is not None:
item_page[2].SetValue(item_page_info[2])
if item_page[2].__class__.__name__ == "ComboBox":
if item_page_info[2] in self.model.fun_list:
fun_val = self.model.fun_list.index(item_page_info[2])
self.model.setParam(item_page_info[1], fun_val)
if item_page[3] is not None:
# show or hide text +/-
if item_page_info[2]:
item_page[3].Show(True)
else:
item_page[3].Hide()
if item_page[4] is not None:
# show of hide the text crtl for fitting error
if item_page_info[4][0]:
item_page[4].Show(True)
item_page[4].SetValue(str(item_page_info[4][1]))
else:
item_page[3].Hide()
if item_page[5] is not None:
# show of hide the text crtl for fitting error
item_page[5].Show(True)
item_page[5].SetValue(str(item_page_info[5][1]))
if item_page[6] is not None:
# show of hide the text crtl for fitting error
item_page[6].Show(True)
item_page[6].SetValue(str(item_page_info[6][1]))
break
def _reset_strparam_state(self, listtorestore, statelist):
"""
Reset the string parameters at the given state
"""
if len(statelist) == 0:
return
listtorestore = copy.deepcopy(statelist)
for item_page, item_page_info in zip(listtorestore, statelist):
# change the state of the check box for simple parameters
if item_page[0] is not None:
item_page[0].SetValue(format_number(item_page_info[0], True))
if item_page[2] is not None:
param_name = item_page_info[1]
value = item_page_info[2]
selection = value
if value in self.model.fun_list:
selection = self.model.fun_list.index(value)
item_page[2].SetValue(selection)
self.model.setParam(param_name, selection)
def _copy_parameters_state(self, listtocopy, statelist):
"""
copy the state of button
:param listtocopy: the list of check button to copy
:param statelist: list of state object to store the current state
"""
if len(listtocopy) == 0:
return
for item in listtocopy:
checkbox_state = None
if item[0] is not None:
checkbox_state = item[0].GetValue()
parameter_name = item[1]
parameter_value = None
if item[2] is not None:
parameter_value = item[2].GetValue()
static_text = None
if item[3] is not None:
static_text = item[3].IsShown()
error_value = None
error_state = None
if item[4] is not None:
error_value = item[4].GetValue()
error_state = item[4].IsShown()
min_value = None
min_state = None
if item[5] is not None:
min_value = item[5].GetValue()
min_state = item[5].IsShown()
max_value = None
max_state = None
if item[6] is not None:
max_value = item[6].GetValue()
max_state = item[6].IsShown()
unit = None
if item[7] is not None:
unit = item[7].GetLabel()
statelist.append([checkbox_state, parameter_name, parameter_value,
static_text, [error_state, error_value],
[min_state, min_value],
[max_state, max_value], unit])
def _draw_model(self, update_chisqr=True, source='model'):
"""
Method to draw or refresh a plotted model.
The method will use the data member from the model page
to build a call to the fitting perspective manager.
:param chisqr: update chisqr value [bool]
"""
self.threaded_draw_queue.put([copy.copy(update_chisqr), copy.copy(source)])
def _threaded_draw_worker(self, threaded_draw_queue):
while True:
# sit and wait for the next task
next_task = threaded_draw_queue.get()
# sleep for 1/10th second in case some other tasks accumulate
time.sleep(0.1)
# skip all intermediate tasks
while self.threaded_draw_queue.qsize() > 0:
self.threaded_draw_queue.task_done()
next_task = self.threaded_draw_queue.get()
# and finally, do the task
self._draw_model_after(*next_task)
threaded_draw_queue.task_done()
def _draw_model_after(self, update_chisqr=True, source='model'):
"""
Method to draw or refresh a plotted model.
The method will use the data member from the model page
to build a call to the fitting perspective manager.
:param chisqr: update chisqr value [bool]
"""
# if self.check_invalid_panel():
# return
if self.model is not None:
temp_smear = None
if hasattr(self, "enable_smearer"):
if not self.disable_smearer.GetValue():
temp_smear = self.current_smearer
# compute weight for the current data
flag = self.get_weight_flag()
weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
toggle_mode_on = self.model_view.IsEnabled()
is_2d = self._is_2D()
self._manager.draw_model(self.model,
data=self.data,
smearer=temp_smear,
qmin=float(self.qmin_x),
qmax=float(self.qmax_x),
page_id=self.uid,
toggle_mode_on=toggle_mode_on,
state=self.state,
enable2D=is_2d,
update_chisqr=update_chisqr,
source='model',
weight=weight)
def _on_show_sld(self, event=None):
"""
Plot SLD profile
"""
# get profile data
x, y = self.model.getProfile()
from sas.sasgui.plottools import Data1D as pf_data1d
from sas.sasgui.guiframe.local_perspectives.plotting.profile_dialog \
import SLDPanel
sld_data = pf_data1d(x, y)
sld_data.name = 'SLD'
sld_data.axes = self.sld_axes
self.panel = SLDPanel(self, data=sld_data, axes=self.sld_axes,
id=wx.ID_ANY)
self.panel.ShowModal()
def _set_multfactor_combobox(self, multiplicity=10):
"""
Set comboBox for multitfactor of CoreMultiShellModel
:param multiplicit: no. of multi-functionality
"""
# build content of the combobox
for idx in range(0, multiplicity):
self.multifactorbox.Append(str(idx), int(idx))
self._hide_multfactor_combobox()
def _show_multfactor_combobox(self):
"""
Show the comboBox of muitfactor of CoreMultiShellModel
"""
if not self.mutifactor_text.IsShown():
self.mutifactor_text.Show(True)
self.mutifactor_text1.Show(True)
if not self.multifactorbox.IsShown():
self.multifactorbox.Show(True)
def _hide_multfactor_combobox(self):
"""
Hide the comboBox of muitfactor of CoreMultiShellModel
"""
if self.mutifactor_text.IsShown():
self.mutifactor_text.Hide()
self.mutifactor_text1.Hide()
if self.multifactorbox.IsShown():
self.multifactorbox.Hide()
[docs] def formfactor_combo_init(self):
"""
First time calls _show_combox_helper
"""
self._show_combox(None)
def _show_combox_helper(self):
"""
Fill panel's combo box according to the type of model selected
"""
mod_cat = self.categorybox.GetStringSelection()
self.structurebox.SetSelection(0)
self.structurebox.Disable()
self.formfactorbox.Clear()
if mod_cat is None:
return
m_list = []
try:
if mod_cat == CUSTOM_MODEL:
for model in self.model_list_box[mod_cat]:
m_list.append(self.model_dictionary[model.name])
else:
cat_dic = self.master_category_dict[mod_cat]
for model, enabled in cat_dic:
if enabled:
m_list.append(self.model_dictionary[model])
except Exception:
msg = traceback.format_exc()
wx.PostEvent(self._manager.parent,
StatusEvent(status=msg, info="error"))
self._populate_box(self.formfactorbox, m_list)
def _on_modify_cat(self, event=None):
"""
Called when category manager is opened
"""
self._manager.parent.on_category_panel(event)
def _show_combox(self, event=None):
"""
Show combox box associate with type of model selected
"""
self.Show(False)
self._show_combox_helper()
self._on_select_model(event=None)
self.Show(True)
self._save_typeOfmodel()
self.sizer4_4.Layout()
self.sizer4.Layout()
self.Layout()
self.Refresh()
def _populate_box(self, combobox, list):
"""
fill combox box with dict item
:param list: contains item to fill the combox
item must model class
"""
mlist = []
for models in list:
if models.name != "NoStructure":
mlist.append((models.name, models))
# Sort the models
mlist_sorted = sorted(mlist)
for item in mlist_sorted:
combobox.Append(item[0], item[1])
return 0
def _onQrangeEnter(self, event):
"""
Check validity of value enter in the Q range field
"""
tcrtl = event.GetEventObject()
# Clear msg if previously shown.
msg = ""
wx.PostEvent(self.parent, StatusEvent(status=msg))
# Flag to register when a parameter has changed.
if tcrtl.GetValue().lstrip().rstrip() != "":
try:
float(tcrtl.GetValue())
tcrtl.SetBackgroundColour(wx.WHITE)
# If qmin and qmax have been modified, update qmin and qmax
if self._validate_qrange(self.qmin, self.qmax):
tempmin = float(self.qmin.GetValue())
if tempmin != self.qmin_x:
self.qmin_x = tempmin
tempmax = float(self.qmax.GetValue())
if tempmax != self.qmax_x:
self.qmax_x = tempmax
else:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered: %s" % \
sys.exc_info()[1]
wx.PostEvent(self.parent, StatusEvent(status=msg))
return
except Exception:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
wx.PostEvent(self.parent, StatusEvent(status=msg))
return
# Check if # of points for theory model are valid(>0).
if self.npts is not None:
if check_float(self.npts):
temp_npts = float(self.npts.GetValue())
if temp_npts != self.num_points:
self.num_points = temp_npts
else:
msg = "Cannot plot: No points in Q range!!! "
wx.PostEvent(self.parent, StatusEvent(status=msg))
else:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered!!!"
wx.PostEvent(self.parent, StatusEvent(status=msg))
self.save_current_state()
event = PageInfoEvent(page=self)
wx.PostEvent(self.parent, event)
self.state_change = False
# Draw the model for a different range
if not self.data.is_data:
self.create_default_data()
self._draw_model()
def _theory_qrange_enter(self, event):
"""
Check validity of value enter in the Q range field
"""
tcrtl = event.GetEventObject()
# Clear msg if previously shown.
msg = ""
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
# Flag to register when a parameter has changed.
is_modified = False
if tcrtl.GetValue().lstrip().rstrip() != "":
try:
value = float(tcrtl.GetValue())
tcrtl.SetBackgroundColour(wx.WHITE)
# If qmin and qmax have been modified, update qmin and qmax
if self._validate_qrange(self.theory_qmin, self.theory_qmax):
tempmin = float(self.theory_qmin.GetValue())
if tempmin != self.theory_qmin_x:
self.theory_qmin_x = tempmin
tempmax = float(self.theory_qmax.GetValue())
if tempmax != self.qmax_x:
self.theory_qmax_x = tempmax
else:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered: %s" % \
sys.exc_info()[1]
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
return
except Exception:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
return
# Check if # of points for theory model are valid(>0).
if self.Npts_total.IsEditable():
if check_float(self.Npts_total):
temp_npts = float(self.Npts_total.GetValue())
if temp_npts != self.num_points:
self.num_points = temp_npts
is_modified = True
else:
msg = "Cannot Plot: No points in Q range!!! "
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
else:
tcrtl.SetBackgroundColour("pink")
msg = "Model Error: wrong value entered!!!"
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self.save_current_state()
event = PageInfoEvent(page=self)
wx.PostEvent(self.parent, event)
self.state_change = False
# Draw the model for a different range
self.create_default_data()
self._draw_model()
def _on_select_model_helper(self):
"""
call back for model selection
"""
# reset dictionary containing reference to dispersion
self._disp_obj_dict = {}
self.disp_cb_dict = {}
self.temp_multi_functional = False
f_id = self.formfactorbox.GetCurrentSelection()
# For MAC
form_factor = None
if f_id >= 0:
form_factor = self.formfactorbox.GetClientData(f_id)
if form_factor is None or \
not hasattr(form_factor, 'is_form_factor') or \
not form_factor.is_form_factor:
self.structurebox.Hide()
self.text2.Hide()
self.structurebox.Disable()
self.structurebox.SetSelection(0)
self.text2.Disable()
else:
self.structurebox.Show()
self.text2.Show()
self.structurebox.Enable()
self.text2.Enable()
if form_factor is not None:
# set multifactor for Mutifunctional models
if form_factor.is_multiplicity_model:
m_id = self.multifactorbox.GetCurrentSelection()
multiplicity = form_factor.multiplicity_info[0]
self.multifactorbox.Clear()
self._set_multfactor_combobox(multiplicity)
self._show_multfactor_combobox()
# ToDo: this info should be called directly from the model
text = form_factor.multiplicity_info[1] # 'No. of Shells: '
self.mutifactor_text.SetLabel(text)
if m_id > multiplicity - 1:
# default value
m_id = 1
self.multi_factor = self.multifactorbox.GetClientData(m_id)
if self.multi_factor is None:
self.multi_factor = 0
self.multifactorbox.SetSelection(m_id)
# Check len of the text1 and max_multiplicity
text = ''
if form_factor.multiplicity_info[0] == \
len(form_factor.multiplicity_info[2]):
text = form_factor.multiplicity_info[2][self.multi_factor]
self.mutifactor_text1.SetLabel(text)
# Check if model has get sld profile.
if len(form_factor.multiplicity_info[3]) > 0:
self.sld_axes = form_factor.multiplicity_info[3]
self.show_sld_button.Show(True)
else:
self.sld_axes = ""
else:
self._hide_multfactor_combobox()
self.show_sld_button.Hide()
self.multi_factor = None
else:
self._hide_multfactor_combobox()
self.show_sld_button.Hide()
self.multi_factor = None
s_id = self.structurebox.GetCurrentSelection()
struct_factor = self.structurebox.GetClientData(s_id)
if struct_factor is not None:
self.model = MultiplicationModel(form_factor(self.multi_factor),
struct_factor())
# multifunctional form factor
if len(form_factor.non_fittable) > 0:
self.temp_multi_functional = True
elif form_factor is not None:
if self.multi_factor is not None:
self.model = form_factor(self.multi_factor)
else:
# old style plugin models do not accept a multiplicity argument
self.model = form_factor()
else:
self.model = None
return
# check if model has magnetic parameters
if len(self.model.magnetic_params) > 0:
self._has_magnetic = True
else:
self._has_magnetic = False
# post state to fit panel
self.state.parameters = []
self.state.model = self.model
self.state.qmin = self.qmin_x
self.state.multi_factor = self.multi_factor
self.disp_list = self.model.getDispParamList()
self.state.disp_list = self.disp_list
self.on_set_focus(None)
self.Layout()
def _validate_qrange(self, qmin_ctrl, qmax_ctrl):
"""
Verify that the Q range controls have valid values
and that Qmin < Qmax.
:param qmin_ctrl: text control for Qmin
:param qmax_ctrl: text control for Qmax
:return: True is the Q range is value, False otherwise
"""
qmin_validity = check_float(qmin_ctrl)
qmax_validity = check_float(qmax_ctrl)
if not (qmin_validity and qmax_validity):
return False
else:
qmin = float(qmin_ctrl.GetValue())
qmax = float(qmax_ctrl.GetValue())
if qmin < qmax:
# Make sure to set both colours white.
qmin_ctrl.SetBackgroundColour(wx.WHITE)
qmin_ctrl.Refresh()
qmax_ctrl.SetBackgroundColour(wx.WHITE)
qmax_ctrl.Refresh()
else:
qmin_ctrl.SetBackgroundColour("pink")
qmin_ctrl.Refresh()
qmax_ctrl.SetBackgroundColour("pink")
qmax_ctrl.Refresh()
msg = "Invalid Q range: Q min must be smaller than Q max"
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
return False
return True
def _validate_Npts(self):
"""
Validate the number of points for fitting is more than 10 points.
If valid, setvalues Npts_fit otherwise post msg.
"""
# default flag
flag = True
# Theory
if self.data is None and self.enable2D:
return flag
for data in self.data_list:
# q value from qx and qy
radius = np.sqrt(data.qx_data * data.qx_data +
data.qy_data * data.qy_data)
# get unmasked index
index_data = (float(self.qmin.GetValue()) <= radius) & \
(radius <= float(self.qmax.GetValue()))
index_data = (index_data) & (data.mask)
index_data = (index_data) & (np.isfinite(data.data))
if len(index_data[index_data]) < 10:
# change the color pink.
self.qmin.SetBackgroundColour("pink")
self.qmin.Refresh()
self.qmax.SetBackgroundColour("pink")
self.qmax.Refresh()
msg = "Data Error: "
msg += "Too few points in %s." % data.name
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self.fitrange = False
flag = False
else:
self.Npts_fit.SetValue(str(len(index_data[index_data])))
self.fitrange = True
return flag
def _validate_Npts_1D(self):
"""
Validate the number of points for fitting is more than 5 points.
If valid, setvalues Npts_fit otherwise post msg.
"""
# default flag
flag = True
# Theory
if self.data is None:
return flag
for data in self.data_list:
# q value from qx and qy
radius = data.x
# get unmasked index
index_data = (float(self.qmin.GetValue()) <= radius) & \
(radius <= float(self.qmax.GetValue()))
index_data = (index_data) & (np.isfinite(data.y))
if len(index_data[index_data]) < 5:
# change the color pink.
self.qmin.SetBackgroundColour("pink")
self.qmin.Refresh()
self.qmax.SetBackgroundColour("pink")
self.qmax.Refresh()
msg = "Data Error: "
msg += "Too few points in %s." % data.name
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self.fitrange = False
flag = False
else:
self.Npts_fit.SetValue(str(len(index_data[index_data])))
self.fitrange = True
return flag
def _check_value_enter(self, list):
"""
:param list: model parameter and panel info
:Note: each item of the list should be as follow:
item=[check button state, parameter's name,
paramater's value, string="+/-",
parameter's error of fit,
parameter's minimum value,
parameter's maximum value ,
parameter's units]
Returns True if the model parameters have changed.
"""
is_modified = False
for item in list:
# skip angle parameters for 1D
if not self.enable2D and item in self.orientation_params:
continue
value_ctrl = item[2]
if not value_ctrl.IsEnabled():
# ArrayDispersion disables PD, Min, Max, Npts, Nsigs
continue
name = item[1]
value_str = value_ctrl.GetValue().strip()
if name.endswith(".npts"):
validity = check_int(value_ctrl)
if not validity:
continue
value = int(value_str)
elif name.endswith(".nsigmas"):
validity = check_float(value_ctrl)
if not validity:
continue
value = float(value_str)
else: # value or polydispersity
# Check that min, max and value are floats
min_ctrl, max_ctrl = item[5], item[6]
min_str = min_ctrl.GetValue().strip()
max_str = max_ctrl.GetValue().strip()
validity = check_float(value_ctrl)
if min_str != "":
validity = validity and check_float(min_ctrl)
if max_str != "":
validity = validity and check_float(max_ctrl)
if not validity:
continue
# Check that min is less than max
low = -np.inf if min_str == "" else float(min_str)
high = np.inf if max_str == "" else float(max_str)
if high < low:
min_ctrl.SetBackgroundColour("pink")
min_ctrl.Refresh()
max_ctrl.SetBackgroundColour("pink")
max_ctrl.Refresh()
# msg = "Invalid fit range for %s: min must be smaller
# than max"%name
# wx.PostEvent(self._manager.parent,
# StatusEvent(status=msg))
continue
# Force value between min and max
value = float(value_str)
if value < low:
value = low
value_ctrl.SetValue(format_number(value))
elif value > high:
value = high
value_ctrl.SetValue(format_number(value))
if name not in self.model.details.keys():
self.model.details[name] = ["", None, None]
old_low, old_high = self.model.details[name][1:3]
if old_low != low or old_high != high:
# The configuration has changed but it won't change the
# computed curve so no need to set is_modified to True
# is_modified = True
self.model.details[name][1:3] = low, high
# Update value in model if it has changed
if (value != self.model.getParam(name) or
(np.isnan(value) and np.isnan(self.model.getParam(name)))):
self.model.setParam(name, value)
is_modified = True
return is_modified
def _set_dipers_Param(self, event):
"""
respond to self.enable_disp and self.disable_disp radio box.
The dispersity object is reset inside the model into Gaussian.
When the user select yes , this method display a combo box for
more selection when the user selects No,the combo box disappears.
Redraw the model with the default dispersity (Gaussian)
"""
# On selction if no model exists.
if self.model is None:
self.disable_disp.SetValue(True)
msg = "Please select a Model first..."
wx.MessageBox(msg, 'Info')
wx.PostEvent(self._manager.parent,
StatusEvent(status="Polydispersion: %s" % msg))
return
self._reset_dispersity()
if self.model is None:
self.model_disp.Hide()
self.sizer4_4.Clear(True)
return
if self.enable_disp.GetValue():
# layout for model containing no dispersity parameters
self.disp_list = self.model.getDispParamList()
if len(self.disp_list) == 0 and len(self.disp_cb_dict) == 0:
self._layout_sizer_noDipers()
else:
# set gaussian sizer
self._on_select_Disp(event=None)
else:
self.sizer4_4.Clear(True)
# post state to fit panel
self.save_current_state()
if event is not None:
event = PageInfoEvent(page=self)
wx.PostEvent(self.parent, event)
# draw the model with the current dispersity
# Wojtek P, Oct 8, 2016: Calling draw_model seems to be unessecary.
# By comenting it we save an extra Iq calculation
# self._draw_model()
# Need to use FitInside again here to replace the next four lines.
# Otherwised polydispersity off does not resize the scrollwindow.
# PDB Nov 28, 2015
self.FitInside()
# self.sizer4_4.Layout()
# self.sizer5.Layout()
# self.Layout()
# self.Refresh()
def _layout_sizer_noDipers(self):
"""
Draw a sizer with no dispersity info
"""
ix = 0
iy = 1
self.fittable_param = []
self.fixed_param = []
self.orientation_params_disp = []
self.sizer4_4.Clear(True)
text = "No polydispersity available for this model"
model_disp = wx.StaticText(self, wx.ID_ANY, text)
self.sizer4_4.Add(model_disp, (iy, ix), (1, 1),
wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
self.sizer4_4.Layout()
self.sizer4.Layout()
def _reset_dispersity(self):
"""
put gaussian dispersity into current model
"""
if self.param_toFit:
for item in self.fittable_param:
if item in self.param_toFit:
self.param_toFit.remove(item)
for item in self.orientation_params_disp:
if item in self.param_toFit:
self.param_toFit.remove(item)
self.fittable_param = []
self.fixed_param = []
self.orientation_params_disp = []
self.values = {}
self.weights = {}
if not self.disp_cb_dict:
self.sizer4_4.Clear(True)
else:
for p in self.disp_cb_dict:
# The parameter was un-selected.
# Go back to Gaussian model (with 0 pts)
disp_model = GaussianDispersion()
self._disp_obj_dict[p] = disp_model
# Set the new model as the dispersion object
# for the selected parameter
try:
self.model.set_dispersion(p, disp_model)
except Exception:
logger.error(traceback.format_exc())
# save state into
self.save_current_state()
self.Layout()
self.Refresh()
def _on_select_Disp(self, event):
"""
allow selecting different dispersion
self.disp_list should change type later .now only gaussian
"""
self._set_sizer_dispersion()
# Redraw the model
# Wojtek P. Nov 7, 2016: Redrawing seems to be unnecessary here
# self._draw_model()
# self._undo.Enable(True)
event = PageInfoEvent(page=self)
wx.PostEvent(self.parent, event)
self.sizer4_4.Layout()
self.sizer4.Layout()
self.SetupScrolling()
def _on_disp_func(self, event=None):
"""
Select a distribution function for the polydispersion
:Param event: ComboBox event
"""
# get ready for new event
if event is not None:
event.Skip()
# Get event object
disp_box = event.GetEventObject()
# Try to select a Distr. function
try:
disp_box.SetBackgroundColour("white")
selection = disp_box.GetCurrentSelection()
param_name = disp_box.Name.split('.')[0]
disp_name = disp_box.GetValue()
dispersity = disp_box.GetClientData(selection)
# disp_model = GaussianDispersion()
disp_model = dispersity()
# Get param names to reset the values of the param
name1 = param_name + ".width"
name2 = param_name + ".npts"
name3 = param_name + ".nsigmas"
# Check Disp. function whether or not it is 'array'
if disp_name.lower() == "array":
value2 = ""
value3 = ""
value1 = self._set_array_disp(name=name1, disp=disp_model)
else:
self._del_array_values(name1)
# self._reset_array_disp(param_name)
self._disp_obj_dict[name1] = disp_model
self.model.set_dispersion(param_name, disp_model)
self.state.disp_obj_dict[name1] = disp_model.type
value1 = str(format_number(self.model.getParam(name1), True))
value2 = str(format_number(self.model.getParam(name2)))
value3 = str(format_number(self.model.getParam(name3)))
# Reset fittable polydispersin parameter value
for item in self.fittable_param:
if item[1] == name1:
item[2].SetValue(value1)
item[5].SetValue("")
item[6].SetValue("")
# Disable for array
if disp_name.lower() == "array":
item[0].SetValue(False)
item[0].Disable()
item[2].Disable()
item[3].Show(False)
item[4].Show(False)
item[5].Disable()
item[6].Disable()
else:
item[0].Enable()
item[2].Enable()
item[3].Show(True)
item[4].Show(True)
item[5].Enable()
item[6].Enable()
break
# Reset fixed polydispersion params
for item in self.fixed_param:
if item[1] == name2:
item[2].SetValue(value2)
# Disable Npts for array
if disp_name.lower() == "array":
item[2].Disable()
else:
item[2].Enable()
if item[1] == name3:
item[2].SetValue(value3)
# Disable Nsigs for array
if disp_name.lower() == "array":
item[2].Disable()
else:
item[2].Enable()
# Make sure the check box updated
self.get_all_checked_params()
# update params
self._update_paramv_on_fit()
# draw
self._draw_model()
self.Layout()
self.Refresh()
except Exception:
logger.error(traceback.format_exc())
# Error msg
msg = "Error occurred:"
msg += " Could not select the distribution function..."
msg += " Please select another distribution function."
disp_box.SetBackgroundColour("pink")
# Focus on Fit button so that users can see the pinky box
self.btFit.SetFocus()
wx.PostEvent(self._manager.parent,
StatusEvent(status=msg, info="error"))
def _set_array_disp(self, name=None, disp=None):
"""
Set array dispersion
:param name: name of the parameter for the dispersion to be set
:param disp: the polydisperion object
"""
# The user wants this parameter to be averaged.
# Pop up the file selection dialog.
path = self._selectDlg()
# Array data
values = []
weights = []
# If nothing was selected, just return
if path is None:
self.disp_cb_dict[name].SetValue(False)
# self.noDisper_rbox.SetValue(True)
return
self._default_save_location = os.path.dirname(path)
if self._manager is not None:
self._manager.parent._default_save_location = \
self._default_save_location
basename = os.path.basename(path)
values, weights = self.read_file(path)
# If any of the two arrays is empty, notify the user that we won't
# proceed
if len(self.param_toFit) > 0:
if name in self.param_toFit:
self.param_toFit.remove(name)
# Tell the user that we are about to apply the distribution
msg = "Applying loaded %s distribution: %s" % (name, path)
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
self._set_array_disp_model(name=name, disp=disp,
values=values, weights=weights)
return basename
def _set_array_disp_model(self, name=None, disp=None,
values=[], weights=[]):
"""
Set array dispersion model
:param name: name of the parameter for the dispersion to be set
:param disp: the polydisperion object
"""
disp.set_weights(values, weights)
self._disp_obj_dict[name] = disp
self.model.set_dispersion(name.split('.')[0], disp)
self.state.disp_obj_dict[name] = disp.type
self.values[name] = values
self.weights[name] = weights
# Store the object to make it persist outside the
# scope of this method
# TODO: refactor model to clean this up?
self.state.values = {}
self.state.weights = {}
self.state.values = copy.deepcopy(self.values)
self.state.weights = copy.deepcopy(self.weights)
# Set the new model as the dispersion object for the
# selected parameter
# self.model.set_dispersion(p, disp_model)
# Store a reference to the weights in the model object
# so that
# it's not lost when we use the model within another thread.
self.state.model = self.model.clone()
self.model._persistency_dict[name.split('.')[0]] = \
[values, weights]
self.state.model._persistency_dict[name.split('.')[0]] = \
[values, weights]
def _del_array_values(self, name=None):
"""
Reset array dispersion
:param name: name of the parameter for the dispersion to be set
"""
# Try to delete values and weight of the names array dic if exists
try:
if name in self.values:
del self.values[name]
del self.weights[name]
# delete all other dic
del self.state.values[name]
del self.state.weights[name]
del self.model._persistency_dict[name.split('.')[0]]
del self.state.model._persistency_dict[name.split('.')[0]]
except Exception:
logger.error(traceback.format_exc())
def _lay_out(self):
"""
returns self.Layout
:Note: Mac seems to like this better when self.
Layout is called after fitting.
"""
self.Layout()
return
def _find_polyfunc_selection(self, disp_func=None):
"""
Find Combobox selection from disp_func
:param disp_function: dispersion distr. function
"""
# Find the selection
if disp_func is not None:
try:
return (list(POLYDISPERSITY_MODELS).index(disp_func.type),
disp_func.type)
except ValueError:
pass # Fall through to default class
return (list(POLYDISPERSITY_MODELS).index('gaussian'), 'gaussian')
[docs] def on_reset_clicked(self, event):
"""
On 'Reset' button for Q range clicked
"""
flag = True
# For 3 different cases: Data2D, Data1D, and theory
if self.model is None:
msg = "Please select a model first..."
wx.MessageBox(msg, 'Info')
flag = False
return
elif self.data.__class__.__name__ == "Data2D":
data_min = 0
x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax))
y = max(math.fabs(self.data.ymin), math.fabs(self.data.ymax))
self.qmin_x = data_min
self.qmax_x = math.sqrt(x * x + y * y)
# self.data.mask = np.ones(len(self.data.data),dtype=bool)
# check smearing
if not self.disable_smearer.GetValue():
# set smearing value whether or
# not the data contain the smearing info
if self.pinhole_smearer.GetValue():
flag = self.update_pinhole_smear()
else:
flag = True
elif self.data is None:
self.qmin_x = _QMIN_DEFAULT
self.qmax_x = _QMAX_DEFAULT
self.num_points = _NPTS_DEFAULT
self.state.npts = self.num_points
elif self.data.__class__.__name__ != "Data2D":
self.qmin_x = min(self.data.x)
self.qmax_x = max(self.data.x)
# check smearing
if not self.disable_smearer.GetValue():
# set smearing value whether or
# not the data contain the smearing info
if self.slit_smearer.GetValue():
flag = self.update_slit_smear()
elif self.pinhole_smearer.GetValue():
flag = self.update_pinhole_smear()
else:
flag = True
else:
flag = False
if flag is False:
msg = "Cannot Plot :Must enter a number!!! "
wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
else:
# set relative text ctrs.
self.qmin.SetValue(str(self.qmin_x))
self.qmax.SetValue(str(self.qmax_x))
self.show_npts2fit()
# At this point, some button and variables satatus (disabled?)
# should be checked such as color that should be reset to
# white in case that it was pink.
self._onparamEnter_helper()
self.save_current_state()
self.state.qmin = self.qmin_x
self.state.qmax = self.qmax_x
# reset the q range values
self._reset_plotting_range(self.state)
self._draw_model()
[docs] def select_log(self, event):
"""
Log checked to generate log spaced points for theory model
"""
[docs] def get_images(self):
"""
Get the images of the plots corresponding this panel for report
: return graphs: list of figures
: Need Move to guiframe
"""
# set list of graphs
graphs = []
canvases = []
res_item = None
# call gui_manager
gui_manager = self._manager.parent
# loops through the panels [dic]
for _, item2 in gui_manager.plot_panels.iteritems():
data_title = self.data.group_id
# try to get all plots belonging to this control panel
try:
g_id = item2.group_id
if g_id == data_title or \
str(g_id).count("res" + str(self.graph_id)) or \
str(g_id).count(str(self.uid)) > 0:
if str(g_id).count("res" + str(self.graph_id)) > 0:
res_item = [item2.figure, item2.canvas]
else:
# append to the list
graphs.append(item2.figure)
canvases.append(item2.canvas)
except Exception:
# Not for control panels
logger.error(traceback.format_exc())
# Make sure the resduals plot goes to the last
if res_item is not None:
graphs.append(res_item[0])
canvases.append(res_item[1])
# return the list of graphs
return graphs, canvases
[docs] def on_function_help_clicked(self, event):
"""
Function called when 'Help' button is pressed next to model
of interest. This calls DocumentationWindow from
documentation_window.py. It will load the top level of the html model
help documenation sphinx generated if either a plugin model (which
normally does not have an html help help file) is selected or if no
model is selected. Otherwise, if a regula model is selected, the
documention for that model will be sent to a browser window.
:todo the quick fix for no documentation in plugins is the if statment.
However, the right way to do this would be to check whether the hmtl
file exists and load the model docs if it does and the general docs if
it doesn't - this will become important if we ever figure out how to
build docs for plugins on the fly. Sep 9, 2018 -PDB
:param event: on Help Button pressed event
"""
if (self.model is not None) and (self.categorybox.GetValue()
!= "Plugin Models"):
name = self.formfactorbox.GetValue()
_TreeLocation = 'user/models/%s.html' % name
_doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
"", name + " Help")
else:
_TreeLocation = 'user/sasgui/perspectives/fitting/models/index.html'
_doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
"", "General Model Help")
[docs] def on_model_help_clicked(self, event):
"""
Function called when 'Description' button is pressed next to model
of interest. This calls the Description embedded in the model. This
should work with either Wx2.8 and lower or higher. If no model is
selected it will give the message that a model must be chosen first
in the box that would normally contain the description. If a badly
behaved model is encountered which has no description then it will
give the message that none is available.
:param event: on Description Button pressed event
"""
if self.model is None:
name = 'index.html'
else:
name = self.formfactorbox.GetValue()
msg = 'Model description:\n'
info = "Info"
if self.model is not None:
# frame.Destroy()
if str(self.model.description).rstrip().lstrip() == '':
msg += "Sorry, no information is available for this model."
else:
msg += self.model.description + '\n'
wx.MessageBox(msg, info)
else:
msg += "You must select a model to get information on this"
wx.MessageBox(msg, info)
def _on_mag_angle_help(self, event):
"""
Bring up Magnetic Angle definition.png image whenever the ? button
is clicked. Calls DocumentationWindow with the path of the location
within the documentation tree (after /doc/ ....". When using old
versions of Wx (i.e. before 2.9 and therefore not part of release
versions distributed via installer) it brings up an image viewer
box which allows the user to click through the rest of the images in
the directory. Not ideal but probably better than alternative which
would bring up the entire discussion of how magnetic models work?
Specially since it is not likely to be accessed. The normal release
versions bring up the normal image box.
:param evt: Triggers on clicking ? in Magnetic Angles? box
"""
_TreeLocation = "_images/M_angles_pic.png"
_doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
"Magnetic Angle Defintions")
def _on_mag_help(self, event):
"""
Bring up Magnetic Angle definition.png image whenever the ? button
is clicked. Calls DocumentationWindow with the path of the location
within the documentation tree (after /doc/ ....". When using old
versions of Wx (i.e. before 2.9 and therefore not part of release
versions distributed via installer) it brings up an image viewer
box which allows the user to click through the rest of the images in
the directory. Not ideal but probably better than alternative which
would bring up the entire discussion of how magnetic models work?
Specially since it is not likely to be accessed. The normal release
versions bring up the normal image box.
:param evt: Triggers on clicking ? in Magnetic Angles? box
"""
_TreeLocation = "user/sasgui/perspectives/fitting/magnetism/magnetism.html"
_doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
"Polarized Beam/Magnetc Help")
def _on_mag_on(self, event):
"""
Magnetic Parameters ON/OFF
"""
button = event.GetEventObject()
if button.GetLabel().count('ON') > 0:
self.magnetic_on = True
button.SetLabel("Magnetic OFF")
m_value = 1
for key in self.model.magnetic_params:
if key.count('M0') > 0:
self.model.setParam(key, m_value)
m_value += 0.5
else:
self.magnetic_on = False
button.SetLabel("Magnetic ON")
for key in self.model.magnetic_params:
if key.count('M0') > 0:
# reset mag value to zero fo safety
self.model.setParam(key, 0.0)
self.Show(False)
self.set_model_param_sizer(self.model)
# self._set_sizer_dispersion()
self.state.magnetic_on = self.magnetic_on
self.SetupScrolling()
self.Show(True)
[docs] def on_pd_help_clicked(self, event):
"""
Bring up Polydispersity Documentation whenever the ? button is clicked.
Calls DocumentationWindow with the path of the location within the
documentation tree (after /doc/ ....". Note that when using old
versions of Wx (before 2.9) and thus not the release version of
istallers, the help comes up at the top level of the file as
webbrowser does not pass anything past the # to the browser when it is
running "file:///...."
:param event: Triggers on clicking ? in polydispersity box
"""
_TreeLocation = "user/sasgui/perspectives/fitting/pd/polydispersity.html"
_PageAnchor = ""
_doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
_PageAnchor, "Polydispersity Help")
[docs] def on_left_down(self, event):
"""
Get key stroke event
"""
# Figuring out key combo: Cmd for copy, Alt for paste
if event.CmdDown() and event.ShiftDown():
self.get_paste()
elif event.CmdDown():
self.get_copy()
else:
event.Skip()
return
# make event free
event.Skip()
[docs] def get_copy(self):
"""
Get copy params to clipboard
"""
content = self.get_copy_params()
flag = self.set_clipboard(content)
self._copy_info(flag)
return flag
[docs] def get_copy_params(self):
"""
Get the string copies of the param names and values in the tap
"""
content = 'sasview_parameter_values:'
# Do it if params exist
if self.parameters:
# go through the parameters
strings = self._get_copy_helper(self.parameters,
self.orientation_params)
content += strings
# go through the fittables
strings = self._get_copy_helper(self.fittable_param,
self.orientation_params_disp)
content += strings
# go through the fixed params
strings = self._get_copy_helper(self.fixed_param,
self.orientation_params_disp)
content += strings
# go through the str params
strings = self._get_copy_helper(self.str_parameters,
self.orientation_params)
content += strings
return content
else:
return False
def _get_copy_params_details(self):
"""
Combines polydisperse parameters with self.parameters so that they can
be written to the clipboard (for Excel or LaTeX). Also returns a list of
the names of parameters that have been fitted
:returns: all_params - A list of all parameters, in the format of
self.parameters
:returns: fitted_par_names - A list of the names of parameters that have
been fitted
"""
# Names of params that are being fitted
fitted_par_names = [param[1] for param in self.param_toFit]
# Names of params with associated polydispersity
disp_params = [param[1].split('.')[0] for param in self.fittable_param]
# Create array of all parameters
all_params = copy.copy(self.parameters)
for param in self.parameters:
if param[1] in disp_params:
# Polydisperse params aren't in self.parameters, so need adding
# to all_params
name = param[1] + ".width"
index = all_params.index(param) + 1
to_insert = []
if name in fitted_par_names:
# Param is fitted, so already has a param list in self.param_toFit
to_insert = self.param_toFit[fitted_par_names.index(name)]
else:
# Param isn't fitted, so mockup a param list
to_insert = [None, name, self.model.getParam(name), None, None]
all_params.insert(index, to_insert)
return all_params, fitted_par_names
[docs] def get_copy_excel(self):
"""
Get copy params to clipboard
"""
content = self.get_copy_params_excel()
flag = self.set_clipboard(content)
self._copy_info(flag)
return flag
[docs] def get_copy_params_excel(self):
"""
Get the string copies of the param names and values in the tap
"""
if not self.parameters:
# Do nothing if parameters doesn't exist
return False
content = ''
crlf = chr(13) + chr(10)
tab = chr(9)
all_params, fitted_param_names = self._get_copy_params_details()
# Construct row of parameter names
for param in all_params:
name = param[1] # Parameter name
content += name
content += tab
if name in fitted_param_names:
# Only print errors for fitted parameters
content += name + "_err"
content += tab
content += crlf
# Construct row of parameter values and errors
for param in all_params:
value = param[2]
if hasattr(value, 'GetValue'):
# param[2] is a text box
value = value.GetValue()
else:
# param[2] is a float (from our self._get_copy_params_details)
value = str(value)
content += value
content += tab
if param[1] in fitted_param_names:
# Only print errors for fitted parameters
content += param[4].GetValue()
content += tab
return content
[docs] def get_copy_latex(self):
"""
Get copy params to clipboard
"""
content = self.get_copy_params_latex()
flag = self.set_clipboard(content)
self._copy_info(flag)
return flag
[docs] def get_copy_params_latex(self):
"""
Get the string copies of the param names and values in the tap
"""
if not self.parameters:
# Do nothing if self.parameters doesn't exist
return False
content = r'\begin{table}'
content += r'\begin{tabular}[h]'
crlf = chr(13) + chr(10)
tab = chr(9)
all_params, fitted_param_names = self._get_copy_params_details()
content += '{|'
for param in all_params:
content += 'l|l|'
content += r'}\hline'
content += crlf
# Construct row of parameter names
for index, param in enumerate(all_params):
name = param[1] # Parameter name
content += name.replace('_', r'\_') # Escape underscores
if name in fitted_param_names:
# Only print errors for fitted parameters
content += ' & '
content += name.replace('_', r'\_') + r"\_err"
if index < len(all_params) - 1:
content += ' & '
content += r'\\ \hline'
content += crlf
# Construct row of values and errors
for index, param in enumerate(all_params):
value = param[2]
if hasattr(value, "GetValue"):
# value is a text box
value = value.GetValue()
else:
# value is a float (from self._get_copy_params_details)
value = str(value)
content += value
if param[1] in fitted_param_names:
# Only print errors for fitted params
content += ' & '
content += param[4].GetValue()
if index < len(all_params) - 1:
content += ' & '
content += r'\\ \hline'
content += crlf
content += r'\end{tabular}'
content += r'\end{table}'
return content
[docs] def set_clipboard(self, content=None):
"""
Put the string to the clipboard
"""
if not content:
return False
if wx.TheClipboard.Open():
wx.TheClipboard.SetData(wx.TextDataObject(str(content)))
wx.TheClipboard.Close()
return True
return None
def _get_copy_helper(self, param, orient_param):
"""
Helping get value and name of the params
: param param: parameters
: param orient_param = oritational params
: return content: strings [list] [name,value:....]
"""
content = ''
bound_hi = ''
bound_lo = ''
# go through the str params
for item in param:
# copy only the params shown
if not item[2].IsShown():
continue
disfunc = ''
try:
if item[7].__class__.__name__ == 'ComboBox':
disfunc = str(item[7].GetValue())
except Exception:
logger.error(traceback.format_exc())
# 2D
if self.data.__class__.__name__ == "Data2D":
try:
check = item[0].GetValue()
except Exception:
check = None
name = item[1]
value = item[2].GetValue()
# 1D
else:
# for 1D all parameters except orientation
if not item[1] in orient_param:
try:
check = item[0].GetValue()
except:
check = None
name = item[1]
value = item[2].GetValue()
# Bounds
try:
bound_lo = item[5].GetValue()
bound_hi = item[6].GetValue()
except Exception:
# harmless - need to just pass
pass
# add to the content
if disfunc != '':
disfunc = ',' + disfunc
# Need to support array func for copy/paste
try:
if disfunc.count('array') > 0:
disfunc += ','
for val in self.values[name]:
disfunc += ' ' + str(val)
disfunc += ','
for weight in self.weights[name]:
disfunc += ' ' + str(weight)
except Exception:
logger.error(traceback.format_exc())
content += name + ',' + str(check) + ',' + value + disfunc + ',' + \
bound_lo + ',' + bound_hi + ':'
return content
[docs] def get_clipboard(self):
"""
Get strings in the clipboard
"""
text = ""
# Get text from the clip board
if wx.TheClipboard.Open():
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
data = wx.TextDataObject()
# get wx dataobject
success = wx.TheClipboard.GetData(data)
# get text
if success:
text = data.GetText()
else:
text = ''
# close clipboard
wx.TheClipboard.Close()
return text
[docs] def get_paste(self):
"""
Paste params from the clipboard
"""
text = self.get_clipboard()
flag = self.get_paste_params(text)
self._copy_info(flag)
return flag
[docs] def get_paste_params(self, text=''):
"""
Get the string copies of the param names and values in the tap
"""
context = {}
# put the text into dictionary
lines = text.split(':')
if lines[0] != 'sasview_parameter_values':
self._copy_info(False)
return False
for line in lines[1:-1]:
if len(line) != 0:
item = line.split(',')
check = item[1]
name = item[0]
value = item[2]
# Transfer the text to content[dictionary]
context[name] = [check, value]
# limits
limit_lo = item[3]
context[name].append(limit_lo)
limit_hi = item[4]
context[name].append(limit_hi)
# ToDo: PlugIn this poly disp function for pasting
try:
poly_func = item[5]
context[name].append(poly_func)
try:
# take the vals and weights for array
array_values = item[6].split(' ')
array_weights = item[7].split(' ')
val = [float(a_val) for a_val in array_values[1:]]
weit = [float(a_weit) for a_weit in array_weights[1:]]
context[name].append(val)
context[name].append(weit)
except:
raise
except:
poly_func = ''
context[name].append(poly_func)
# Do it if params exist
if self.parameters:
# go through the parameters
self._get_paste_helper(self.parameters,
self.orientation_params, context)
# go through the fittables
self._get_paste_helper(self.fittable_param,
self.orientation_params_disp,
context)
# go through the fixed params
self._get_paste_helper(self.fixed_param,
self.orientation_params_disp, context)
# go through the str params
self._get_paste_helper(self.str_parameters,
self.orientation_params, context)
return True
return None
def _get_paste_helper(self, param, orient_param, content):
"""
Helping set values of the params
: param param: parameters
: param orient_param: oritational params
: param content: dictionary [ name, value: name1.value1,...]
"""
# go through the str params
for item in param:
# 2D
if self.data.__class__.__name__ == "Data2D":
name = item[1]
if name in content.keys():
values = content[name]
check = values[0]
pd = values[1]
if name.count('.') > 0:
# If this is parameter.width, then pd may be a floating
# point value or it may be an array distribution.
# Nothing to do for parameter.npts or parameter.nsigmas.
try:
float(pd)
if name.endswith('.npts'):
pd = int(pd)
except Exception:
# continue
if not pd and pd != '':
continue
item[2].SetValue(str(pd))
if item in self.fixed_param and pd == '':
# Only array func has pd == '' case.
item[2].Enable(False)
else:
item[2].Enable(True)
if item[2].__class__.__name__ == "ComboBox":
if content[name][1] in self.model.fun_list:
fun_val = self.model.fun_list.index(content[name][1])
self.model.setParam(name, fun_val)
try:
item[5].SetValue(str(values[-3]))
item[6].SetValue(str(values[-2]))
except Exception:
# passing as harmless non-update
pass
value = content[name][1:]
self._paste_poly_help(item, value)
if check == 'True':
is_true = True
elif check == 'False':
is_true = False
else:
is_true = None
if is_true is not None:
item[0].SetValue(is_true)
# 1D
else:
# for 1D all parameters except orientation
if not item[1] in orient_param:
name = item[1]
if name in content.keys():
check = content[name][0]
# Avoid changing combox content
value = content[name][1:]
pd = value[0]
if name.count('.') > 0:
# If this is parameter.width, then pd may be a
# floating point value or it may be an array
# distribution. Nothing to do for parameter.npts or
# parameter.nsigmas.
try:
pd = float(pd)
if name.endswith('.npts'):
pd = int(pd)
except Exception:
# continue
if not pd and pd != '':
continue
item[2].SetValue(str(pd))
if item in self.fixed_param and pd == '':
# Only array func has pd == '' case.
item[2].Enable(False)
else:
item[2].Enable(True)
if item[2].__class__.__name__ == "ComboBox":
if value[0] in self.model.fun_list:
fun_val = self.model.fun_list.index(value[0])
self.model.setParam(name, fun_val)
# save state
try:
item[5].SetValue(str(value[-3]))
item[6].SetValue(str(value[-2]))
except Exception:
# passing as harmless non-update
pass
self._paste_poly_help(item, value)
if check == 'True':
is_true = True
elif check == 'False':
is_true = False
else:
is_true = None
if is_true is not None:
item[0].SetValue(is_true)
self.select_param(event=None)
self.Refresh()
def _paste_poly_help(self, item, value):
"""
Helps get paste for poly function
*item* is the parameter name
*value* depends on which parameter is being processed, and whether it
has array polydispersity.
For parameters without array polydispersity:
parameter => ['FLOAT', '']
parameter.width => ['FLOAT', 'DISTRIBUTION', '']
parameter.npts => ['FLOAT', '']
parameter.nsigmas => ['FLOAT', '']
For parameters with array polydispersity:
parameter => ['FLOAT', '']
parameter.width => ['FILENAME', 'array', [x1, ...], [w1, ...]]
parameter.npts => ['FLOAT', '']
parameter.nsigmas => ['FLOAT', '']
"""
# Do nothing if not setting polydispersity
if len(value[3]) == 0:
return
try:
name = item[7].Name
param_name = name.split('.')[0]
item[7].SetValue(value[1])
selection = item[7].GetCurrentSelection()
dispersity = item[7].GetClientData(selection)
disp_model = dispersity()
if value[1] == 'array':
pd_vals = np.array(value[2])
pd_weights = np.array(value[3])
if len(pd_vals) == 0 or len(pd_vals) != len(pd_weights):
msg = ("bad array distribution parameters for %s"
% param_name)
raise ValueError(msg)
self._set_disp_cb(True, item=item)
self._set_array_disp_model(name=name,
disp=disp_model,
values=pd_vals,
weights=pd_weights)
else:
self._set_disp_cb(False, item=item)
self._disp_obj_dict[name] = disp_model
self.model.set_dispersion(param_name, disp_model)
self.state.disp_obj_dict[name] = disp_model.type
# TODO: It's not an array, why update values and weights?
self.model._persistency_dict[param_name] = \
[self.values, self.weights]
self.state.values = self.values
self.state.weights = self.weights
except Exception:
logger.error(traceback.format_exc())
print("Error in BasePage._paste_poly_help: %s" % \
sys.exc_info()[1])
def _set_disp_cb(self, isarray, item):
"""
Set cb for array disp
"""
if isarray:
item[0].SetValue(False)
item[0].Enable(False)
item[2].Enable(False)
item[3].Show(False)
item[4].Show(False)
item[5].SetValue('')
item[5].Enable(False)
item[6].SetValue('')
item[6].Enable(False)
else:
item[0].Enable()
item[2].Enable()
item[3].Show(True)
item[4].Show(True)
item[5].Enable()
item[6].Enable()
[docs] def update_pinhole_smear(self):
"""
Method to be called by sub-classes
Moveit; This method doesn't belong here
"""
print("BasicPage.update_pinhole_smear was called: skipping")
return
def _read_category_info(self):
"""
Reads the categories in from file
"""
# # ILL mod starts here - July 2012 kieranrcampbell@gmail.com
self.master_category_dict = defaultdict(list)
self.by_model_dict = defaultdict(list)
self.model_enabled_dict = defaultdict(bool)
categorization_file = CategoryInstaller.get_user_file()
with open(categorization_file, 'rb') as f:
self.master_category_dict = json.load(f)
self._regenerate_model_dict()
def _regenerate_model_dict(self):
"""
regenerates self.by_model_dict which has each model name as the
key and the list of categories belonging to that model
along with the enabled mapping
"""
self.by_model_dict = defaultdict(list)
for category in self.master_category_dict:
for (model, enabled) in self.master_category_dict[category]:
self.by_model_dict[model].append(category)
self.model_enabled_dict[model] = enabled
def _populate_listbox(self):
"""
fills out the category list box
"""
uncat_str = 'Plugin Models'
self._read_category_info()
self.categorybox.Clear()
cat_list = sorted(self.master_category_dict.keys())
if uncat_str not in cat_list:
cat_list.append(uncat_str)
for category in cat_list:
if category != '':
self.categorybox.Append(category)
if self.categorybox.GetSelection() == wx.NOT_FOUND:
self.categorybox.SetSelection(0)
else:
self.categorybox.SetSelection(
self.categorybox.GetSelection())
# self._on_change_cat(None)
def _on_change_cat(self, event):
"""
Callback for category change action
"""
self.model_name = None
category = self.categorybox.GetStringSelection()
if category is None:
return
self.model_box.Clear()
if category == CUSTOM_MODEL:
for model in self.model_list_box[category]:
str_m = str(model).split(".")[0]
self.model_box.Append(str_m)
else:
for model, enabled in sorted(self.master_category_dict[category],
key=lambda name: name[0]):
if enabled:
self.model_box.Append(model)
def _fill_model_sizer(self, sizer):
"""
fill sizer containing model info
"""
# This should only be called once per fit tab
# print "==== Entering _fill_model_sizer"
# Add model function Details button in fitpanel.
# The following 3 lines are for Mac. Let JHC know before modifying...
title = "Model"
self.formfactorbox = None
self.multifactorbox = None
self.mbox_description = wx.StaticBox(self, wx.ID_ANY, str(title))
boxsizer1 = wx.StaticBoxSizer(self.mbox_description, wx.VERTICAL)
sizer_cat = wx.BoxSizer(wx.HORIZONTAL)
self.mbox_description.SetForegroundColour(wx.RED)
wx_id = self._ids.next()
self.model_func = wx.Button(self, wx_id, 'Help', size=(80, 23))
self.model_func.Bind(wx.EVT_BUTTON, self.on_function_help_clicked,
id=wx_id)
self.model_func.SetToolTipString("Full Model Function Help")
wx_id = self._ids.next()
self.model_help = wx.Button(self, wx_id, 'Description', size=(80, 23))
self.model_help.Bind(wx.EVT_BUTTON, self.on_model_help_clicked,
id=wx_id)
self.model_help.SetToolTipString("Short Model Function Description")
wx_id = self._ids.next()
self.model_view = wx.Button(self, wx_id, "Show 2D", size=(80, 23))
self.model_view.Bind(wx.EVT_BUTTON, self._onModel2D, id=wx_id)
hint = "toggle view of model from 1D to 2D or 2D to 1D"
self.model_view.SetToolTipString(hint)
cat_set_box = wx.StaticBox(self, wx.ID_ANY, 'Category')
sizer_cat_box = wx.StaticBoxSizer(cat_set_box, wx.HORIZONTAL)
sizer_cat_box.SetMinSize((200, 50))
self.categorybox = wx.ComboBox(self, wx.ID_ANY,
style=wx.CB_READONLY)
self.categorybox.SetToolTip(wx.ToolTip("Select a Category/Type"))
self._populate_listbox()
wx.EVT_COMBOBOX(self.categorybox, wx.ID_ANY, self._show_combox)
# self.shape_rbutton = wx.RadioButton(self, wx.ID_ANY, 'Shapes',
# style=wx.RB_GROUP)
# self.shape_indep_rbutton = wx.RadioButton(self, wx.ID_ANY,
# "Shape-Independent")
# self.struct_rbutton = wx.RadioButton(self, wx.ID_ANY,
# "Structure Factor ")
# self.plugin_rbutton = wx.RadioButton(self, wx.ID_ANY,
# "Uncategorized")
# self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
# id=self.shape_rbutton.GetId())
# self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
# id=self.shape_indep_rbutton.GetId())
# self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
# id=self.struct_rbutton.GetId())
# self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
# id=self.plugin_rbutton.GetId())
# MAC needs SetValue
show_cat_button = wx.Button(self, wx.ID_ANY, "Modify")
cat_tip = "Modify model categories \n"
cat_tip += "(also accessible from the menu bar)."
show_cat_button.SetToolTip(wx.ToolTip(cat_tip))
show_cat_button.Bind(wx.EVT_BUTTON, self._on_modify_cat)
sizer_cat_box.Add(self.categorybox, 1, wx.RIGHT, 3)
sizer_cat_box.Add((10, 10))
sizer_cat_box.Add(show_cat_button)
# self.shape_rbutton.SetValue(True)
sizer_radiobutton = wx.GridSizer(2, 2, 5, 5)
# sizer_radiobutton.Add(self.shape_rbutton)
# sizer_radiobutton.Add(self.shape_indep_rbutton)
sizer_radiobutton.Add((5, 5))
sizer_radiobutton.Add(self.model_view, 1, wx.RIGHT, 5)
# sizer_radiobutton.Add(self.plugin_rbutton)
# sizer_radiobutton.Add(self.struct_rbutton)
# sizer_radiobutton.Add((5,5))
sizer_radiobutton.Add(self.model_help, 1, wx.RIGHT | wx.LEFT, 5)
# sizer_radiobutton.Add((5,5))
sizer_radiobutton.Add(self.model_func, 1, wx.RIGHT, 5)
sizer_cat.Add(sizer_cat_box, 1, wx.LEFT, 2.5)
sizer_cat.Add(sizer_radiobutton)
sizer_selection = wx.BoxSizer(wx.HORIZONTAL)
mutifactor_selection = wx.BoxSizer(wx.HORIZONTAL)
self.text1 = wx.StaticText(self, wx.ID_ANY, "")
self.text2 = wx.StaticText(self, wx.ID_ANY, "P(Q)*S(Q)")
self.mutifactor_text = wx.StaticText(self, wx.ID_ANY, "No. of Shells: ")
self.mutifactor_text1 = wx.StaticText(self, wx.ID_ANY, "")
self.show_sld_button = wx.Button(self, wx.ID_ANY, "Show SLD Profile")
self.show_sld_button.Bind(wx.EVT_BUTTON, self._on_show_sld)
self.formfactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
self.formfactorbox.SetToolTip(wx.ToolTip("Select a Model"))
if self.model is not None:
self.formfactorbox.SetValue(self.model.name)
self.structurebox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
self.multifactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
self.initialize_combox()
wx.EVT_COMBOBOX(self.formfactorbox, wx.ID_ANY, self._on_select_model)
wx.EVT_COMBOBOX(self.structurebox, wx.ID_ANY, self._on_select_model)
wx.EVT_COMBOBOX(self.multifactorbox, wx.ID_ANY, self._on_select_model)
# check model type to show sizer
if self.model is not None:
print("_set_model_sizer_selection: disabled.")
# self._set_model_sizer_selection(self.model)
sizer_selection.Add(self.text1)
sizer_selection.Add((10, 5))
sizer_selection.Add(self.formfactorbox)
sizer_selection.Add((5, 5))
sizer_selection.Add(self.text2)
sizer_selection.Add((5, 5))
sizer_selection.Add(self.structurebox)
mutifactor_selection.Add((13, 5))
mutifactor_selection.Add(self.mutifactor_text)
mutifactor_selection.Add(self.multifactorbox)
mutifactor_selection.Add((5, 5))
mutifactor_selection.Add(self.mutifactor_text1)
mutifactor_selection.Add((10, 5))
mutifactor_selection.Add(self.show_sld_button)
boxsizer1.Add(sizer_cat)
boxsizer1.Add((10, 10))
boxsizer1.Add(sizer_selection)
boxsizer1.Add((10, 10))
boxsizer1.Add(mutifactor_selection)
self._set_multfactor_combobox()
self.multifactorbox.SetSelection(1)
self.show_sld_button.Hide()
sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
sizer.Layout()
[docs] def on_smear_helper(self, update=False):
"""
Help for onSmear if implemented
:param update: force or not to update
"""
[docs] def reset_page(self, state, first=False):
"""
reset the state if implemented
"""
[docs] def onSmear(self, event):
"""
Create a smear object if implemented
"""
[docs] def onPinholeSmear(self, event):
"""
Create a custom pinhole smear object if implemented
"""
[docs] def onSlitSmear(self, event):
"""
Create a custom slit smear object if implemented
"""
[docs] def update_slit_smear(self):
"""
called by kill_focus on pinhole TextCntrl
to update the changes if implemented
"""
[docs] def select_param(self, event):
"""
Select TextCtrl checked if implemented
"""
[docs] def set_data(self, data=None):
"""
Sets data if implemented
"""
def _is_2D(self):
"""
Check if data_name is Data2D if implemented
"""
def _on_select_model(self, event=None):
"""
call back for model selection if implemented
"""
[docs] def get_weight_flag(self):
"""
Get flag corresponding to a given weighting dI data if implemented
"""
def _set_sizer_dispersion(self):
"""
draw sizer for dispersity if implemented
"""
[docs] def get_all_checked_params(self):
"""
Found all parameters current check and add them to list of parameters
to fit if implemented
"""
[docs] def show_npts2fit(self):
"""
setValue Npts for fitting if implemented
"""
def _onModel2D(self, event):
"""
toggle view of model from 1D to 2D or 2D from 1D if implemented
"""
[docs]class ModelTextCtrl(wx.TextCtrl):
"""
Text control for model and fit parameters.
Binds the appropriate events for user interactions.
Default callback methods can be overwritten on initialization
:param kill_focus_callback: callback method for EVT_KILL_FOCUS event
:param set_focus_callback: callback method for EVT_SET_FOCUS event
:param mouse_up_callback: callback method for EVT_LEFT_UP event
:param text_enter_callback: callback method for EVT_TEXT_ENTER event
"""
# Set to True when the mouse is clicked while whole string is selected
full_selection = False
# Call back for EVT_SET_FOCUS events
_on_set_focus_callback = None
def __init__(self, parent, id=-1,
value=wx.EmptyString,
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=0,
validator=wx.DefaultValidator,
name=wx.TextCtrlNameStr,
kill_focus_callback=None,
set_focus_callback=None,
mouse_up_callback=None,
text_enter_callback=None):
wx.TextCtrl.__init__(self, parent, id, value, pos,
size, style, validator, name)
# Bind appropriate events
self._on_set_focus_callback = parent.onSetFocus \
if set_focus_callback is None else set_focus_callback
self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
self.Bind(wx.EVT_KILL_FOCUS, self._silent_kill_focus
if kill_focus_callback is None else kill_focus_callback)
self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter
if text_enter_callback is None else text_enter_callback)
if not ON_MAC:
self.Bind(wx.EVT_LEFT_UP, self._highlight_text
if mouse_up_callback is None else mouse_up_callback)
def _on_set_focus(self, event):
"""
Catch when the text control is set in focus to highlight the whole
text if necessary
:param event: mouse event
"""
event.Skip()
self.full_selection = True
return self._on_set_focus_callback(event)
def _highlight_text(self, event):
"""
Highlight text of a TextCtrl only of no text has be selected
:param event: mouse event
"""
# Make sure the mouse event is available to other listeners
event.Skip()
control = event.GetEventObject()
if self.full_selection:
self.full_selection = False
# Check that we have a TextCtrl
if issubclass(control.__class__, wx.TextCtrl):
# Check whether text has been selected,
# if not, select the whole string
(start, end) = control.GetSelection()
if start == end:
control.SetSelection(-1, -1)
def _silent_kill_focus(self, event):
"""
Save the state of the page
"""
event.Skip()
# pass