source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 4acca8c

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 4acca8c was 4333edf, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Deal with pesky warnings about functions not being properly serialized.
They are now, although they are pretty redundant for Data1D/Data2D
structures.

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