source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 10786bc2

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since 10786bc2 was 10786bc2, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Added missing types for (de)serialization. SASVIEW-984

  • Property mode set to 100644
File size: 47.7 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3Global defaults and various utility functions usable by the general GUI
4"""
5
6import os
7import re
8import sys
9import imp
10import warnings
11import webbrowser
12import urllib.parse
13import json
14from io import BytesIO
15
16import numpy as np
17
18warnings.simplefilter("ignore")
19import logging
20
21from PyQt5 import QtCore
22from PyQt5 import QtGui
23from PyQt5 import QtWidgets
24
25from periodictable import formula as Formula
26from sas.qtgui.Plotting import DataTransform
27from sas.qtgui.Plotting.ConvertUnits import convertUnit
28from sas.qtgui.Plotting.PlotterData import Data1D
29from sas.qtgui.Plotting.PlotterData import Data2D
30from sas.qtgui.Plotting.Plottables import Plottable
31from sas.sascalc.dataloader.data_info import Sample, Source, Vector
32from sas.sascalc.dataloader.data_info import Detector, Process, TransmissionSpectrum
33from sas.sascalc.dataloader.data_info import Aperture, Collimation
34from sas.qtgui.Plotting.Plottables import View
35from sas.qtgui.Plotting.Plottables import PlottableTheory1D
36from sas.qtgui.Plotting.Plottables import PlottableFit1D
37from sas.qtgui.Plotting.Plottables import Text
38from sas.qtgui.Plotting.Plottables import Chisq
39from sas.qtgui.MainWindow.DataState import DataState
40
41from sas.sascalc.fit.AbstractFitEngine import FResult
42from sas.sascalc.fit.AbstractFitEngine import FitData1D, FitData2D
43from sasmodels.sasview_model import SasviewModel
44
45from sas.sascalc.dataloader.loader import Loader
46from sas.qtgui.Utilities import CustomDir
47
48if os.path.splitext(sys.argv[0])[1].lower() != ".py":
49        HELP_DIRECTORY_LOCATION = "doc"
50else:
51        HELP_DIRECTORY_LOCATION = "docs/sphinx-docs/build/html"
52IMAGES_DIRECTORY_LOCATION = HELP_DIRECTORY_LOCATION + "/_images"
53
54# This matches the ID of a plot created using FittingLogic._create1DPlot, e.g.
55# "5 [P(Q)] modelname"
56# or
57# "4 modelname".
58# Useful for determining whether the plot in question is for an intermediate result, such as P(Q) or S(Q) in the
59# case of a product model; the identifier for this is held in square brackets, as in the example above.
60theory_plot_ID_pattern = re.compile(r"^([0-9]+)\s+(\[(.*)\]\s+)?(.*)$")
61
62def get_app_dir():
63    """
64        The application directory is the one where the default custom_config.py
65        file resides.
66
67        :returns: app_path - the path to the applicatin directory
68    """
69    # First, try the directory of the executable we are running
70    app_path = sys.path[0]
71    if os.path.isfile(app_path):
72        app_path = os.path.dirname(app_path)
73    if os.path.isfile(os.path.join(app_path, "custom_config.py")):
74        app_path = os.path.abspath(app_path)
75        #logging.info("Using application path: %s", app_path)
76        return app_path
77
78    # Next, try the current working directory
79    if os.path.isfile(os.path.join(os.getcwd(), "custom_config.py")):
80        #logging.info("Using application path: %s", os.getcwd())
81        return os.path.abspath(os.getcwd())
82
83    # Finally, try the directory of the sasview module
84    # TODO: gui_manager will have to know about sasview until we
85    # clean all these module variables and put them into a config class
86    # that can be passed by sasview.py.
87    # logging.info(sys.executable)
88    # logging.info(str(sys.argv))
89    from sas import sasview as sasview
90    app_path = os.path.dirname(sasview.__file__)
91    # logging.info("Using application path: %s", app_path)
92    return app_path
93
94def get_user_directory():
95    """
96        Returns the user's home directory
97    """
98    userdir = os.path.join(os.path.expanduser("~"), ".sasview")
99    if not os.path.isdir(userdir):
100        os.makedirs(userdir)
101    return userdir
102
103def _find_local_config(confg_file, path):
104    """
105        Find configuration file for the current application
106    """
107    config_module = None
108    fObj = None
109    try:
110        fObj, path_config, descr = imp.find_module(confg_file, [path])
111        config_module = imp.load_module(confg_file, fObj, path_config, descr)
112    except ImportError:
113        pass
114    except ValueError:
115        print("Value error")
116        pass
117    finally:
118        if fObj is not None:
119            fObj.close()
120    return config_module
121
122
123# Get APP folder
124PATH_APP = get_app_dir()
125DATAPATH = PATH_APP
126
127# Read in the local config, which can either be with the main
128# application or in the installation directory
129config = _find_local_config('local_config', PATH_APP)
130
131if config is None:
132    config = _find_local_config('local_config', os.getcwd())
133else:
134    pass
135
136c_conf_dir = CustomDir.setup_conf_dir(PATH_APP)
137
138custom_config = _find_local_config('custom_config', c_conf_dir)
139if custom_config is None:
140    custom_config = _find_local_config('custom_config', os.getcwd())
141    if custom_config is None:
142        msgConfig = "Custom_config file was not imported"
143
144#read some constants from config
145APPLICATION_STATE_EXTENSION = config.APPLICATION_STATE_EXTENSION
146APPLICATION_NAME = config.__appname__
147SPLASH_SCREEN_PATH = config.SPLASH_SCREEN_PATH
148WELCOME_PANEL_ON = config.WELCOME_PANEL_ON
149SPLASH_SCREEN_WIDTH = config.SPLASH_SCREEN_WIDTH
150SPLASH_SCREEN_HEIGHT = config.SPLASH_SCREEN_HEIGHT
151SS_MAX_DISPLAY_TIME = config.SS_MAX_DISPLAY_TIME
152if not WELCOME_PANEL_ON:
153    WELCOME_PANEL_SHOW = False
154else:
155    WELCOME_PANEL_SHOW = True
156try:
157    DATALOADER_SHOW = custom_config.DATALOADER_SHOW
158    TOOLBAR_SHOW = custom_config.TOOLBAR_SHOW
159    FIXED_PANEL = custom_config.FIXED_PANEL
160    if WELCOME_PANEL_ON:
161        WELCOME_PANEL_SHOW = custom_config.WELCOME_PANEL_SHOW
162    PLOPANEL_WIDTH = custom_config.PLOPANEL_WIDTH
163    DATAPANEL_WIDTH = custom_config.DATAPANEL_WIDTH
164    GUIFRAME_WIDTH = custom_config.GUIFRAME_WIDTH
165    GUIFRAME_HEIGHT = custom_config.GUIFRAME_HEIGHT
166    CONTROL_WIDTH = custom_config.CONTROL_WIDTH
167    CONTROL_HEIGHT = custom_config.CONTROL_HEIGHT
168    DEFAULT_PERSPECTIVE = custom_config.DEFAULT_PERSPECTIVE
169    CLEANUP_PLOT = custom_config.CLEANUP_PLOT
170    SAS_OPENCL = custom_config.SAS_OPENCL
171    # custom open_path
172    open_folder = custom_config.DEFAULT_OPEN_FOLDER
173    if open_folder is not None and os.path.isdir(open_folder):
174        DEFAULT_OPEN_FOLDER = os.path.abspath(open_folder)
175    else:
176        DEFAULT_OPEN_FOLDER = PATH_APP
177except AttributeError:
178    DATALOADER_SHOW = True
179    TOOLBAR_SHOW = True
180    FIXED_PANEL = True
181    WELCOME_PANEL_SHOW = False
182    PLOPANEL_WIDTH = config.PLOPANEL_WIDTH
183    DATAPANEL_WIDTH = config.DATAPANEL_WIDTH
184    GUIFRAME_WIDTH = config.GUIFRAME_WIDTH
185    GUIFRAME_HEIGHT = config.GUIFRAME_HEIGHT
186    CONTROL_WIDTH = -1
187    CONTROL_HEIGHT = -1
188    DEFAULT_PERSPECTIVE = None
189    CLEANUP_PLOT = False
190    DEFAULT_OPEN_FOLDER = PATH_APP
191    SAS_OPENCL = config.SAS_OPENCL
192
193#DEFAULT_STYLE = config.DEFAULT_STYLE
194
195PLUGIN_STATE_EXTENSIONS = config.PLUGIN_STATE_EXTENSIONS
196OPEN_SAVE_MENU = config.OPEN_SAVE_PROJECT_MENU
197VIEW_MENU = config.VIEW_MENU
198EDIT_MENU = config.EDIT_MENU
199extension_list = []
200if APPLICATION_STATE_EXTENSION is not None:
201    extension_list.append(APPLICATION_STATE_EXTENSION)
202EXTENSIONS = PLUGIN_STATE_EXTENSIONS + extension_list
203try:
204    PLUGINS_WLIST = '|'.join(config.PLUGINS_WLIST)
205except AttributeError:
206    PLUGINS_WLIST = ''
207APPLICATION_WLIST = config.APPLICATION_WLIST
208IS_WIN = True
209IS_LINUX = False
210CLOSE_SHOW = True
211TIME_FACTOR = 2
212NOT_SO_GRAPH_LIST = ["BoxSum"]
213
214
215class Communicate(QtCore.QObject):
216    """
217    Utility class for tracking of the Qt signals
218    """
219    # File got successfully read
220    fileReadSignal = QtCore.pyqtSignal(list)
221
222    # Open File returns "list" of paths
223    fileDataReceivedSignal = QtCore.pyqtSignal(dict)
224
225    # Update Main window status bar with "str"
226    # Old "StatusEvent"
227    statusBarUpdateSignal = QtCore.pyqtSignal(str)
228
229    # Send data to the current perspective
230    updatePerspectiveWithDataSignal = QtCore.pyqtSignal(list)
231
232    # New data in current perspective
233    updateModelFromPerspectiveSignal = QtCore.pyqtSignal(QtGui.QStandardItem)
234
235    # New theory data in current perspective
236    updateTheoryFromPerspectiveSignal = QtCore.pyqtSignal(QtGui.QStandardItem)
237
238    # Request to delete plots (in the theory view) related to a given model ID
239    deleteIntermediateTheoryPlotsSignal = QtCore.pyqtSignal(str)
240
241    # New plot requested from the GUI manager
242    # Old "NewPlotEvent"
243    plotRequestedSignal = QtCore.pyqtSignal(list, int)
244
245    # Plot from file names
246    plotFromFilenameSignal = QtCore.pyqtSignal(str)
247
248    # Plot update requested from a perspective
249    plotUpdateSignal = QtCore.pyqtSignal(list)
250
251    # Progress bar update value
252    progressBarUpdateSignal = QtCore.pyqtSignal(int)
253
254    # Workspace charts added/removed
255    activeGraphsSignal = QtCore.pyqtSignal(list)
256
257    # Current workspace chart's name changed
258    activeGraphName = QtCore.pyqtSignal(tuple)
259
260    # Current perspective changed
261    perspectiveChangedSignal = QtCore.pyqtSignal(str)
262
263    # File/dataset got deleted
264    dataDeletedSignal = QtCore.pyqtSignal(list)
265
266    # Send data to Data Operation Utility panel
267    sendDataToPanelSignal = QtCore.pyqtSignal(dict)
268
269    # Send result of Data Operation Utility panel to Data Explorer
270    updateModelFromDataOperationPanelSignal = QtCore.pyqtSignal(QtGui.QStandardItem, dict)
271
272    # Notify about a new custom plugin being written/deleted/modified
273    customModelDirectoryChanged = QtCore.pyqtSignal()
274
275    # Notify the gui manager about new data to be added to the grid view
276    sendDataToGridSignal = QtCore.pyqtSignal(list)
277
278    # Mask Editor requested
279    maskEditorSignal = QtCore.pyqtSignal(Data2D)
280
281    #second Mask Editor for external
282    extMaskEditorSignal = QtCore.pyqtSignal()
283
284    # Fitting parameter copy to clipboard
285    copyFitParamsSignal = QtCore.pyqtSignal(str)
286
287    # Fitting parameter copy to clipboard for Excel
288    copyExcelFitParamsSignal = QtCore.pyqtSignal(str)
289
290    # Fitting parameter copy to clipboard for Latex
291    copyLatexFitParamsSignal = QtCore.pyqtSignal(str)
292
293    # Fitting parameter paste from clipboard
294    pasteFitParamsSignal = QtCore.pyqtSignal()
295
296    # Notify about new categories/models from category manager
297    updateModelCategoriesSignal = QtCore.pyqtSignal()
298
299    # Tell the data explorer to switch tabs
300    changeDataExplorerTabSignal = QtCore.pyqtSignal(int)
301
302    # Plot fitting results (FittingWidget->GuiManager)
303    resultPlotUpdateSignal = QtCore.pyqtSignal(list)
304
305    # show the plot as a regular in-workspace object
306    forcePlotDisplaySignal = QtCore.pyqtSignal(list)
307
308def updateModelItemWithPlot(item, update_data, name="", checkbox_state=None):
309    """
310    Adds a checkboxed row named "name" to QStandardItem
311    Adds 'update_data' to that row.
312    """
313    assert isinstance(item, QtGui.QStandardItem)
314
315    # Check if data with the same ID is already present
316    for index in range(item.rowCount()):
317        plot_item = item.child(index)
318        if not plot_item.isCheckable():
319            continue
320        plot_data = plot_item.child(0).data()
321        if plot_data.id is not None and \
322                plot_data.name == update_data.name:
323                #(plot_data.name == update_data.name or plot_data.id == update_data.id):
324            # if plot_data.id is not None and plot_data.id == update_data.id:
325            # replace data section in item
326            plot_item.child(0).setData(update_data)
327            plot_item.setText(name)
328            # Plot title if any
329            if plot_item.child(1).hasChildren():
330                plot_item.child(1).child(0).setText("Title: %s"%name)
331            # Force redisplay
332            return
333    # Create the new item
334    checkbox_item = createModelItemWithPlot(update_data, name)
335
336    if checkbox_state is not None:
337        checkbox_item.setCheckState(checkbox_state)
338    # Append the new row to the main item
339    item.appendRow(checkbox_item)
340
341def deleteRedundantPlots(item, new_plots):
342    """
343    Checks all plots that are children of the given item; if any have an ID or name not included in new_plots,
344    it is deleted. Useful for e.g. switching from P(Q)S(Q) to P(Q); this would remove the old S(Q) plot.
345
346    Ensure that new_plots contains ALL the relevant plots(!!!)
347    """
348    assert isinstance(item, QtGui.QStandardItem)
349
350    # lists of plots names/ids for all deletable plots on item
351    names = [p.name for p in new_plots if p.name is not None]
352    ids = [p.id for p in new_plots if p.id is not None]
353
354    items_to_delete = []
355
356    for index in range(item.rowCount()):
357        plot_item = item.child(index)
358        if not plot_item.isCheckable():
359            continue
360        plot_data = plot_item.child(0).data()
361        if (plot_data.id is not None) and \
362            (plot_data.id not in ids) and \
363            (plot_data.name not in names) and \
364            (plot_data.plot_role == Data1D.ROLE_DELETABLE):
365            items_to_delete.append(plot_item)
366
367    for plot_item in items_to_delete:
368        item.removeRow(plot_item.row())
369
370class HashableStandardItem(QtGui.QStandardItem):
371    """
372    Subclassed standard item with reimplemented __hash__
373    to allow for use as an index.
374    """
375    def __init__(self, parent=None):
376        super(HashableStandardItem, self).__init__()
377
378    def __hash__(self):
379        ''' just a random hash value '''
380        #return hash(self.__init__)
381        return 0
382
383    def clone(self):
384        ''' Assure __hash__ is cloned as well'''
385        clone = super(HashableStandardItem, self).clone()
386        clone.__hash__ = self.__hash__
387        return clone
388
389def getMonospaceFont():
390    """Convenience function; returns a monospace font to be used in any shells, code editors, etc."""
391
392    # Note: Consolas is only available on Windows; the style hint is used on other operating systems
393    font = QtGui.QFont("Consolas", 10)
394    font.setStyleHint(QtGui.QFont.Monospace, QtGui.QFont.PreferQuality)
395    return font
396
397def createModelItemWithPlot(update_data, name=""):
398    """
399    Creates a checkboxed QStandardItem named "name"
400    Adds 'update_data' to that row.
401    """
402    py_update_data = update_data
403
404    checkbox_item = HashableStandardItem()
405    checkbox_item.setCheckable(True)
406    checkbox_item.setCheckState(QtCore.Qt.Checked)
407    checkbox_item.setText(name)
408
409    # Add "Info" item
410    if isinstance(py_update_data, (Data1D, Data2D)):
411        # If Data1/2D added - extract Info from it
412        info_item = infoFromData(py_update_data)
413    else:
414        # otherwise just add a naked item
415        info_item = QtGui.QStandardItem("Info")
416
417    # Add the actual Data1D/Data2D object
418    object_item = QtGui.QStandardItem()
419    object_item.setData(update_data)
420
421    # Set the data object as the first child
422    checkbox_item.setChild(0, object_item)
423
424    # Set info_item as the second child
425    checkbox_item.setChild(1, info_item)
426
427    # And return the newly created item
428    return checkbox_item
429
430def updateModelItem(item, update_data, name=""):
431    """
432    Adds a simple named child to QStandardItem
433    """
434    assert isinstance(item, QtGui.QStandardItem)
435
436    # Add the actual Data1D/Data2D object
437    object_item = QtGui.QStandardItem()
438    object_item.setText(name)
439    object_item.setData(update_data)
440
441    # Append the new row to the main item
442    item.appendRow(object_item)
443
444def updateModelItemStatus(model_item, filename="", name="", status=2):
445    """
446    Update status of checkbox related to high- and low-Q extrapolation
447    choice in Invariant Panel
448    """
449    assert isinstance(model_item, QtGui.QStandardItemModel)
450
451    # Iterate over model looking for items with checkboxes
452    for index in range(model_item.rowCount()):
453        item = model_item.item(index)
454        if item.text() == filename and item.isCheckable() \
455                and item.checkState() == QtCore.Qt.Checked:
456            # Going 1 level deeper only
457            for index_2 in range(item.rowCount()):
458                item_2 = item.child(index_2)
459                if item_2 and item_2.isCheckable() and item_2.text() == name:
460                    item_2.setCheckState(status)
461
462    return
463
464def itemFromFilename(filename, model_item):
465    """
466    Returns the model item text=filename in the model
467    """
468    assert isinstance(model_item, QtGui.QStandardItemModel)
469    assert isinstance(filename, str)
470
471    # Iterate over model looking for named items
472    item = list([i for i in [model_item.item(index)
473                             for index in range(model_item.rowCount())]
474                 if str(i.text()) == filename])
475    return item[0] if len(item)>0 else None
476
477def plotsFromModel(model_name, model_item):
478    """
479    Returns the list of plots for the item with model name in the model
480    """
481    assert isinstance(model_item, QtGui.QStandardItem)
482    assert isinstance(model_name, str)
483
484    plot_data = []
485    # Iterate over model looking for named items
486    for index in range(model_item.rowCount()):
487        item = model_item.child(index)
488        if isinstance(item.data(), (Data1D, Data2D)):
489            plot_data.append(item.data())
490        if model_name in str(item.text()):
491            #plot_data.append(item.child(0).data())
492            # Going 1 level deeper only
493            for index_2 in range(item.rowCount()):
494                item_2 = item.child(index_2)
495                if item_2 and isinstance(item_2.data(), (Data1D, Data2D)):
496                    plot_data.append(item_2.data())
497
498    return plot_data
499
500def plotsFromFilename(filename, model_item):
501    """
502    Returns the list of plots for the item with text=filename in the model
503    """
504    assert isinstance(model_item, QtGui.QStandardItemModel)
505    assert isinstance(filename, str)
506
507    plot_data = {}
508    # Iterate over model looking for named items
509    for index in range(model_item.rowCount()):
510        item = model_item.item(index)
511        if filename in str(item.text()):
512            # TODO: assure item type is correct (either data1/2D or Plotter)
513            plot_data[item] = item.child(0).data()
514            # Going 1 level deeper only
515            for index_2 in range(item.rowCount()):
516                item_2 = item.child(index_2)
517                if item_2 and item_2.isCheckable():
518                    # TODO: assure item type is correct (either data1/2D or Plotter)
519                    plot_data[item_2] = item_2.child(0).data()
520
521    return plot_data
522
523def getChildrenFromItem(root):
524    """
525    Recursively go down the model item looking for all children
526    """
527    def recurse(parent):
528        for row in range(parent.rowCount()):
529            for column in range(parent.columnCount()):
530                child = parent.child(row, column)
531                yield child
532                if child.hasChildren():
533                    yield from recurse(child)
534    if root is not None:
535        yield from recurse(root)
536
537def plotsFromCheckedItems(model_item):
538    """
539    Returns the list of plots for items in the model which are checked
540    """
541    assert isinstance(model_item, QtGui.QStandardItemModel)
542
543    plot_data = []
544
545    # Iterate over model looking for items with checkboxes
546    for index in range(model_item.rowCount()):
547        item = model_item.item(index)
548        if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
549            data = item.child(0).data()
550            plot_data.append((item, data))
551
552        items = list(getChildrenFromItem(item))
553
554        for it in items:
555            if it.isCheckable() and it.checkState() == QtCore.Qt.Checked:
556                data = it.child(0).data()
557                plot_data.append((it, data))
558
559    return plot_data
560
561def infoFromData(data):
562    """
563    Given Data1D/Data2D object, extract relevant Info elements
564    and add them to a model item
565    """
566    assert isinstance(data, (Data1D, Data2D))
567
568    info_item = QtGui.QStandardItem("Info")
569
570    title_item = QtGui.QStandardItem("Title: " + data.title)
571    info_item.appendRow(title_item)
572    run_item = QtGui.QStandardItem("Run: " + str(data.run))
573    info_item.appendRow(run_item)
574    type_item = QtGui.QStandardItem("Type: " + str(data.__class__.__name__))
575    info_item.appendRow(type_item)
576
577    if data.path:
578        path_item = QtGui.QStandardItem("Path: " + data.path)
579        info_item.appendRow(path_item)
580
581    if data.instrument:
582        instr_item = QtGui.QStandardItem("Instrument: " + data.instrument)
583        info_item.appendRow(instr_item)
584
585    process_item = QtGui.QStandardItem("Process")
586    if isinstance(data.process, list) and data.process:
587        for process in data.process:
588            if process is None:
589                continue
590            process_date = process.date
591            process_date_item = QtGui.QStandardItem("Date: " + process_date)
592            process_item.appendRow(process_date_item)
593
594            process_descr = process.description
595            process_descr_item = QtGui.QStandardItem("Description: " + process_descr)
596            process_item.appendRow(process_descr_item)
597
598            process_name = process.name
599            process_name_item = QtGui.QStandardItem("Name: " + process_name)
600            process_item.appendRow(process_name_item)
601
602    info_item.appendRow(process_item)
603
604    return info_item
605
606def dataFromItem(item):
607    """
608    Retrieve Data1D/2D component from QStandardItem.
609    The assumption - data stored in SasView standard, in child 0
610    """
611    try:
612        data = item.child(0).data()
613    except AttributeError:
614        data = None
615    return data
616
617def openLink(url):
618    """
619    Open a URL in an external browser.
620    Check the URL first, though.
621    """
622    parsed_url = urllib.parse.urlparse(url)
623    if parsed_url.scheme:
624        webbrowser.open(url)
625    else:
626        msg = "Attempt at opening an invalid URL"
627        raise AttributeError(msg)
628
629def showHelp(url):
630    """
631    Open a local url in the default browser
632    """
633    location = HELP_DIRECTORY_LOCATION + url
634    #WP: Added to handle OSX bundle docs
635    if os.path.isdir(location) == False:
636        sas_path = os.path.abspath(os.path.dirname(sys.argv[0]))
637        location = sas_path+"/"+location
638    try:
639        webbrowser.open('file://' + os.path.realpath(location))
640    except webbrowser.Error as ex:
641        logging.warning("Cannot display help. %s" % ex)
642
643def retrieveData1d(data):
644    """
645    Retrieve 1D data from file and construct its text
646    representation
647    """
648    if not isinstance(data, Data1D):
649        msg = "Incorrect type passed to retrieveData1d"
650        raise AttributeError(msg)
651    try:
652        xmin = min(data.x)
653        ymin = min(data.y)
654    except:
655        msg = "Unable to find min/max of \n data named %s" % \
656                    data.filename
657        #logging.error(msg)
658        raise ValueError(msg)
659
660    text = data.__str__()
661    text += 'Data Min Max:\n'
662    text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
663    text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
664    if data.dy is not None:
665        text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
666    text += '\nData Points:\n'
667    x_st = "X"
668    for index in range(len(data.x)):
669        if data.dy is not None and len(data.dy) > index:
670            dy_val = data.dy[index]
671        else:
672            dy_val = 0.0
673        if data.dx is not None and len(data.dx) > index:
674            dx_val = data.dx[index]
675        else:
676            dx_val = 0.0
677        if data.dxl is not None and len(data.dxl) > index:
678            if index == 0:
679                x_st = "Xl"
680            dx_val = data.dxl[index]
681        elif data.dxw is not None and len(data.dxw) > index:
682            if index == 0:
683                x_st = "Xw"
684            dx_val = data.dxw[index]
685
686        if index == 0:
687            text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
688        text += "%s \t%s \t%s \t%s \t%s\n" % (index,
689                                                data.x[index],
690                                                data.y[index],
691                                                dy_val,
692                                                dx_val)
693    return text
694
695def retrieveData2d(data):
696    """
697    Retrieve 2D data from file and construct its text
698    representation
699    """
700    if not isinstance(data, Data2D):
701        msg = "Incorrect type passed to retrieveData2d"
702        raise AttributeError(msg)
703
704    text = data.__str__()
705    text += 'Data Min Max:\n'
706    text += 'I_min = %s\n' % min(data.data)
707    text += 'I_max = %s\n\n' % max(data.data)
708    text += 'Data (First 2501) Points:\n'
709    text += 'Data columns include err(I).\n'
710    text += 'ASCII data starts here.\n'
711    text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
712    di_val = 0.0
713    dx_val = 0.0
714    dy_val = 0.0
715    len_data = len(data.qx_data)
716    for index in range(0, len_data):
717        x_val = data.qx_data[index]
718        y_val = data.qy_data[index]
719        i_val = data.data[index]
720        if data.err_data is not None:
721            di_val = data.err_data[index]
722        if data.dqx_data is not None:
723            dx_val = data.dqx_data[index]
724        if data.dqy_data is not None:
725            dy_val = data.dqy_data[index]
726
727        text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
728                                                        x_val,
729                                                        y_val,
730                                                        i_val,
731                                                        di_val,
732                                                        dx_val,
733                                                        dy_val)
734        # Takes too long time for typical data2d: Break here
735        if index >= 2500:
736            text += ".............\n"
737            break
738
739    return text
740
741def onTXTSave(data, path):
742    """
743    Save file as formatted txt
744    """
745    with open(path,'w') as out:
746        has_errors = True
747        if data.dy is None or not data.dy.any():
748            has_errors = False
749        # Sanity check
750        if has_errors:
751            try:
752                if len(data.y) != len(data.dy):
753                    has_errors = False
754            except:
755                has_errors = False
756        if has_errors:
757            if data.dx is not None and data.dx.any():
758                out.write("<X>   <Y>   <dY>   <dX>\n")
759            else:
760                out.write("<X>   <Y>   <dY>\n")
761        else:
762            out.write("<X>   <Y>\n")
763
764        for i in range(len(data.x)):
765            if has_errors:
766                if data.dx is not None and data.dx.any():
767                    if  data.dx[i] is not None:
768                        out.write("%g  %g  %g  %g\n" % (data.x[i],
769                                                        data.y[i],
770                                                        data.dy[i],
771                                                        data.dx[i]))
772                    else:
773                        out.write("%g  %g  %g\n" % (data.x[i],
774                                                    data.y[i],
775                                                    data.dy[i]))
776                else:
777                    out.write("%g  %g  %g\n" % (data.x[i],
778                                                data.y[i],
779                                                data.dy[i]))
780            else:
781                out.write("%g  %g\n" % (data.x[i],
782                                        data.y[i]))
783
784def saveData1D(data):
785    """
786    Save 1D data points
787    """
788    default_name = os.path.basename(data.filename)
789    default_name, extension = os.path.splitext(default_name)
790    if not extension:
791        extension = ".txt"
792    default_name += "_out" + extension
793
794    wildcard = "Text files (*.txt);;"\
795                "CanSAS 1D files(*.xml)"
796    kwargs = {
797        'caption'   : 'Save As',
798        'directory' : default_name,
799        'filter'    : wildcard,
800        'parent'    : None,
801    }
802    # Query user for filename.
803    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
804    filename = filename_tuple[0]
805
806    # User cancelled.
807    if not filename:
808        return
809
810    #Instantiate a loader
811    loader = Loader()
812    if os.path.splitext(filename)[1].lower() == ".txt":
813        onTXTSave(data, filename)
814    if os.path.splitext(filename)[1].lower() == ".xml":
815        loader.save(filename, data, ".xml")
816
817def saveData2D(data):
818    """
819    Save data2d dialog
820    """
821    default_name = os.path.basename(data.filename)
822    default_name, _ = os.path.splitext(default_name)
823    ext_format = ".dat"
824    default_name += "_out" + ext_format
825
826    wildcard = "IGOR/DAT 2D file in Q_map (*.dat)"
827    kwargs = {
828        'caption'   : 'Save As',
829        'directory' : default_name,
830        'filter'    : wildcard,
831        'parent'    : None,
832    }
833    # Query user for filename.
834    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
835    filename = filename_tuple[0]
836
837    # User cancelled.
838    if not filename:
839        return
840
841    #Instantiate a loader
842    loader = Loader()
843
844    if os.path.splitext(filename)[1].lower() == ext_format:
845        loader.save(filename, data, ext_format)
846
847class FormulaValidator(QtGui.QValidator):
848    def __init__(self, parent=None):
849        super(FormulaValidator, self).__init__(parent)
850 
851    def validate(self, input, pos):
852
853        self._setStyleSheet("")
854        return QtGui.QValidator.Acceptable, pos
855
856        #try:
857        #    Formula(str(input))
858        #    self._setStyleSheet("")
859        #    return QtGui.QValidator.Acceptable, pos
860
861        #except Exception as e:
862        #    self._setStyleSheet("background-color:pink;")
863        #    return QtGui.QValidator.Intermediate, pos
864
865    def _setStyleSheet(self, value):
866        try:
867            if self.parent():
868                self.parent().setStyleSheet(value)
869        except:
870            pass
871
872def xyTransform(data, xLabel="", yLabel=""):
873    """
874    Transforms x and y in View and set the scale
875    """
876    # Changing the scale might be incompatible with
877    # currently displayed data (for instance, going
878    # from ln to log when all plotted values have
879    # negative natural logs).
880    # Go linear and only change the scale at the end.
881    xscale = 'linear'
882    yscale = 'linear'
883    # Local data is either 1D or 2D
884    if data.id == 'fit':
885        return
886
887    # make sure we have some function to operate on
888    if xLabel is None:
889        xLabel = 'log10(x)'
890    if yLabel is None:
891        yLabel = 'log10(y)'
892
893    # control axis labels from the panel itself
894    yname, yunits = data.get_yaxis()
895    xname, xunits = data.get_xaxis()
896
897    # Goes through all possible scales
898    # self.x_label is already wrapped with Latex "$", so using the argument
899
900    # X
901    if xLabel == "x":
902        data.transformX(DataTransform.toX, DataTransform.errToX)
903        xLabel = "%s(%s)" % (xname, xunits)
904    if xLabel == "x^(2)":
905        data.transformX(DataTransform.toX2, DataTransform.errToX2)
906        xunits = convertUnit(2, xunits)
907        xLabel = "%s^{2}(%s)" % (xname, xunits)
908    if xLabel == "x^(4)":
909        data.transformX(DataTransform.toX4, DataTransform.errToX4)
910        xunits = convertUnit(4, xunits)
911        xLabel = "%s^{4}(%s)" % (xname, xunits)
912    if xLabel == "ln(x)":
913        data.transformX(DataTransform.toLogX, DataTransform.errToLogX)
914        xLabel = "\ln{(%s)}(%s)" % (xname, xunits)
915    if xLabel == "log10(x)":
916        data.transformX(DataTransform.toX_pos, DataTransform.errToX_pos)
917        xscale = 'log'
918        xLabel = "%s(%s)" % (xname, xunits)
919    if xLabel == "log10(x^(4))":
920        data.transformX(DataTransform.toX4, DataTransform.errToX4)
921        xunits = convertUnit(4, xunits)
922        xLabel = "%s^{4}(%s)" % (xname, xunits)
923        xscale = 'log'
924
925    # Y
926    if yLabel == "ln(y)":
927        data.transformY(DataTransform.toLogX, DataTransform.errToLogX)
928        yLabel = "\ln{(%s)}(%s)" % (yname, yunits)
929    if yLabel == "y":
930        data.transformY(DataTransform.toX, DataTransform.errToX)
931        yLabel = "%s(%s)" % (yname, yunits)
932    if yLabel == "log10(y)":
933        data.transformY(DataTransform.toX_pos, DataTransform.errToX_pos)
934        yscale = 'log'
935        yLabel = "%s(%s)" % (yname, yunits)
936    if yLabel == "y^(2)":
937        data.transformY(DataTransform.toX2, DataTransform.errToX2)
938        yunits = convertUnit(2, yunits)
939        yLabel = "%s^{2}(%s)" % (yname, yunits)
940    if yLabel == "1/y":
941        data.transformY(DataTransform.toOneOverX, DataTransform.errOneOverX)
942        yunits = convertUnit(-1, yunits)
943        yLabel = "1/%s(%s)" % (yname, yunits)
944    if yLabel == "y*x^(2)":
945        data.transformY(DataTransform.toYX2, DataTransform.errToYX2)
946        xunits = convertUnit(2, xunits)
947        yLabel = "%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
948    if yLabel == "y*x^(4)":
949        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
950        xunits = convertUnit(4, xunits)
951        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
952    if yLabel == "1/sqrt(y)":
953        data.transformY(DataTransform.toOneOverSqrtX, DataTransform.errOneOverSqrtX)
954        yunits = convertUnit(-0.5, yunits)
955        yLabel = "1/\sqrt{%s}(%s)" % (yname, yunits)
956    if yLabel == "ln(y*x)":
957        data.transformY(DataTransform.toLogXY, DataTransform.errToLogXY)
958        yLabel = "\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
959    if yLabel == "ln(y*x^(2))":
960        data.transformY(DataTransform.toLogYX2, DataTransform.errToLogYX2)
961        xunits = convertUnit(2, xunits)
962        yLabel = "\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
963    if yLabel == "ln(y*x^(4))":
964        data.transformY(DataTransform.toLogYX4, DataTransform.errToLogYX4)
965        xunits = convertUnit(4, xunits)
966        yLabel = "\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
967    if yLabel == "log10(y*x^(4))":
968        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
969        xunits = convertUnit(4, xunits)
970        yscale = 'log'
971        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
972
973    # Perform the transformation of data in data1d->View
974    data.transformView()
975
976    return (xLabel, yLabel, xscale, yscale)
977
978def formatNumber(value, high=False):
979    """
980    Return a float in a standardized, human-readable formatted string.
981    This is used to output readable (e.g. x.xxxe-y) values to the panel.
982    """
983    try:
984        value = float(value)
985    except:
986        output = "NaN"
987        return output.lstrip().rstrip()
988
989    if high:
990        output = "%-7.5g" % value
991
992    else:
993        output = "%-5.3g" % value
994    return output.lstrip().rstrip()
995
996def replaceHTMLwithUTF8(html):
997    """
998    Replace some important HTML-encoded characters
999    with their UTF-8 equivalents
1000    """
1001    # Angstrom
1002    html_out = html.replace("&#x212B;", "Å")
1003    # infinity
1004    html_out = html_out.replace("&#x221e;", "∞")
1005    # +/-
1006    html_out = html_out.replace("&#177;", "±")
1007
1008    return html_out
1009
1010def replaceHTMLwithASCII(html):
1011    """
1012    Replace some important HTML-encoded characters
1013    with their ASCII equivalents
1014    """
1015    # Angstrom
1016    html_out = html.replace("&#x212B;", "Ang")
1017    # infinity
1018    html_out = html_out.replace("&#x221e;", "inf")
1019    # +/-
1020    html_out = html_out.replace("&#177;", "+/-")
1021
1022    return html_out
1023
1024def convertUnitToUTF8(unit):
1025    """
1026    Convert ASCII unit display into UTF-8 symbol
1027    """
1028    if unit == "1/A":
1029        return "Å<sup>-1</sup>"
1030    elif unit == "1/cm":
1031        return "cm<sup>-1</sup>"
1032    elif unit == "Ang":
1033        return "Å"
1034    elif unit == "1e-6/Ang^2":
1035        return "10<sup>-6</sup>/Å<sup>2</sup>"
1036    elif unit == "inf":
1037        return "∞"
1038    elif unit == "-inf":
1039        return "-∞"
1040    else:
1041        return unit
1042
1043def convertUnitToHTML(unit):
1044    """
1045    Convert ASCII unit display into well rendering HTML
1046    """
1047    if unit == "1/A":
1048        return "&#x212B;<sup>-1</sup>"
1049    elif unit == "1/cm":
1050        return "cm<sup>-1</sup>"
1051    elif unit == "Ang":
1052        return "&#x212B;"
1053    elif unit == "1e-6/Ang^2":
1054        return "10<sup>-6</sup>/&#x212B;<sup>2</sup>"
1055    elif unit == "inf":
1056        return "&#x221e;"
1057    elif unit == "-inf":
1058        return "-&#x221e;"
1059    else:
1060        return unit
1061
1062def parseName(name, expression):
1063    """
1064    remove "_" in front of a name
1065    """
1066    if re.match(expression, name) is not None:
1067        word = re.split(expression, name, 1)
1068        for item in word:           
1069            if item.lstrip().rstrip() != '':
1070                return item
1071    else:
1072        return name
1073
1074def toDouble(value_string):
1075    """
1076    toFloat conversion which cares deeply about user's locale
1077    """
1078    # Holy shit this escalated quickly in Qt5.
1079    # No more float() cast on general locales.
1080    value = QtCore.QLocale().toFloat(value_string)
1081    if value[1]:
1082        return value[0]
1083
1084    # Try generic locale
1085    value = QtCore.QLocale(QtCore.QLocale('en_US')).toFloat(value_string)
1086    if value[1]:
1087        return value[0]
1088    else:
1089        raise TypeError
1090
1091def findNextFilename(filename, directory):
1092    """
1093    Finds the next available (non-existing) name for 'filename' in 'directory'.
1094    plugin.py -> plugin (n).py  - for first 'n' for which the file doesn't exist
1095    """
1096    basename, ext = os.path.splitext(filename)
1097    # limit the number of copies
1098    MAX_FILENAMES = 1000
1099    # Start with (1)
1100    number_ext = 1
1101    proposed_filename = ""
1102    found_filename = False
1103    # Find the next available filename or exit if too many copies
1104    while not found_filename or number_ext > MAX_FILENAMES:
1105        proposed_filename = basename + " ("+str(number_ext)+")" + ext
1106        if os.path.exists(os.path.join(directory, proposed_filename)):
1107            number_ext += 1
1108        else:
1109            found_filename = True
1110
1111    return proposed_filename
1112
1113
1114class DoubleValidator(QtGui.QDoubleValidator):
1115    """
1116    Allow only dots as decimal separator
1117    """
1118    def validate(self, input, pos):
1119        """
1120        Return invalid for commas
1121        """
1122        if input is not None and ',' in input:
1123            return (QtGui.QValidator.Invalid, input, pos)
1124        return super(DoubleValidator, self).validate(input, pos)
1125
1126    def fixup(self, input):
1127        """
1128        Correct (remove) potential preexisting content
1129        """
1130        super(DoubleValidator, self).fixup(input)
1131        input = input.replace(",", "")
1132
1133def checkModel(path):
1134    """
1135    Check that the model save in file 'path' can run.
1136    """
1137    # The following return needs to be removed once
1138    # the unittest related changes in Sasmodels are commited
1139    # return True
1140    # try running the model
1141    from sasmodels.sasview_model import load_custom_model
1142    Model = load_custom_model(path)
1143    model = Model()
1144    q =  np.array([0.01, 0.1])
1145    _ = model.evalDistribution(q)
1146    qx, qy =  np.array([0.01, 0.01]), np.array([0.1, 0.1])
1147    _ = model.evalDistribution([qx, qy])
1148
1149    # check the model's unit tests run
1150    from sasmodels.model_test import run_one
1151    # TestSuite module in Qt5 now deletes tests in the suite after running,
1152    # so suite[0] in run_one() in sasmodels/model_test.py will contain [None] and
1153    # test.info.tests will raise.
1154    # Not sure how to change the behaviour here, most likely sasmodels will have to
1155    # be modified
1156    result = run_one(path)
1157
1158    return result
1159
1160def saveData(fp, data):
1161    """
1162    save content of data to fp (a .write()-supporting file-like object)
1163    """
1164
1165    def add_type(dict, type):
1166        dict['__type__'] = type.__name__
1167        return dict
1168
1169    def jdefault(o):
1170        """
1171        objects that can't otherwise be serialized need to be converted
1172        """
1173        # tuples and sets (TODO: default JSONEncoder converts tuples to lists, create custom Encoder that preserves tuples)
1174        if isinstance(o, (tuple, set)):
1175            content = { 'data': list(o) }
1176            return add_type(content, type(o))
1177
1178        # "simple" types
1179        if isinstance(o, (Sample, Source, Vector, FResult)):
1180            return add_type(o.__dict__, type(o))
1181        # detector
1182        if isinstance(o, (Detector, Process, TransmissionSpectrum, Aperture, Collimation)):
1183            return add_type(o.__dict__, type(o))
1184
1185        if isinstance(o, (Plottable, View)):
1186            return add_type(o.__dict__, type(o))
1187
1188        # SasviewModel - unique
1189        if isinstance(o, SasviewModel):
1190            # don't store parent
1191            content = o.__dict__.copy()
1192            return add_type(content, SasviewModel)
1193
1194        # DataState
1195        if isinstance(o, (Data1D, Data2D, FitData1D, FitData2D)):
1196            # don't store parent
1197            content = o.__dict__.copy()
1198            #content.pop('parent')
1199            return add_type(content, type(o))
1200
1201        # ndarray
1202        if isinstance(o, np.ndarray):
1203            buffer = BytesIO()
1204            np.save(buffer, o)
1205            buffer.seek(0)
1206            content = { 'data': buffer.read().decode('latin-1') }
1207            return add_type(content, type(o))
1208
1209        # not supported
1210        logging.info("data cannot be serialized to json: %s" % type(o))
1211        return None
1212
1213    json.dump(data, fp, indent=2, sort_keys=True, default=jdefault)
1214
1215def readDataFromFile(fp):
1216    '''
1217    Reads in Data1D/Data2 datasets from the file.
1218    Datasets are stored in the JSON format.
1219    '''
1220    supported = [
1221        tuple, set,
1222        Sample, Source, Vector,
1223        Plottable, Data1D, Data2D, PlottableTheory1D, PlottableFit1D, Text, Chisq, View,
1224        Detector, Process, TransmissionSpectrum, Collimation, Aperture,
1225        DataState, np.ndarray, FResult, FitData1D, FitData2D, SasviewModel]
1226
1227    lookup = dict((cls.__name__, cls) for cls in supported)
1228
1229    class TooComplexException(Exception):
1230        pass
1231
1232    def simple_type(cls, data, level):
1233        class Empty(object):
1234            def __init__(self):
1235                for key, value in data.items():
1236                    setattr(self, key, generate(value, level))
1237
1238        # create target object
1239        o = Empty()
1240        o.__class__ = cls
1241
1242        return o
1243
1244    def construct(type, data, level):
1245        try:
1246            cls = lookup[type]
1247        except KeyError:
1248            logging.info('unknown type: %s' % type)
1249            return None
1250
1251        # tuples and sets
1252        if cls in (tuple, set):
1253            # convert list to tuple/set
1254            return cls(generate(data['data'], level))
1255
1256        # "simple" types
1257        if cls in (Sample, Source, Vector, FResult, FitData1D, FitData2D,
1258                   SasviewModel, Detector, Process, TransmissionSpectrum,
1259                   Collimation, Aperture):
1260            return simple_type(cls, data, level)
1261        if issubclass(cls, Plottable) or (cls == View):
1262            return simple_type(cls, data, level)
1263
1264        # DataState
1265        if cls == DataState:
1266            o = simple_type(cls, data, level)
1267            o.parent = None # TODO: set to ???
1268            return o
1269
1270        # ndarray
1271        if cls == np.ndarray:
1272            buffer = BytesIO()
1273            buffer.write(data['data'].encode('latin-1'))
1274            buffer.seek(0)
1275            return np.load(buffer)
1276
1277        logging.info('not implemented: %s, %s' % (type, cls))
1278        return None
1279
1280    def generate(data, level):
1281        if level > 16: # recursion limit (arbitrary number)
1282            raise TooComplexException()
1283        else:
1284            level += 1
1285
1286        if isinstance(data, dict):
1287            try:
1288                type = data['__type__']
1289            except KeyError:
1290                # if dictionary doesn't have __type__ then it is assumed to be just an ordinary dictionary
1291                o = {}
1292                for key, value in data.items():
1293                    o[key] = generate(value, level)
1294                return o
1295
1296            return construct(type, data, level)
1297
1298        if isinstance(data, list):
1299            return [generate(item, level) for item in data]
1300
1301        return data
1302
1303    new_stored_data = {}
1304    for id, data in json.load(fp).items():
1305        try:
1306            new_stored_data[id] = generate(data, 0)
1307        except TooComplexException:
1308            logging.info('unable to load %s' % id)
1309
1310    return new_stored_data
1311
1312def readProjectFromSVS(filepath):
1313    """
1314    Read old SVS file and convert to the project dictionary
1315    """
1316    from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
1317    from sas.sascalc.fit.pagestate import Reader
1318
1319    loader = Loader()
1320    loader.associate_file_reader('.svs', Reader)
1321    temp = loader.load(filepath)
1322    state_reader = Reader()
1323    data_svs, state_svs = state_reader.read(filepath)
1324
1325    output = []
1326    if isinstance(temp, list) and isinstance(state_svs, list):
1327        for item, state in zip(temp, state_svs):
1328            output.append([item, state])
1329    else:
1330        output[temp, state_svs]
1331    return output
1332
1333def convertFromSVS(datasets):
1334    """
1335    Read in properties from SVS and convert into a simple dict
1336    """
1337    content = {}
1338    for dataset in datasets:
1339        # we already have data - interested only in properties
1340        #[[item_1, state_1], [item_2, state_2],...]
1341        data = dataset[0]
1342        params = dataset[1]
1343        content[params.data_id] = {}
1344        content[params.data_id]['fit_data'] = [data, {'checked': 2}, []]
1345        param_dict = {}
1346        param_dict['fitpage_category'] = [params.categorycombobox]
1347        param_dict['fitpage_model'] = [params.formfactorcombobox]
1348        param_dict['fitpage_structure'] = [params.structurecombobox]
1349        param_dict['2D_params'] = [str(params.is_2D)]
1350        param_dict['chainfit_params'] = ["False"]
1351        param_dict['data_id'] = [params.data_id]
1352        param_dict['data_name'] = [params.data_name]
1353        param_dict['is_data'] = [str(params.is_data)]
1354        param_dict['magnetic_params'] = [str(params.magnetic_on)]
1355        param_dict['model_name'] = [params.formfactorcombobox]
1356        param_dict['polydisperse_params'] = [str(params.enable_disp)]
1357        param_dict['q_range_max'] = [str(params.qmax)]
1358        param_dict['q_range_min'] = [str(params.qmin)]
1359        # Smearing is a bit trickier. 4.x has multiple keywords,
1360        # one for each combobox option
1361        if params.enable_smearer:
1362            if params.slit_smearer:
1363                w = 1
1364            elif params.pinhole_smearer:
1365                w = 2
1366            else:
1367                w = 0
1368            param_dict['smearing'] = [str(w)]
1369        # weighting is also tricky. 4.x has multiple keywords,
1370        # one for each radio box.
1371        if params.dI_noweight:
1372            w = 2
1373        elif params.dI_didata:
1374            w = 3
1375        elif params.dI_sqrdata:
1376            w = 4
1377        elif params.dI_idata:
1378            w = 5
1379        else:
1380            w = 2
1381        param_dict['weighting'] = [str(w)]
1382
1383        # 4.x multi_factor is really the multiplicity
1384        if params.multi_factor is not None:
1385            param_dict['multiplicity'] = [str(int(params.multi_factor))]
1386
1387        # playing with titles
1388        data.filename = params.file
1389        data.title = params.data_name
1390        data.name = params.data_name
1391
1392        # main parameters
1393        for p in params.parameters:
1394            p_name = p[1]
1395            param_dict[p_name] = [str(p[0]), str(p[2]), None, str(p[5][1]), str(p[6][1]), []]
1396        # orientation parameters
1397        if params.is_2D:
1398            for p in params.orientation_params:
1399                p_name = p[1]
1400                p_min = "-360.0"
1401                p_max = "360.0"
1402                if p[5][1] != "":
1403                    p_min = p[5][1]
1404                if p[6][1] != "":
1405                    p_max = p[6][1]
1406                param_dict[p_name] = [str(p[0]), str(p[2]), None, p_min, p_max, []]
1407
1408        # disperse parameters
1409        if params.enable_disp:
1410            for p in params.fittable_param:
1411                p_name = p[1]
1412                p_opt = str(p[0])
1413                p_err = "0"
1414                p_width = str(p[2])
1415                p_min = str(0)
1416                p_max = "inf"
1417                param_npts = p_name.replace('.width','.npts')
1418                param_nsigmas = p_name.replace('.width', '.nsigmas')
1419                if params.is_2D and p_name in params.disp_obj_dict:
1420                    lookup = params.orientation_params_disp
1421                    p_min = "-360.0"
1422                    p_max = "360.0"
1423                else:
1424                    lookup = params.fixed_param
1425                p_npts = [s[2] for s in lookup if s[1] == param_npts][0]
1426                p_nsigmas = [s[2] for s in lookup if s[1] == param_nsigmas][0]
1427                if p_name in params.disp_obj_dict:
1428                    p_disp = params.disp_obj_dict[p_name]
1429                else:
1430                    p_disp = "gaussian"
1431                param_dict[p_name] = [p_opt, p_width, p_min, p_max, p_npts, p_nsigmas, p_disp]
1432
1433        param_dict['is_batch_fitting'] = ['False']
1434        content[params.data_id]['fit_params'] = param_dict
1435
1436    return content
1437
1438def enum(*sequential, **named):
1439    """Create an enumeration object from a list of strings"""
1440    enums = dict(zip(sequential, range(len(sequential))), **named)
1441    return type('Enum', (), enums)
Note: See TracBrowser for help on using the repository browser.