source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 133812c7

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 133812c7 was 133812c7, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Merged ESS_GUI

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