import sys import json import os import numpy from collections import defaultdict import logging import traceback from twisted.internet import threads from PyQt4 import QtGui from PyQt4 import QtCore from sasmodels import generate from sasmodels import modelinfo from sasmodels.sasview_model import load_standard_models from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller from sas.sasgui.guiframe.dataFitting import Data1D from sas.sasgui.guiframe.dataFitting import Data2D import sas.qtgui.GuiUtils as GuiUtils from sas.sascalc.dataloader.data_info import Detector from sas.sascalc.dataloader.data_info import Source from sas.sasgui.perspectives.fitting.model_thread import Calc1D from sas.sasgui.perspectives.fitting.model_thread import Calc2D from UI.FittingWidgetUI import Ui_FittingWidgetUI TAB_MAGNETISM = 4 TAB_POLY = 3 CATEGORY_DEFAULT = "Choose category..." QMIN_DEFAULT = 0.0005 QMAX_DEFAULT = 0.5 NPTS_DEFAULT = 50 class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI): """ Main widget for selecting form and structure factor models """ def __init__(self, parent=None, data=None, id=1): super(FittingWidget, self).__init__() # Necessary globals self.parent = parent # SasModel is loaded self.model_is_loaded = False # Data[12]D passed and set self.data_is_loaded = False # Current SasModel in view self.kernel_module = None # Current SasModel view dimension self.is2D = False # Current SasModel is multishell self.model_has_shells = False # Utility variable to enable unselectable option in category combobox self._previous_category_index = 0 # Utility variable for multishell display self._last_model_row = 0 # Dictionary of {model name: model class} for the current category self.models = {} # Which tab is this widget displayed in? self.tab_id = id # Range parameters self.q_range_min = QMIN_DEFAULT self.q_range_max = QMAX_DEFAULT self.npts = NPTS_DEFAULT # Main Data[12]D holder self._data = None # Main GUI setup up self.setupUi(self) self.setWindowTitle("Fitting") self.communicate = self.parent.communicate # Set the main models # We can't use a single model here, due to restrictions on flattening # the model tree with subclassed QAbstractProxyModel... self._model_model = QtGui.QStandardItemModel() self._poly_model = QtGui.QStandardItemModel() self._magnet_model = QtGui.QStandardItemModel() # Param model displayed in param list self.lstParams.setModel(self._model_model) self.readCategoryInfo() self.model_parameters = None self.lstParams.setAlternatingRowColors(True) # Poly model displayed in poly list self.lstPoly.setModel(self._poly_model) self.setPolyModel() self.setTableProperties(self.lstPoly) # Magnetism model displayed in magnetism list self.lstMagnetic.setModel(self._magnet_model) self.setMagneticModel() self.setTableProperties(self.lstMagnetic) # Defaults for the structure factors self.setDefaultStructureCombo() # Make structure factor and model CBs disabled self.disableModelCombo() self.disableStructureCombo() # Generate the category list for display category_list = sorted(self.master_category_dict.keys()) self.cbCategory.addItem(CATEGORY_DEFAULT) self.cbCategory.addItems(category_list) self.cbCategory.addItem("Structure Factor") self.cbCategory.setCurrentIndex(0) self._index = data if data is not None: self.data = data # Connect signals to controls self.initializeSignals() # Initial control state self.initializeControls() @property def data(self): return self._data @data.setter def data(self, value): """ data setter """ # _index contains the QIndex with data self._index = value # _data contains the actual Data[12]D self._data = GuiUtils.dataFromItem(value[0]) self.data_is_loaded = True # Tag along functionality self.updateQRange() self.cmdFit.setEnabled(True) def acceptsData(self): """ Tells the caller this widget can accept new dataset """ return not self.data_is_loaded def disableModelCombo(self): """ Disable the combobox """ self.cbModel.setEnabled(False) self.label_3.setEnabled(False) def enableModelCombo(self): """ Enable the combobox """ self.cbModel.setEnabled(True) self.label_3.setEnabled(True) def disableStructureCombo(self): """ Disable the combobox """ self.cbStructureFactor.setEnabled(False) self.label_4.setEnabled(False) def enableStructureCombo(self): """ Enable the combobox """ self.cbStructureFactor.setEnabled(True) self.label_4.setEnabled(True) def updateQRange(self): """ Updates Q Range display """ if self.data_is_loaded: self.q_range_min, self.q_range_max, self.npts = self.computeDataRange(self.data) # set Q range labels on the main tab self.lblMinRangeDef.setText(str(self.q_range_min)) self.lblMaxRangeDef.setText(str(self.q_range_max)) # set Q range labels on the options tab self.txtMaxRange.setText(str(self.q_range_max)) self.txtMinRange.setText(str(self.q_range_min)) self.txtNpts.setText(str(self.npts)) def initializeControls(self): """ Set initial control enablement """ self.cmdFit.setEnabled(False) self.cmdPlot.setEnabled(True) self.chkPolydispersity.setEnabled(True) self.chkPolydispersity.setCheckState(False) self.chk2DView.setEnabled(True) self.chk2DView.setCheckState(False) self.chkMagnetism.setEnabled(False) self.chkMagnetism.setCheckState(False) # Tabs self.tabFitting.setTabEnabled(TAB_POLY, False) self.tabFitting.setTabEnabled(TAB_MAGNETISM, False) self.lblChi2Value.setText("---") # Update Q Ranges self.updateQRange() def initializeSignals(self): """ Connect GUI element signals """ # Comboboxes self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor) self.cbCategory.currentIndexChanged.connect(self.onSelectCategory) self.cbModel.currentIndexChanged.connect(self.onSelectModel) # Checkboxes self.chk2DView.toggled.connect(self.toggle2D) self.chkPolydispersity.toggled.connect(self.togglePoly) self.chkMagnetism.toggled.connect(self.toggleMagnetism) # Buttons self.cmdFit.clicked.connect(self.onFit) self.cmdPlot.clicked.connect(self.onPlot) # Line edits self.txtNpts.textChanged.connect(self.onNpts) self.txtMinRange.textChanged.connect(self.onMinRange) self.txtMaxRange.textChanged.connect(self.onMaxRange) # Respond to change in parameters from the UI self._model_model.itemChanged.connect(self.updateParamsFromModel) self._poly_model.itemChanged.connect(self.onPolyModelChange) # TODO after the poly_model prototype accepted #self._magnet_model.itemChanged.connect(self.onMagneticModelChange) def setDefaultStructureCombo(self): """ Fill in the structure factors combo box with defaults """ structure_factor_list = self.master_category_dict.pop('Structure Factor') structure_factors = ["None"] self.cbStructureFactor.clear() structure_factors = [factor[0] for factor in structure_factor_list] self.cbStructureFactor.addItems(sorted(structure_factors)) def onSelectCategory(self): """ Select Category from list """ category = self.cbCategory.currentText() # Check if the user chose "Choose category entry" if str(category) == CATEGORY_DEFAULT: # if the previous category was not the default, keep it. # Otherwise, just return if self._previous_category_index != 0: self.cbCategory.setCurrentIndex(self._previous_category_index) return if category == "Structure Factor": self.disableModelCombo() self.enableStructureCombo() return # Safely clear and enable the model combo self.cbModel.blockSignals(True) self.cbModel.clear() self.cbModel.blockSignals(False) self.enableModelCombo() self.disableStructureCombo() self._previous_category_index = self.cbCategory.currentIndex() # Retrieve the list of models model_list = self.master_category_dict[str(category)] models = [] # Populate the models combobox for (model, _) in model_list: models.append(model) self.cbModel.addItems(sorted(models)) def onSelectModel(self): """ Respond to select Model from list event """ model = str(self.cbModel.currentText()) # SasModel -> QModel self.setModelModel(model) if self._index is None: # Create default datasets if no data passed if self.is2D: self.createDefault2dData() else: self.createDefault1dData() # DESIGN: create the theory now or on Plot event? #self.createTheoryIndex() else: # Create datasets and errorbars for current data if self.is2D: self.calculate2DForModel() else: self.calculate1DForModel() # TODO: attach the chart to index def onSelectStructureFactor(self): """ Select Structure Factor from list """ model = str(self.cbModel.currentText()) structure = str(self.cbStructureFactor.currentText()) self.setModelModel(model, structure_factor=structure) def readCategoryInfo(self): """ Reads the categories in from file """ self.master_category_dict = defaultdict(list) self.by_model_dict = defaultdict(list) self.model_enabled_dict = defaultdict(bool) categorization_file = CategoryInstaller.get_user_file() if not os.path.isfile(categorization_file): categorization_file = CategoryInstaller.get_default_file() with open(categorization_file, 'rb') as cat_file: self.master_category_dict = json.load(cat_file) self.regenerateModelDict() # Load the model dict models = load_standard_models() for model in models: self.models[model.name] = model def regenerateModelDict(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 getIterParams(self, model): """ Returns a list of all multi-shell parameters in 'model' """ return list(filter(lambda par: "[" in par.name, model.iq_parameters)) def getMultiplicity(self, model): """ Finds out if 'model' has multishell parameters. If so, returns the name of the counter parameter and the number of shells """ iter_params = self.getIterParams(model) # return the iterator parameter name and length return (iter_params[0].length_control if iter_params else "", iter_params[0].length if iter_params else 0) def addBackgroundToModel(self, model): """ Adds background parameter with default values to the model """ assert isinstance(model, QtGui.QStandardItemModel) checked_list = ['background', '0.001', '-inf', 'inf', '1/cm'] self.addCheckedListToModel(model, checked_list) def addScaleToModel(self, model): """ Adds scale parameter with default values to the model """ assert isinstance(model, QtGui.QStandardItemModel) checked_list = ['scale', '1.0', '0.0', 'inf', ''] self.addCheckedListToModel(model, checked_list) def addCheckedListToModel(self, model, param_list): """ Add a QItem to model. Makes the QItem checkable """ assert isinstance(model, QtGui.QStandardItemModel) item_list = [QtGui.QStandardItem(item) for item in param_list] item_list[0].setCheckable(True) model.appendRow(item_list) def addHeadersToModel(self, model): """ Adds predefined headers to the model """ model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter")) model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("Value")) model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min")) model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max")) model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("[Units]")) def addPolyHeadersToModel(self, model): """ Adds predefined headers to the model """ model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Parameter")) model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("PD[ratio]")) model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Min")) model.setHeaderData(3, QtCore.Qt.Horizontal, QtCore.QVariant("Max")) model.setHeaderData(4, QtCore.Qt.Horizontal, QtCore.QVariant("Npts")) model.setHeaderData(5, QtCore.Qt.Horizontal, QtCore.QVariant("Nsigs")) model.setHeaderData(6, QtCore.Qt.Horizontal, QtCore.QVariant("Function")) def setModelModel(self, model_name, structure_factor=None): """ Setting model parameters into table based on selected category """ # Crete/overwrite model items self._model_model.clear() kernel_module = generate.load_kernel_module(model_name) self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', [])) # Instantiate the current sasmodel self.kernel_module = self.models[model_name]() # Explicitly add scale and background with default values self.addScaleToModel(self._model_model) self.addBackgroundToModel(self._model_model) # Update the QModel self.addParametersToModel(self.model_parameters, self._model_model) self.addHeadersToModel(self._model_model) # Add structure factor if structure_factor is not None and structure_factor != "None": structure_module = generate.load_kernel_module(structure_factor) structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', [])) self.addSimpleParametersToModel(structure_parameters, self._model_model) else: self.addStructureFactor() # Multishell models need additional treatment self.addExtraShells() # Add polydispersity to the model self.setPolyModel() # Add magnetic parameters to the model self.setMagneticModel() # Now we claim the model has been loaded self.model_is_loaded = True # Update Q Ranges self.updateQRange() def onPolyModelChange(self, item): """ Callback method for updating the main model and sasmodel parameters with the GUI values in the polydispersity view """ model_column = item.column() model_row = item.row() name_index = self._poly_model.index(model_row, 0) # Extract changed value. Assumes proper validation by QValidator/Delegate value = float(item.text()) parameter_name = str(self._poly_model.data(name_index).toPyObject()) # "distribution of sld" etc. if "Distribution of" in parameter_name: parameter_name = parameter_name[16:] property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()) # Value, min, max, etc. print "%s(%s) => %d" % (parameter_name, property_name, value) # Update the sasmodel #self.kernel_module.params[parameter_name] = value # Reload the main model - may not be required if no variable is shown in main view #model = str(self.cbModel.currentText()) #self.setModelModel(model) pass # debug anchor def updateParamsFromModel(self, item): """ Callback method for updating the sasmodel parameters with the GUI values """ model_column = item.column() model_row = item.row() name_index = self._model_model.index(model_row, 0) if model_column == 0: # Assure we're dealing with checkboxes if not item.isCheckable(): return status = item.checkState() # If multiple rows selected - toggle all of them rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()] # Switch off signaling from the model to avoid multiple calls self._model_model.blockSignals(True) # Convert to proper indices and set requested enablement items = [self._model_model.item(row, 0).setCheckState(status) for row in rows] self._model_model.blockSignals(False) return # Extract changed value. Assumes proper validation by QValidator/Delegate value = float(item.text()) parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc. property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc. print "%s(%s) => %d" % (parameter_name, property_name, value) self.kernel_module.params[parameter_name] = value # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf] # magnetic params in self.kernel_module.details['M0:parameter_name'] = value # multishell params in self.kernel_module.details[??] = value def computeDataRange(self, data): """ Compute the minimum and the maximum range of the data return the npts contains in data """ assert data is not None assert (isinstance(data, Data1D) or isinstance(data, Data2D)) qmin, qmax, npts = None, None, None if isinstance(data, Data1D): try: qmin = min(data.x) qmax = max(data.x) npts = len(data.x) except (ValueError, TypeError): msg = "Unable to find min/max/length of \n data named %s" % \ data.filename raise ValueError, msg else: qmin = 0 try: x = max(numpy.fabs(data.xmin), numpy.fabs(data.xmax)) y = max(numpy.fabs(data.ymin), numpy.fabs(data.ymax)) except (ValueError, TypeError): msg = "Unable to find min/max of \n data named %s" % \ data.filename raise ValueError, msg qmax = numpy.sqrt(x * x + y * y) npts = len(data.data) return qmin, qmax, npts def addParametersToModel(self, parameters, model): """ Update local ModelModel with sasmodel parameters """ multishell_parameters = self.getIterParams(parameters) multishell_param_name, _ = self.getMultiplicity(parameters) for param in parameters.iq_parameters: # don't include shell parameters if param.name == multishell_param_name: continue # Modify parameter name from [n] to 1 item_name = param.name if param in multishell_parameters: item_name = self.replaceShellName(param.name, 1) item1 = QtGui.QStandardItem(item_name) item1.setCheckable(True) # check for polydisp params if param.polydisperse: poly_item = QtGui.QStandardItem("Polydispersity") item1_1 = QtGui.QStandardItem("Distribution") # Find param in volume_params for p in parameters.form_volume_parameters: if p.name != param.name: continue item1_2 = QtGui.QStandardItem(str(p.default)) item1_3 = QtGui.QStandardItem(str(p.limits[0])) item1_4 = QtGui.QStandardItem(str(p.limits[1])) item1_5 = QtGui.QStandardItem(p.units) poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5]) break # Add the polydisp item as a child item1.appendRow([poly_item]) # Param values item2 = QtGui.QStandardItem(str(param.default)) # TODO: the error column. # Either add a proxy model or a custom view delegate #item_err = QtGui.QStandardItem() item3 = QtGui.QStandardItem(str(param.limits[0])) item4 = QtGui.QStandardItem(str(param.limits[1])) item5 = QtGui.QStandardItem(param.units) model.appendRow([item1, item2, item3, item4, item5]) # Update the counter used for multishell display self._last_model_row = self._model_model.rowCount() def addSimpleParametersToModel(self, parameters, model): """ Update local ModelModel with sasmodel parameters """ for param in parameters.iq_parameters: # Modify parameter name from [n] to 1 item_name = param.name item1 = QtGui.QStandardItem(item_name) item1.setCheckable(True) # Param values item2 = QtGui.QStandardItem(str(param.default)) # TODO: the error column. # Either add a proxy model or a custom view delegate #item_err = QtGui.QStandardItem() item3 = QtGui.QStandardItem(str(param.limits[0])) item4 = QtGui.QStandardItem(str(param.limits[1])) item5 = QtGui.QStandardItem(param.units) model.appendRow([item1, item2, item3, item4, item5]) # Update the counter used for multishell display self._last_model_row = self._model_model.rowCount() def createDefault1dData(self): """ Create default data for fitting perspective Only when the page is on theory mode. """ x = numpy.linspace(start=self.q_range_min, stop=self.q_range_max, num=self.npts, 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.tab_id) + " data" self._data.group_id = str(self.tab_id) + " Model1D" def createDefault2dData(self): """ Create 2D data by default Only when the page is on theory mode. """ self._data = Data2D() qmax = self.q_range_max / numpy.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.tab_id) + " data" self._data.group_id = str(self.tab_id) + " Model2D" # Default detector 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 # theory default: assume the beam #center is located at the center of sqr detector xmax = qmax xmin = -qmax ymax = qmax ymin = -qmax qstep = self.npts x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True) y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True) # Use data info instead new_x = numpy.tile(x, (len(y), 1)) new_y = numpy.tile(y, (len(x), 1)) new_y = new_y.swapaxes(0, 1) # all data required in 1d array qx_data = new_x.flatten() qy_data = new_y.flatten() q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data) # set all True (standing for unmasked) as default mask = numpy.ones(len(qx_data), dtype=bool) # calculate the range of qx and qy: this way, # it is a little more independent # store x and y bin centers in q space x_bins = x y_bins = y self._data.source = Source() self._data.data = numpy.ones(len(mask)) self._data.err_data = numpy.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 def createTheoryIndex(self): """ Create a QStandardModelIndex containing default model data """ name = self.kernel_module.name if self.is2D: name += "2d" name = "M%i [%s]" % (self.tab_id, name) new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(self.data), name=name) # Notify the GUI manager so it can update the theory model in DataExplorer self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item) def onFit(self): """ Perform fitting on the current data """ #self.calculate1DForModel() pass def onPlot(self): """ Plot the current set of data """ # TODO: reimplement basepage.py/_update_paramv_on_fit if self.data is None or not self._data.is_data: self.createDefault2dData() if self.is2D else self.createDefault1dData() self.calculate2DForModel() if self.is2D else self.calculate1DForModel() def onNpts(self, text): """ Callback for number of points line edit update """ # assumes type/value correctness achieved with QValidator try: self.npts = int(text) except: pass def onMinRange(self, text): """ Callback for minimum range of points line edit update """ # assumes type/value correctness achieved with QValidator try: self.q_range_min = float(text) except: pass # set Q range labels on the main tab self.lblMinRangeDef.setText(str(self.q_range_min)) def onMaxRange(self, text): """ Callback for maximum range of points line edit update """ # assumes type/value correctness achieved with QValidator try: self.q_range_max = float(text) except: pass # set Q range labels on the main tab self.lblMaxRangeDef.setText(str(self.q_range_max)) def calculate1DForModel(self): """ Prepare the fitting data object, based on current ModelModel """ self.calc_1D = Calc1D(data=self.data, model=self.kernel_module, page_id=0, qmin=self.q_range_min, qmax=self.q_range_max, smearer=None, state=None, weight=None, fid=None, toggle_mode_on=False, completefn=self.complete1D, update_chisqr=True, exception_handler=self.calcException, source=None) # Instead of starting the thread with # self.calc_1D.queue() # let's try running the async request calc_thread = threads.deferToThread(self.calc_1D.compute) calc_thread.addCallback(self.complete1D) def complete1D(self, return_data): """ Plot the current data """ # Unpack return data from Calc1D x, y, page_id, state, weight,\ fid, toggle_mode_on, \ elapsed, index, model,\ data, update_chisqr, source = return_data # Create the new plot new_plot = Data1D(x=x, y=y) new_plot.is_data = False new_plot.dy = numpy.zeros(len(y)) _yaxis, _yunit = data.get_yaxis() _xaxis, _xunit = data.get_xaxis() new_plot.title = data.name new_plot.group_id = data.group_id if new_plot.group_id == None: new_plot.group_id = data.group_id new_plot.id = str(self.tab_id) + " " + data.name new_plot.name = model.name + " [" + data.name + "]" new_plot.xaxis(_xaxis, _xunit) new_plot.yaxis(_yaxis, _yunit) # Assign the new Data1D object-wide self._data = new_plot self.createTheoryIndex() #output=self._cal_chisqr(data=data, # fid=fid, # weight=weight, # page_id=page_id, # index=index) def calculate2DForModel(self): """ Prepare the fitting data object, based on current ModelModel """ self.calc_2D = Calc2D(data=self.data, model=self.kernel_module, page_id=0, qmin=self.q_range_min, qmax=self.q_range_max, smearer=None, state=None, weight=None, fid=None, toggle_mode_on=False, completefn=self.complete2D, update_chisqr=True, exception_handler=self.calcException, source=None) # Instead of starting the thread with # self.calc_2D.queue() # let's try running the async request calc_thread = threads.deferToThread(self.calc_2D.compute) calc_thread.addCallback(self.complete2D) def complete2D(self, return_data): """ Plot the current data Should be a rewrite of fitting.py/_complete2D """ image, data, page_id, model, state, toggle_mode_on,\ elapsed, index, fid, qmin, qmax, weight, \ update_chisqr, source = return_data numpy.nan_to_num(image) new_plot = Data2D(image=image, err_image=data.err_data) new_plot.name = model.name + '2d' new_plot.title = "Analytical model 2D " new_plot.id = str(page_id) + " " + data.name new_plot.group_id = str(page_id) + " Model2D" new_plot.detector = data.detector new_plot.source = data.source new_plot.is_data = False new_plot.qx_data = data.qx_data new_plot.qy_data = data.qy_data new_plot.q_data = data.q_data new_plot.mask = data.mask ## plot boundaries new_plot.ymin = data.ymin new_plot.ymax = data.ymax new_plot.xmin = data.xmin new_plot.xmax = data.xmax title = data.title new_plot.is_data = False if data.is_data: data_name = str(data.name) else: data_name = str(model.__class__.__name__) + '2d' if len(title) > 1: new_plot.title = "Model2D for %s " % model.name + data_name new_plot.name = model.name + " [" + \ data_name + "]" # Assign the new Data2D object-wide self._data = new_plot self.createTheoryIndex() #output=self._cal_chisqr(data=data, # weight=weight, # fid=fid, # page_id=page_id, # index=index) # self._plot_residuals(page_id=page_id, data=data, fid=fid, # index=index, weight=weight) def calcException(self, etype, value, tb): """ Something horrible happened in the deferred. Cry me a river. """ logging.error("".join(traceback.format_exception(etype, value, tb))) msg = traceback.format_exception(etype, value, tb, limit=1) def replaceShellName(self, param_name, value): """ Updates parameter name from [n_shell] to value """ assert '[' in param_name return param_name[:param_name.index('[')]+str(value) def setTableProperties(self, table): """ Setting table properties """ # Table properties table.verticalHeader().setVisible(False) table.setAlternatingRowColors(True) table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding) table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) table.resizeColumnsToContents() # Header header = table.horizontalHeader() header.setResizeMode(QtGui.QHeaderView.ResizeToContents) header.ResizeMode(QtGui.QHeaderView.Interactive) header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents) header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents) def setPolyModel(self): """ Set polydispersity values """ if not self.model_parameters: return self._poly_model.clear() for row, param in enumerate(self.model_parameters.form_volume_parameters): # Counters should not be included if not param.polydisperse: continue # Potential multishell params checked_list = ["Distribution of "+param.name, str(param.default), str(param.limits[0]), str(param.limits[1]), "35", "3", ""] self.addCheckedListToModel(self._poly_model, checked_list) #TODO: Need to find cleaner way to input functions func = QtGui.QComboBox() func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',]) func_index = self.lstPoly.model().index(row, 6) self.lstPoly.setIndexWidget(func_index, func) self.addPolyHeadersToModel(self._poly_model) def setMagneticModel(self): """ Set magnetism values on model """ if not self.model_parameters: return self._magnet_model.clear() for param in self.model_parameters.call_parameters: if param.type != "magnetic": continue checked_list = [param.name, str(param.default), str(param.limits[0]), str(param.limits[1]), param.units] self.addCheckedListToModel(self._magnet_model, checked_list) self.addHeadersToModel(self._magnet_model) def addStructureFactor(self): """ Add structure factors to the list of parameters """ if self.kernel_module.is_form_factor: self.enableStructureCombo() else: self.disableStructureCombo() def addExtraShells(self): """ Add a combobox for multiple shell display """ param_name, param_length = self.getMultiplicity(self.model_parameters) if param_length == 0: return # cell 1: variable name item1 = QtGui.QStandardItem(param_name) func = QtGui.QComboBox() func.addItems([str(i+1) for i in xrange(param_length)]) func.currentIndexChanged.connect(self.modifyShellsInList) # cell 2: combobox item2 = QtGui.QStandardItem() self._model_model.appendRow([item1, item2]) # Beautify the row: span columns 2-4 shell_row = self._model_model.rowCount() shell_index = self._model_model.index(shell_row-1, 1) self.lstParams.setIndexWidget(shell_index, func) self._last_model_row = self._model_model.rowCount() def modifyShellsInList(self, index): """ Add/remove additional multishell parameters """ # Find row location of the combobox last_row = self._last_model_row remove_rows = self._model_model.rowCount() - last_row if remove_rows > 1: self._model_model.removeRows(last_row, remove_rows) multishell_parameters = self.getIterParams(self.model_parameters) for i in xrange(index): for par in multishell_parameters: param_name = self.replaceShellName(par.name, i+2) #param_name = str(p.name) + str(i+2) item1 = QtGui.QStandardItem(param_name) item1.setCheckable(True) # check for polydisp params if par.polydisperse: poly_item = QtGui.QStandardItem("Polydispersity") item1_1 = QtGui.QStandardItem("Distribution") # Find param in volume_params for p in self.model_parameters.form_volume_parameters: if p.name != par.name: continue item1_2 = QtGui.QStandardItem(str(p.default)) item1_3 = QtGui.QStandardItem(str(p.limits[0])) item1_4 = QtGui.QStandardItem(str(p.limits[1])) item1_5 = QtGui.QStandardItem(p.units) poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5]) break item1.appendRow([poly_item]) item2 = QtGui.QStandardItem(str(par.default)) item3 = QtGui.QStandardItem(str(par.limits[0])) item4 = QtGui.QStandardItem(str(par.limits[1])) item5 = QtGui.QStandardItem(par.units) self._model_model.appendRow([item1, item2, item3, item4, item5]) def togglePoly(self, isChecked): """ Enable/disable the polydispersity tab """ self.tabFitting.setTabEnabled(TAB_POLY, isChecked) def toggleMagnetism(self, isChecked): """ Enable/disable the magnetism tab """ self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked) def toggle2D(self, isChecked): """ Enable/disable the controls dependent on 1D/2D data instance """ self.chkMagnetism.setEnabled(isChecked) self.is2D = isChecked