source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 1d6899f

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

Updated cansas read (cherrypicked and fixed from master).
Fixes: hdf5 returns byte strings so these need to be recasted properly.
https://github.com/h5py/h5py/issues/379

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