diff --git a/.travis.yml b/.travis.yml index 6eb2935f..752ddce5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,8 @@ -language: python +language: c -python: - - 2.6 - - 2.7 - - 3.3 - - 3.4 - - 3.5 +os: + - linux + - osx # Setting sudo to false opts in to Travis-CI container-based builds. sudo: false @@ -27,45 +24,37 @@ env: # The following versions are the 'default' for tests, unless # overidden underneath. They are defined here in order to save having # to repeat them for all configurations. - - NUMPY_VERSION=1.9 - ASTROPY_VERSION=stable - SETUP_CMD='test' - - PIP_DEPENDENCIES='' + - PIP_DEPENDENCIES='sphinx_automodapi sphinx_rtd_theme' # For this package-template, we include examples of Cython modules, # so Cython is required for testing. If your package does not include # Cython code, you can set CONDA_DEPENDENCIES='' - - CONDA_DEPENDENCIES='Cython' + - CONDA_CHANNELS='http://ssb.stsci.edu/astroconda astropy-ci-extras' + - CONDA_DEPENDENCIES='numpy scipy cython specutils qtpy pyqtgraph pyyaml six' + matrix: - # Make sure that egg_info works without dependencies - - SETUP_CMD='egg_info' - # Try all python versions with the latest numpy - - SETUP_CMD='test' + - PYTHON_VERSION=2.7 SETUP_CMD='install' + - PYTHON_VERSION=2.7 SETUP_CMD='test' + - PYTHON_VERSION=2.7 SETUP_CMD='egg_info' + - PYTHON_VERSION=3.5 SETUP_CMD='install' + - PYTHON_VERSION=3.5 SETUP_CMD='test' + - PYTHON_VERSION=3.5 SETUP_CMD='egg_info' + matrix: include: - # Do a coverage test in Python 2. - - python: 2.7 - env: SETUP_CMD='test --coverage' + - env: PYTHON_VERSION=2.7 SETUP_CMD='test --coverage' # Check for sphinx doc build warnings - we do this first because it # may run for a long time - - python: 2.7 - env: SETUP_CMD='build_sphinx -w' + - env: PYTHON_VERSION=2.7 SETUP_CMD='build_sphinx -w' # Try Astropy development version - - python: 2.7 - env: ASTROPY_VERSION=development - - python: 3.4 - env: ASTROPY_VERSION=development - - # Try older numpy versions - - python: 2.7 - env: NUMPY_VERSION=1.8 - - python: 2.7 - env: NUMPY_VERSION=1.7 - - python: 2.7 - env: NUMPY_VERSION=1.6 + - env: PYTHON_VERSION=2.7 ASTROPY_VERSION=development + + - env: PYTHON_VERSION=3.5 ASTROPY_VERSION=development before_install: @@ -93,7 +82,7 @@ install: # As described above, using ci-helpers, you should be able to set up an # environment with dependencies installed using conda and pip, but in some # cases this may not provide enough flexibility in how to install a - # specific dependency (and it will not be able to install non-Python + # specific dependency (and it will not be able to install non-Python # dependencies). Therefore, you can also include commands below (as # well as at the start of the install section or in the before_install # section if they are needed before setting up conda) to install any diff --git a/README.md b/README.md index adb206ba..57a93780 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # SpecViz +[![astropy](http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)](http://www.astropy.org/) [![Build Status](https://travis-ci.org/spacetelescope/specviz.svg?branch=loaders)](https://travis-ci.org/spacetelescope/specviz) [![Documentation Status](http://readthedocs.org/projects/specviz/badge/?version=latest)](http://specviz.readthedocs.io/en/latest/?badge=latest) + An gui-based interactive analysis tool for one dimensional astronomical data using Python. diff --git a/docs/environment.yml b/docs/environment.yml new file mode 100644 index 00000000..8be49ec5 --- /dev/null +++ b/docs/environment.yml @@ -0,0 +1,14 @@ +name: specviz +channels: + - http://ssb.stsci.edu/astroconda +dependencies: + - pyyaml + - cython + - numpy + - scipy + - astropy + - specutils + - pyqtgraph + - pip: + - sphinx_automodapi + - sphinx_rtd_theme diff --git a/docs/make.bat b/docs/make.bat index 7d9cdb1b..bf6a2534 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -19,7 +19,7 @@ if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories + echo. dirhtml to make HTML files named ndex.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files diff --git a/docs/source/conf.py b/docs/source/conf.py index 01532160..78c20797 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,12 +36,14 @@ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.autosummary', - 'sphinx.ext.pngmath', + 'sphinx.ext.imgmath', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx_automodapi.automodapi' ] +suppress_warnings = ['image.nonlocal_uri'] + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/source/installation.rst b/docs/source/installation.rst index baceb49b..04364683 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -3,7 +3,7 @@ Installation ============ -SpecViz is distributed through the `Anaconda `_ package +SpecViz is distributed through the `Anaconda `__ package manager. Specifically, it lives within Space Telescope Science Institute's `AstroConda `_ channel. @@ -70,7 +70,7 @@ Please keep in mind that PyQt5 is the recommended PyQt implementation as `Qt4 development and support has ended `_. Below are instructions for installing using *either* `Homebrew `_ *or* `Anaconda `_. +.sh/>`_ *or* `Anaconda `__. PyQt5 """"" diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 00000000..ac0056d1 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,5 @@ +python: + setup_py_install: true + +conda: + file: docs/environment.yml diff --git a/requirements.txt b/requirements.txt index 7431f281..c436ca12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ ---index-url https://pypi.python.org/simple/ +-index-url https://pypi.python.org/simple/ pyyaml cython numpy scipy astropy -sphinx_automodapi +sphinx_automodapi +sphinx_rtd_theme git+https://github.com/nmearl/specutils.git git+https://github.com/nmearl/pyqtgraph.git -e . diff --git a/setup.cfg b/setup.cfg index 6a6a7f46..04531702 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [build_sphinx] -source-dir = docs +source-dir = docs/source build-dir = docs/_build all_files = 1 @@ -7,17 +7,17 @@ all_files = 1 upload-dir = docs/_build/html show-response = 1 -[pytest] +[tool:pytest] minversion = 2.2 norecursedirs = build docs/_build -doctest_plus = enabled +doctest_plus = disabled [ah_bootstrap] auto_use = True [metadata] package_name = specviz -description = An interactive astronomical analysis tool. +description = An interactive astronomical analysis tool. long_description = SpecViz is a tool for 1-D spectral visualization and analysis of astronomical spectrograms. author = Nicholas Earl, Ivo Busko, Pey Lian Lim author_email = nearl@stsci.edu diff --git a/specviz/analysis/tests/__init__.py b/specviz/analysis/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/specviz/analysis/tests/test_statistics.py b/specviz/analysis/tests/test_statistics.py new file mode 100644 index 00000000..584369d2 --- /dev/null +++ b/specviz/analysis/tests/test_statistics.py @@ -0,0 +1,15 @@ +import numpy as np + +from ..statistics import stats + +def test_simple_stats(): + a = np.ones(10) + truth = {'mean': 1, + 'median': 1, + 'stddev': 0, + 'total': 9, + 'npoints': 10} + + calc = stats(a) + + assert calc == truth, "Crazy wrong stats" diff --git a/specviz/core/comms.py b/specviz/core/comms.py index 847f8a09..7414dae3 100644 --- a/specviz/core/comms.py +++ b/specviz/core/comms.py @@ -16,13 +16,13 @@ class EventNode(object): - """ - An event + """An event An event is defined by the arguments the listeners of the event expect to be given. Parameters + ---------- args: [arg, ...] The list of keyword arguments that the event will provide to its listeners diff --git a/specviz/io/loaders/hstcos_loader.py b/specviz/io/loaders/hstcos_loader.py index 67fa1f1c..0560c7bc 100644 --- a/specviz/io/loaders/hstcos_loader.py +++ b/specviz/io/loaders/hstcos_loader.py @@ -45,7 +45,12 @@ def cos_spectrum_loader(file_name, **kwargs): uncertainty = StdDevUncertainty(hdu[1].data["ERROR"].flatten()) data = hdu[1].data['FLUX'].flatten() + dispersion = hdu[1].data['wavelength'].flatten() unit = Unit("erg/cm**2 Angstrom s") - return Spectrum1DRef(data=data, name=name, - uncertainty=uncertainty, unit=unit, meta=meta) + return Spectrum1DRef.from_array(data=data, + dispersion=dispersion, + dispersion_unit=Unit('Angstrom'), + uncertainty=uncertainty, + unit=unit, + meta=meta) diff --git a/specviz/io/loaders/hststis_loader.py b/specviz/io/loaders/hststis_loader.py index ea94b4fb..ea3a61b7 100644 --- a/specviz/io/loaders/hststis_loader.py +++ b/specviz/io/loaders/hststis_loader.py @@ -45,7 +45,12 @@ def stis_spectrum_loader(file_name, **kwargs): uncertainty = StdDevUncertainty(hdu[1].data["ERROR"].flatten()) data = hdu[1].data['FLUX'].flatten() + dispersion = hdu[1].data['wavelength'].flatten() unit = Unit("erg/cm**2 Angstrom s") - return Spectrum1DRef(data=data, name=name, - uncertainty=uncertainty, unit=unit, meta=meta) + return Spectrum1DRef.from_array(data=data, + dispersion=dispersion, + dispersion_unit=Unit('Angstrom'), + uncertainty=uncertainty, + unit=unit, + meta=meta) diff --git a/specviz/io/model_io/__init__.py b/specviz/io/model_io/__init__.py deleted file mode 100644 index 37b9bea0..00000000 --- a/specviz/io/model_io/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'busko' diff --git a/specviz/io/model_io/py_model_io.py b/specviz/io/model_io/py_model_io.py deleted file mode 100644 index 351da5b0..00000000 --- a/specviz/io/model_io/py_model_io.py +++ /dev/null @@ -1,250 +0,0 @@ -# -# Functions in this module support the reading and writing -# of astropy's spectral compound models from/to file. -# -# The format used in these files is plain python, directly -# importable by the user. This format was introduced and is -# discussed in the specfit project at: -# -# https://github.com/ibusko/specfit -# - -import os, sys, re, dis - -from io import StringIO -from qtpy.QtWidgets import QFileDialog - -MODEL_FILE_FILTER = "Pyhton files (*.py)" - - -# Helper functions -def _get_component_name(function): - # there must be a better way of getting the class' name...... - class_string = str(function.__class__) - return class_string.split('\'>')[0].split(".")[-1] - -def _get_component_path(function): - class_string = str(function.__class__) - module_path = class_string.split('\'')[1] - index = module_path.rfind('.') - module_path = module_path[:index] - return module_path - - -# Builds a compound model specified in a .py file -def buildModelFromFile(fname): - directory = os.path.dirname(fname) - sys.path.append(directory) - - f = os.path.basename(str(fname)).split('.')[0] # remove .py from end of file name so it can be imported - - # if already defined, force a reload. - import_statement = "import " + f - if f in sys.modules.keys(): - import_statement = "reload(sys.modules['" + f + "'])" - - try: - exec(import_statement) - module = sys.modules[f] - - # this will pick up the first model defined in the file. A different - # mechanism is needed to handle files with multiple model definitions. - for variable in dir(module): - if not variable.startswith('__'): - # this assumes that the module contains only model definitions and - # imports for the functional types used in the model definitions. - # This 'if' statement skips the function types, everything that - # passes is assumed to be a valid compound model definition. - if (str(type(module.__dict__[variable]))).find('astropy.modeling.core._ModelMeta') < 0: - - compound_model = module.__dict__[variable] - - # Return an empty string for now, in place of the model expression. This - # needs to be fixed in the unlikely case we ever use this method. - return compound_model, "", directory - - return None,None,None - except Exception as e: - print("ERROR: " + str(e)) - return None,None,None - - -# Writes a compound model expression to file. The 'header' string -# contains the import statements that refer to each component type -# that appear in the expression. -def _writeToFile(expression_string, model_directory, parent, header): - - fname = QFileDialog.getSaveFileName(parent, 'Export to .py file', model_directory)[0] - - if len(fname) > 0: - # enforce correct suffix. - if not fname.endswith(".py"): - fname += ".py" - - f = open(fname, 'w') - - f.write(header) - f.write(expression_string) - f.close() - -# here we handle the case of a spectral model with a single component. -# It's not strictly a compound model, but saving and retrieving isolated -# components as if they were compound models makes for a simpler interface. -# Unfortunately, isolated components cannot be added to an already existing -# compound model if that model was fitted already. -def _writeSingleComponentModel(model, model_directory, parent): - name = _get_component_name(model) - path = _get_component_path(model) - - header = "from " + path + " import " + name + "\n\n" - expression_string = "model1 = \\\n" + _assemble_component_spec(model) - - _writeToFile(expression_string, model_directory, parent, header) - - -# Builds a multi-component model expression inside a string, -# and dumps string to file. -def _writeCompoundModel(model, model_directory, parent): - - # The following assumes that the formatted string expression - # in an astropy compound model has operands of the form [0], [1], - # etc, that is, a sequential number enclosed in square brackets. - expression = model._format_expression() - tokens = re.split(r'[0-9]+', expression) - - # this loop builds the main expression, and captures - # information needed for building the file header (where - # the import statements go). - expression_string = "" - import_module_names = {} - for token, component in zip(tokens, iter(model)): - # clean up astropy-inserted characters - token = token.replace('[', '') - token = token.replace(']', '') - - expression_string += str(token) + _assemble_component_spec(component) - - # here we store the module paths for each component. Using - # a dictionary key ensures that we get only one of each. - path = _get_component_path(component) - name = _get_component_name(component) - import_module_names[name] = path - - # this loop now uses the captured information from above to - # build a set of import statements that go at the beginning - # of the file. - header = "" - for name, path in import_module_names.items(): - header += "from " + path + " import " + name + "\n" - header += "\n" - - # we need to add a reference to the model so it can actually - # be used after imported. We just use 'model1' for the variable - # name. This also implicitly assumes that only one model will be - # stored in the file. It remains to be seen how useful this - # assumption will be in practice. - header += "model1 = \\\n" - - _writeToFile(expression_string, model_directory, parent, header) - - -# Saves spectral model to file. This is the main entry -# point for the 'save to file' functionality. -# parent: optional QWidget used for screen centering. -# expression: not used for .py files. -def saveModelToFile(parent, model, model_directory, expression=None): - if not hasattr(model, '_format_expression'): - _writeSingleComponentModel(model, model_directory, parent) - else: - _writeCompoundModel(model, model_directory, parent) - - -# Disassembles a tie callable. Ties read from a model -# file are not directly accessible in text form because -# the model file is compiled at import time. -def get_tie_text(tie): - if tie: - # dis() only outputs on standard output..... - keep = sys.stdout - sys.stdout = StringIO() - dis.dis(tie) - assembler_text = sys.stdout.getvalue() - sys.stdout.close() - sys.stdout = keep - result = _parse_assembler_text(assembler_text) - else: - result = 'False' - return result - - -# This parses the text returned by the disassembler for -# a lambda function that multiplies a constant by a -# variable. That is, we are assuming that ties are coded -# as lambda functions with multiplication by a constant, -# as in the STSDAS' specfit task. -parser = re.compile(r'\(([^)]+)\)') # picks up whatever is enclosed in parenthesis -def _parse_assembler_text(text): - tokens = parser.findall(text) - factor = tokens[0] - lambda_variable_name = tokens[1] - function_id = tokens[2] - par_name = tokens[3] - - return "lambda %s: %s * %s[%s].%s" % \ - (lambda_variable_name, - factor, - lambda_variable_name, - function_id, - par_name) - - -# Builds the text string that describes an operand (a spectral component) -# for an astropy compound model. -def _assemble_component_spec(component): - result = "" - - # function name - Note that get_component_name works - # pretty much independently of the models registry. - # Any model will work because the function name is - # derived from the component's __class__. - name = _get_component_name(component) - result += name - - # component name - if component.name: - result += "(name = \'" - result += component.name + "\',\n" - else: - result += "(\n" - - # parameter names and values - for i, param_name in enumerate(component.param_names): - result += " " + param_name - result += " = " - result += str(component.parameters[i]) + ",\n" - - # parameter bounds - bounds = component.bounds - result += " bounds = {\n" - for param_name in component.param_names: - result += " '" + param_name + "': " + str(bounds[param_name]) + ",\n" - result += " },\n" - - # parameter fixed flags - fixed = component.fixed - result += " fixed = {\n" - for param_name in component.param_names: - result += " '" + param_name + "': " + str(fixed[param_name]) + ",\n" - result += " },\n" - - # parameter ties. Ties have to be disassembled and parsed - # in order to become human-readable and writable to file. - ties = component.tied - result += " tied = {\n" - for param_name in component.param_names: - tie_text = get_tie_text(ties[param_name]) - result += " '" + param_name + "': " + tie_text + ",\n" - result += " },\n" - - result += " ) \\\n" - return result diff --git a/specviz/io/model_io/yaml_model_io.py b/specviz/io/model_io/yaml_model_io.py deleted file mode 100644 index b6e771c3..00000000 --- a/specviz/io/model_io/yaml_model_io.py +++ /dev/null @@ -1,330 +0,0 @@ -# -# Functions in this module support the reading and writing -# of astropy's spectral compound models from/to YAML files. -# - -import os -import sys -import re -import dis -import copy -import yaml - -from io import StringIO -from ast import literal_eval - -from qtpy.QtWidgets import QFileDialog -from specviz.interfaces.factories import ModelFactory -from specviz.core.layer import ModelLayer - -MODEL_FILE_FILTER = "YAML files (*.yaml)" -EXPRESSION_NAME = 'arithmetic behavior' -MODEL_NAME = 'model' - - -# Helper functions -def _get_model_class_name(function): - class_string = str(function.__class__) - return class_string.split('\'>')[0].split(".")[-1] - - - -# ---------- From YAML file to model ------------------------' - -def _ingest_constraints(param_dict): - - bounds = param_dict['constraints']['bounds'] - fixed = param_dict['constraints']['fixed'] - tied = param_dict['constraints']['tied'] - - # bounds are tuples stored as strings so the user - # can read and edit the file using a text editor. - # They need to be converted back to python tuples. - for name in bounds: - bound = literal_eval(bounds[name]) - bounds[name] = (bound[0], bound[1]) - - # TODO: re-do this when implementing ties - # YAML returns different data types depending - # on the model type. They need to be properly - # converted. - for name in fixed: - if isinstance(fixed[name], str): - fixed[name] = literal_eval(fixed[name]) - tied[name] = literal_eval(tied[name]) - - return bounds, fixed, tied - - -def _build_single_model(in_map, model_name=None): - if model_name is None: - entry_name = list(in_map.keys())[0] - else: - entry_name = model_name - - model_name = in_map[entry_name]['class'] - - # model names in ModelFactory do not terminate - # with a redundant '1D' suffix; remove it. - model_cls = ModelFactory.all_models[model_name[:-2]] - - param_dict = in_map[entry_name] - name = param_dict['name'] - - bounds, fixed, tied = _ingest_constraints(param_dict) - - # the model constructor call can directly handle - # all parameter constraints, and the name - model = model_cls(name=name, bounds=bounds, fixed=fixed, tied=tied) - - # parameter values are top level objects in the model - # instance, unlike other parameter attributes such as - # bounds and ties. They have to be set explicitly. - parameters = param_dict['parameters'] - for pname in parameters: - value = float(param_dict['parameters'][pname]) - setattr(model, pname, value) - - return model - - -# If no arithmetic behavior expression is know, -# build a compound model by adding together all -# models present in the map. -def _build_additive_model(in_map): - model = None - for model_name in in_map[MODEL_NAME]: - in_model = _build_single_model(in_map[MODEL_NAME], model_name=model_name) - if model is None: - model = in_model - else: - model += in_model - return model - - -# If an arithmetic behavior expression is present, -# use it to build the compound model -def _build_compound_model(in_map): - model_list = [] - for model_name in in_map[MODEL_NAME]: - model_list.append(_build_single_model(in_map[MODEL_NAME], model_name=model_name)) - - formula = in_map[EXPRESSION_NAME] - - return ModelLayer.from_formula(model_list, formula), formula - - -def buildModelFromFile(fname): - """ - Builds a compound model specified in a YAML file. - - This is the main entry point for the 'read from file' - functionality. The caller is responsible for providing - the full-path file name. - - Parameters - ---------- - fname: str - the ffully qualified file name - """ - directory = os.path.dirname(fname) - - f = open(fname, "r") - in_map = yaml.safe_load(f) - f.close() - - expression = "" - - if MODEL_NAME in in_map: - # compound model - if EXPRESSION_NAME in in_map and len(in_map[EXPRESSION_NAME]) > 0: - model, expression = _build_compound_model(in_map) - else: - # add all models together if no formula is present - model = _build_additive_model(in_map) - else: - # single model - model = _build_single_model(in_map) - - return model, expression, directory - - - -# ---------- From model to YAML file ------------------------' - -# Builds a dictionary with model constraints by directly -# referring to the _constraints attribute in a model. -def _build_constraints_dict(model): - constraints_dict = copy.deepcopy(model._constraints) - - # bounds are stored as strings so - # they can be edited by the user. - for name in constraints_dict['bounds']: - bound1 = constraints_dict['bounds'][name][0] - bound2 = constraints_dict['bounds'][name][1] - constraints_dict['bounds'][name] = "(%s,%s)" % (str(bound1), str(bound2)) - - # clean up. This is something that exists only - # in single models and is not needed to rebuild - # the model from its YAML description. - if 'eqcons' in constraints_dict: - constraints_dict.pop('eqcons') - constraints_dict.pop('ineqcons') - - return constraints_dict - - -# From a single model, builds the dict to be output to YAML file. -def _build_output_dict_single(model): - model_name = model.name - - param_dict = {} - for name, value in zip(model.param_names, model.parameters): - param_dict[name] = str(value) - - constraints = _build_constraints_dict(model) - - model_dict = { - 'name': model_name, - 'class': _get_model_class_name(model), - 'parameters': param_dict, - 'constraints': constraints} - - return model_name, model_dict - - -# From a compound model, builds the dict to be output to YAML file. -def _build_output_dict_compound(model): - model_name = model.name - - param_dict = {} - for parameter_name, value in zip(model.param_names, model.parameters): - param_dict[parameter_name] = str(value) - - # In a compound model, we don't want the constraints as stored in - # the _constraints attribute, because these are keyed by the parameter - # names *in the compound model*, such as 'amplitude_0'. We need keys - # that relate directly with the underlying Parameter objects. Thus we - # cannot use method _build_constraints_dict, since it uses a direct - # copy of the _constraints attribute to drive its machinery. - constraints_dict = {'bounds':{},'tied':{},'fixed':{}} - for parameter_name in model.param_names: - parameter = getattr(model, parameter_name) - for constraint_name in parameter.constraints: - constraint = getattr(parameter, constraint_name) - constraints_dict[constraint_name][parameter_name] = str(constraint) - - model_dict = { - 'name': model_name, - 'class': _get_model_class_name(model), - 'parameters': param_dict, - 'constraints': constraints_dict} - - return model_name, model_dict - - -# Writes a dict to YAML file. -def _writeToFile(out_model_dict, model_directory, parent): - - fname = QFileDialog.getSaveFileName(parent, 'Save to file', model_directory)[0] - - if len(fname) > 0: - # enforce correct suffix. - if not fname.endswith(".yaml"): - fname += ".yaml" - - f = open(fname, "w") - yaml.dump(out_model_dict, f,default_flow_style=False) - f.close() - - -# Handles the case of a spectral model with a single component. It's -# not strictly a compound model, but saving and retrieving isolated -# components as if they were compound models makes for a simpler -# interface. -def _writeSingleComponentModel(model, model_directory, parent): - out_model_dict = {} - - model_name, model_dict = _build_output_dict_single(model) - out_model_dict[model_name] = model_dict - - _writeToFile(out_model_dict, model_directory, parent) - - -# Handles the case of a compound model -def _writeCompoundModel(compound_model, model_directory, parent, expression): - out_model_dict = {MODEL_NAME:{}} - - for model in compound_model: - model_name, model_dict = _build_output_dict_compound(model) - out_model_dict[MODEL_NAME][model_name] = model_dict - - out_model_dict[EXPRESSION_NAME] = expression - - _writeToFile(out_model_dict, model_directory, parent) - - -def saveModelToFile(parent, model, model_directory, expression=None): - """ - Saves spectral model to file. - - This is the main entry point for the 'save to file' - functionality. - - Parameters - ---------- - parent : QWidget or None - optional widget used for screen centering. - expression: str - the formula associated with the compound model - """ - if not hasattr(model, '_format_expression'): - _writeSingleComponentModel(model, model_directory, parent) - else: - _writeCompoundModel(model, model_directory, parent, expression) - - -#--------------------------------------------------------------------# -# -# Utility functions that might be used when we implement ties. -# - -# Disassembles a tie callable. Ties read from a model -# file are not directly accessible in text form because -# the model file is compiled at import time. -def get_tie_text(tie): - if tie: - # dis() only outputs on standard output..... - keep = sys.stdout - sys.stdout = StringIO() - dis.dis(tie) - assembler_text = sys.stdout.getvalue() - sys.stdout.close() - sys.stdout = keep - result = _parse_assembler_text(assembler_text) - else: - result = 'False' - return result - - -# This parses the text returned by the disassembler for -# a lambda function that multiplies a constant by a -# variable. That is, we are assuming that ties are coded -# as lambda functions with multiplication by a constant, -# as in the STSDAS' specfit task. -parser = re.compile(r'\(([^)]+)\)') # picks up whatever is enclosed in parenthesis -def _parse_assembler_text(text): - tokens = parser.findall(text) - factor = tokens[0] - lambda_variable_name = tokens[1] - function_id = tokens[2] - par_name = tokens[3] - - return "lambda %s: %s * %s[%s].%s" % \ - (lambda_variable_name, - factor, - lambda_variable_name, - function_id, - par_name) - -