source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 17e2d502

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 17e2d502 was 17e2d502, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 months ago

Batch page serialization/deserialization

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