source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 99f8760

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

Improvements to batch save/load SASVIEW-1222.
Project save/load will now recreate the grid window as well as retain
result theories under dataset indices.

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