<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Contains the worker classes/functions executed on the server side.
"""
import logging
import os
import jedi
# pylint: disable=C0103, global-variable-not-assigned

def _logger():
    """
     Returns the module's logger
    """
    return logging.getLogger(__name__)

def calltips(request_data):
    """
    Worker that returns a list of calltips.

    A calltips is a tuple made of the following parts:
      - module_name: name of the module of the function invoked
      - call_name: name of the function that is being called
      - params: the list of parameter names.
      - index: index of the current parameter
      - bracket_start

    :returns tuple(module_name, call_name, params)
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    # use jedi to get call signatures
    script = jedi.Script(code, line, column, path, encoding)
    signatures = script.call_signatures()
    for sig in signatures:
        results = (str(sig.module_name), str(sig.name),
                   [p.description for p in sig.params], sig.index,
                   sig.bracket_start, column)
        # todo: add support for multiple signatures
        return True, results
    return False, []

def goto_assignments(request_data):
    """
    Go to assignements worker.
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    script = jedi.Script(code, line, column, path, encoding)
    try:
        definitions = script.goto_assignments()
    except jedi.NotFoundError:
        pass
    else:
        ret_val = [(d.module_path, d.line, d.column, d.full_name)
                   for d in definitions]
        return True, ret_val

_old_definitions = {}

class Definition(object):
    """
    Represents a defined name in a python source code (import, function, class,
    method). Definition usually form a tree limited to 2 levels (we stop at the
    method level).
    """
    def __init__(self, name='', icon='', line=1, column=0, full_name=''):
        #: Icon resource name associated with the definition, can be None
        self.icon = icon
        #: Definition name (name of the class, method, variable)
        self.name = name
        #: The line of the definition in the current editor text
        self.line = line
        #: The column of the definition in the current editor text
        self.column = column
        #: Symbol name + parent name (for methods and class variables)
        self.full_name = full_name
        #: Possible list of children (only classes have children)
        self.children = []
        if self.full_name == "":
            self.full_name = self.name

    def add_child(self, definition):
        """
        Adds a child definition
        """
        self.children.append(definition)

    def to_dict(self):
        """
        Serialises a definition to a dictionary, ready for json.

        Children are serialised recursively.
        """
        ddict = {'name': self.name, 'icon': self.icon,
                 'line': self.line, 'column': self.column,
                 'full_name': self.full_name, 'children': []}
        for child in self.children:
            ddict['children'].append(child.to_dict())
        return ddict

    def from_dict(self, ddict):
        """
        Deserialise the definition from a simple dict.
        """
        self.name = ddict['name']
        self.icon = ddict['icon']
        self.line = ddict['line']
        self.column = ddict['column']
        self.full_name = ddict['full_name']
        self.children[:] = []
        for child_dict in ddict['children']:
            self.children.append(Definition().from_dict(child_dict))
        return self

    def __repr__(self):
        return 'Definition(%r, %r, %r, %r)' % (self.name, self.icon,
                                               self.line, self.column)

    def __eq__(self, other):
        if len(self.children) != len(other.children):
            return False
        for self_child, other_child in zip(self.children, other.children):
            if self_child != other_child:
                return False
        return (self.name == other.name and self.full_name == other.full_name
                and self.line == other.line and self.column == other.column)

def defined_names(request_data):
    """
    Returns the list of defined names for the document.
    """
    def _compare_definitions(a, b):
        """
        Compare two definition lists.

        Returns True if they are different.
        """
        if len(a) != len(b):
            return True
        else:
            # compare every definition. If one is different, break
            for def_a, def_b in zip(a, b):
                if def_a != def_b:
                    return True
            return False

    global _old_definitions
    code = request_data['code']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    ret_val = []
    toplvl_definitions = jedi.defined_names(code, path, encoding)
    for d in toplvl_definitions:
        d_line, d_column = d.start_pos
        # use full name for import type
        definition = Definition(d.name, icon_from_typename(d.name, d.type),
                                d_line, d_column, d.full_name)
        # check for methods in class
        if d.type == "class" or d.type == 'function':
            try:
                sub_definitions = d.defined_names()
                for sub_d in sub_definitions:
                    icon = icon_from_typename(sub_d.name, sub_d.type)
                    line, column = sub_d.start_pos
                    if sub_d.full_name == "":
                        sub_d.full_name = sub_d.name
                    sub_definition = Definition(sub_d.name, icon, line, column,
                                                sub_d.full_name)
                    definition.add_child(sub_definition)
            except AttributeError:
                pass
        if d.type != 'import':
            ret_val.append(definition)
    try:
        old_definitions = _old_definitions["%s_definitions" % path]
    except KeyError:
        old_definitions = []
    status = False
    if ret_val:
        if not _compare_definitions(ret_val, old_definitions):
            ret_val = None
            _logger().debug("No changes detected")
        else:
            _old_definitions["%s_definitions" % path] = ret_val
            _logger().debug("Document structure changed")
            status = True
            ret_val = [d.to_dict() for d in ret_val]
    return status, ret_val

def quick_doc(request_data):
    """
    Worker that returns the documentation of the symbol under cursor.
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = 'utf-8'
    encoding = 'utf-8'
    script = jedi.Script(code, line, column, path, encoding)
    try:
        definitions = script.goto_definitions()
    except jedi.NotFoundError:
        return []
    else:
        ret_val = [d.doc for d in definitions]
        return True, ret_val

def run_pep8(request_data):
    """
    Worker that run the pep8 tool on the current editor text.

    :returns a list of tuples (msg, msg_type, line_number)
    """
    import pep8
    from pyqode.python.backend.pep8utils import CustomChecker
    WARNING = 1
    code = request_data['code']
    path = request_data['path']
    # setup our custom style guide with our custom checker which returns a list
    # of strings instread of spitting the results at stdout
    pep8style = pep8.StyleGuide(parse_argv=False, config_file=True,
                                checker_class=CustomChecker)
    try:
        results = pep8style.input_file(path, lines=code.splitlines(True))
    except Exception:
        _logger().exception('Failed to run PEP8 analysis with data=%r'
                            % request_data)
        return False, []
    else:
        messages = []
        # pylint: disable=unused-variable
        for line_number, offset, code, text, doc in results:
            messages.append(('[PEP8] %s' % text, WARNING, line_number))
        return True, messages

def run_frosted(request_data):
    """
    Worker that run a frosted (the fork of pyflakes) code analysis on the
    current editor text.
    """
    global prev_results
    from frosted import checker
    import _ast
    WARNING = 1
    ERROR = 2
    ret_val = []
    code = request_data['code']
    path = request_data['path']
    encoding = request_data['encoding']
    if not code or not encoding or not path:
        status = False
    else:
        # First, compile into an AST and handle syntax errors.
        try:
            tree = compile(code.encode(encoding), path, "exec",
                           _ast.PyCF_ONLY_AST)
        except SyntaxError as value:
            msg = '[pyFlakes] %s' % value.args[0]
            # pylint: disable=unused-variable
            (lineno, offset, text) = value.lineno, value.offset, value.text
            # If there's an encoding problem with the file, the text is None
            if text is None:
                # Avoid using msg, since for the only known case, it
                # contains a bogus message that claims the encoding the
                # file declared was unknown.s
                _logger().warning("[SyntaxError] %s: problem decoding source", path)
            else:
                ret_val.append((msg, ERROR, lineno))
        else:
            # Okay, it's syntactically valid.  Now check it.
            # pylint: disable=no-value-for-parameter
            w = checker.Checker(tree, os.path.split(path)[1])
            w.messages.sort(key=lambda m: m.lineno)
            for warning in w.messages:
                msg = "[pyFlakes] %s: %s" % (warning.type.error_code, warning.message.split(':')[-1])
                line = warning.lineno
                status = (WARNING if warning.type.error_code.startswith('W') else
                          ERROR)
                ret_val.append((msg, status, line))
        status = True
    prev_results = ret_val
    return status, ret_val

def icon_from_typename(name, icon_type):
    """
    Returns the icon resource filename that corresponds to the given typename.

    :param name: name of the completion. Use to make the distinction between
        public and private completions (using the count of starting '_')
    :pram typename: the typename reported by jedi

    :returns: The associate icon resource filename or None.
    """
    ICONS = {'CLASS': ':/pyqode_python_icons/rc/class.png',
             'IMPORT': ':/pyqode_python_icons/rc/namespace.png',
             'STATEMENT': ':/pyqode_python_icons/rc/var.png',
             'FORFLOW': ':/pyqode_python_icons/rc/var.png',
             'MODULE': ':/pyqode_python_icons/rc/namespace.png',
             'KEYWORD': ':/pyqode_python_icons/rc/keyword.png',
             'PARAM': ':/pyqode_python_icons/rc/var.png',
             'ARRAY': ':/pyqode_python_icons/rc/var.png',
             'INSTANCE': ':/pyqode_python_icons/rc/var.png',
             'PARAM-PRIV': ':/pyqode_python_icons/rc/var.png',
             'PARAM-PROT': ':/pyqode_python_icons/rc/var.png',
             'FUNCTION': ':/pyqode_python_icons/rc/func.png',
             'DEF': ':/pyqode_python_icons/rc/func.png',
             'FUNCTION-PRIV': ':/pyqode_python_icons/rc/func_priv.png',
             'FUNCTION-PROT': ':/pyqode_python_icons/rc/func_prot.png'}
    ret_val = None
    icon_type = icon_type.upper()
    # jedi 0.8 introduced NamedPart class, which have a string instead of being
    # one
    if hasattr(name, "string"):
        name = name.string
    if icon_type == "FORFLOW" or icon_type == "STATEMENT":
        icon_type = "PARAM"
    if icon_type == "PARAM" or icon_type == "FUNCTION":
        if name.startswith("__"):
            icon_type += "-PRIV"
        elif name.startswith("_"):
            icon_type += "-PROT"
    if icon_type in ICONS:
        ret_val = ICONS[icon_type]
    elif icon_type:
        _logger().warning("Unimplemented completion icon_type: %s", icon_type)
    return ret_val

class JediCompletionProvider:
    """
    Provides code completion using the awesome `jedi`_  library

    .. _`jedi`: https://github.com/davidhalter/jedi
    """
    # pylint: disable=no-init, unused-argument

    @staticmethod
    def complete(code, line, column, path, encoding, prefix):
        """
        Completes python code using `jedi`_.

        :returns: a list of completion.
        """
        ret_val = []
        try:
            script = jedi.Script(code, line, column, path, encoding)
            completions = script.completions()
            print('completions: %r' % completions)
        except jedi.NotFoundError:
            completions = []
        for completion in completions:
            ret_val.append({
                'name': completion.name,
                'icon': icon_from_typename(
                    completion.name, completion.type),
                'tooltip': completion.full_name})
        return ret_val
<commit_msg>Cancel blank lines warning<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Contains the worker classes/functions executed on the server side.
"""
import logging
import os
import jedi
# pylint: disable=C0103, global-variable-not-assigned

def _logger():
    """
     Returns the module's logger
    """
    return logging.getLogger(__name__)

def calltips(request_data):
    """
    Worker that returns a list of calltips.

    A calltips is a tuple made of the following parts:
      - module_name: name of the module of the function invoked
      - call_name: name of the function that is being called
      - params: the list of parameter names.
      - index: index of the current parameter
      - bracket_start

    :returns tuple(module_name, call_name, params)
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    # use jedi to get call signatures
    script = jedi.Script(code, line, column, path, encoding)
    signatures = script.call_signatures()
    for sig in signatures:
        results = (str(sig.module_name), str(sig.name),
                   [p.description for p in sig.params], sig.index,
                   sig.bracket_start, column)
        # todo: add support for multiple signatures
        return True, results
    return False, []

def goto_assignments(request_data):
    """
    Go to assignements worker.
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    script = jedi.Script(code, line, column, path, encoding)
    try:
        definitions = script.goto_assignments()
    except jedi.NotFoundError:
        pass
    else:
        ret_val = [(d.module_path, d.line, d.column, d.full_name)
                   for d in definitions]
        return True, ret_val

_old_definitions = {}

class Definition(object):
    """
    Represents a defined name in a python source code (import, function, class,
    method). Definition usually form a tree limited to 2 levels (we stop at the
    method level).
    """
    def __init__(self, name='', icon='', line=1, column=0, full_name=''):
        #: Icon resource name associated with the definition, can be None
        self.icon = icon
        #: Definition name (name of the class, method, variable)
        self.name = name
        #: The line of the definition in the current editor text
        self.line = line
        #: The column of the definition in the current editor text
        self.column = column
        #: Symbol name + parent name (for methods and class variables)
        self.full_name = full_name
        #: Possible list of children (only classes have children)
        self.children = []
        if self.full_name == "":
            self.full_name = self.name

    def add_child(self, definition):
        """
        Adds a child definition
        """
        self.children.append(definition)

    def to_dict(self):
        """
        Serialises a definition to a dictionary, ready for json.

        Children are serialised recursively.
        """
        ddict = {'name': self.name, 'icon': self.icon,
                 'line': self.line, 'column': self.column,
                 'full_name': self.full_name, 'children': []}
        for child in self.children:
            ddict['children'].append(child.to_dict())
        return ddict

    def from_dict(self, ddict):
        """
        Deserialise the definition from a simple dict.
        """
        self.name = ddict['name']
        self.icon = ddict['icon']
        self.line = ddict['line']
        self.column = ddict['column']
        self.full_name = ddict['full_name']
        self.children[:] = []
        for child_dict in ddict['children']:
            self.children.append(Definition().from_dict(child_dict))
        return self

    def __repr__(self):
        return 'Definition(%r, %r, %r, %r)' % (self.name, self.icon,
                                               self.line, self.column)

    def __eq__(self, other):
        if len(self.children) != len(other.children):
            return False
        for self_child, other_child in zip(self.children, other.children):
            if self_child != other_child:
                return False
        return (self.name == other.name and self.full_name == other.full_name
                and self.line == other.line and self.column == other.column)

def defined_names(request_data):
    """
    Returns the list of defined names for the document.
    """
    def _compare_definitions(a, b):
        """
        Compare two definition lists.

        Returns True if they are different.
        """
        if len(a) != len(b):
            return True
        else:
            # compare every definition. If one is different, break
            for def_a, def_b in zip(a, b):
                if def_a != def_b:
                    return True
            return False

    global _old_definitions
    code = request_data['code']
    path = request_data['path']
    # encoding = request_data['encoding']
    encoding = 'utf-8'
    ret_val = []
    toplvl_definitions = jedi.defined_names(code, path, encoding)
    for d in toplvl_definitions:
        d_line, d_column = d.start_pos
        # use full name for import type
        definition = Definition(d.name, icon_from_typename(d.name, d.type),
                                d_line, d_column, d.full_name)
        # check for methods in class
        if d.type == "class" or d.type == 'function':
            try:
                sub_definitions = d.defined_names()
                for sub_d in sub_definitions:
                    icon = icon_from_typename(sub_d.name, sub_d.type)
                    line, column = sub_d.start_pos
                    if sub_d.full_name == "":
                        sub_d.full_name = sub_d.name
                    sub_definition = Definition(sub_d.name, icon, line, column,
                                                sub_d.full_name)
                    definition.add_child(sub_definition)
            except AttributeError:
                pass
        if d.type != 'import':
            ret_val.append(definition)
    try:
        old_definitions = _old_definitions["%s_definitions" % path]
    except KeyError:
        old_definitions = []
    status = False
    if ret_val:
        if not _compare_definitions(ret_val, old_definitions):
            ret_val = None
            _logger().debug("No changes detected")
        else:
            _old_definitions["%s_definitions" % path] = ret_val
            _logger().debug("Document structure changed")
            status = True
            ret_val = [d.to_dict() for d in ret_val]
    return status, ret_val

def quick_doc(request_data):
    """
    Worker that returns the documentation of the symbol under cursor.
    """
    code = request_data['code']
    line = request_data['line']
    column = request_data['column']
    path = request_data['path']
    # encoding = 'utf-8'
    encoding = 'utf-8'
    script = jedi.Script(code, line, column, path, encoding)
    try:
        definitions = script.goto_definitions()
    except jedi.NotFoundError:
        return []
    else:
        ret_val = [d.doc for d in definitions]
        return True, ret_val

def run_pep8(request_data):
    """
    Worker that run the pep8 tool on the current editor text.

    :returns a list of tuples (msg, msg_type, line_number)
    """
    import pep8
    from pyqode.python.backend.pep8utils import CustomChecker
    WARNING = 1
    code = request_data['code']
    path = request_data['path']
    # setup our custom style guide with our custom checker which returns a list
    # of strings instread of spitting the results at stdout
    pep8style = pep8.StyleGuide(parse_argv=False, config_file=True,
                                checker_class=CustomChecker)
    try:
        results = pep8style.input_file(path, lines=code.splitlines(True))
    except Exception:
        _logger().exception('Failed to run PEP8 analysis with data=%r'
                            % request_data)
        return False, []
    else:
        messages = []
        # pylint: disable=unused-variable
        for line_number, offset, code, text, doc in results:
            if not 'blank line contains whitespace' in text:
                # skip this warning because the editor will ensure not
                # whitespace are left when saving the text
                messages.append(('[PEP8] %s' % text, WARNING, line_number))
        return True, messages

def run_frosted(request_data):
    """
    Worker that run a frosted (the fork of pyflakes) code analysis on the
    current editor text.
    """
    global prev_results
    from frosted import checker
    import _ast
    WARNING = 1
    ERROR = 2
    ret_val = []
    code = request_data['code']
    path = request_data['path']
    encoding = request_data['encoding']
    if not code or not encoding or not path:
        status = False
    else:
        # First, compile into an AST and handle syntax errors.
        try:
            tree = compile(code.encode(encoding), path, "exec",
                           _ast.PyCF_ONLY_AST)
        except SyntaxError as value:
            msg = '[pyFlakes] %s' % value.args[0]
            # pylint: disable=unused-variable
            (lineno, offset, text) = value.lineno, value.offset, value.text
            # If there's an encoding problem with the file, the text is None
            if text is None:
                # Avoid using msg, since for the only known case, it
                # contains a bogus message that claims the encoding the
                # file declared was unknown.s
                _logger().warning("[SyntaxError] %s: problem decoding source", path)
            else:
                ret_val.append((msg, ERROR, lineno))
        else:
            # Okay, it's syntactically valid.  Now check it.
            # pylint: disable=no-value-for-parameter
            w = checker.Checker(tree, os.path.split(path)[1])
            w.messages.sort(key=lambda m: m.lineno)
            for warning in w.messages:
                msg = "[pyFlakes] %s: %s" % (warning.type.error_code, warning.message.split(':')[-1])
                line = warning.lineno
                status = (WARNING if warning.type.error_code.startswith('W') else
                          ERROR)
                ret_val.append((msg, status, line))
        status = True
    prev_results = ret_val
    return status, ret_val

def icon_from_typename(name, icon_type):
    """
    Returns the icon resource filename that corresponds to the given typename.

    :param name: name of the completion. Use to make the distinction between
        public and private completions (using the count of starting '_')
    :pram typename: the typename reported by jedi

    :returns: The associate icon resource filename or None.
    """
    ICONS = {'CLASS': ':/pyqode_python_icons/rc/class.png',
             'IMPORT': ':/pyqode_python_icons/rc/namespace.png',
             'STATEMENT': ':/pyqode_python_icons/rc/var.png',
             'FORFLOW': ':/pyqode_python_icons/rc/var.png',
             'MODULE': ':/pyqode_python_icons/rc/namespace.png',
             'KEYWORD': ':/pyqode_python_icons/rc/keyword.png',
             'PARAM': ':/pyqode_python_icons/rc/var.png',
             'ARRAY': ':/pyqode_python_icons/rc/var.png',
             'INSTANCE': ':/pyqode_python_icons/rc/var.png',
             'PARAM-PRIV': ':/pyqode_python_icons/rc/var.png',
             'PARAM-PROT': ':/pyqode_python_icons/rc/var.png',
             'FUNCTION': ':/pyqode_python_icons/rc/func.png',
             'DEF': ':/pyqode_python_icons/rc/func.png',
             'FUNCTION-PRIV': ':/pyqode_python_icons/rc/func_priv.png',
             'FUNCTION-PROT': ':/pyqode_python_icons/rc/func_prot.png'}
    ret_val = None
    icon_type = icon_type.upper()
    # jedi 0.8 introduced NamedPart class, which have a string instead of being
    # one
    if hasattr(name, "string"):
        name = name.string
    if icon_type == "FORFLOW" or icon_type == "STATEMENT":
        icon_type = "PARAM"
    if icon_type == "PARAM" or icon_type == "FUNCTION":
        if name.startswith("__"):
            icon_type += "-PRIV"
        elif name.startswith("_"):
            icon_type += "-PROT"
    if icon_type in ICONS:
        ret_val = ICONS[icon_type]
    elif icon_type:
        _logger().warning("Unimplemented completion icon_type: %s", icon_type)
    return ret_val

class JediCompletionProvider:
    """
    Provides code completion using the awesome `jedi`_  library

    .. _`jedi`: https://github.com/davidhalter/jedi
    """
    # pylint: disable=no-init, unused-argument

    @staticmethod
    def complete(code, line, column, path, encoding, prefix):
        """
        Completes python code using `jedi`_.

        :returns: a list of completion.
        """
        ret_val = []
        try:
            script = jedi.Script(code, line, column, path, encoding)
            completions = script.completions()
            print('completions: %r' % completions)
        except jedi.NotFoundError:
            completions = []
        for completion in completions:
            ret_val.append({
                'name': completion.name,
                'icon': icon_from_typename(
                    completion.name, completion.type),
                'tooltip': completion.full_name})
        return ret_val
<|endoftext|>


<commit_before>class Trainer(object):
    SUPPORTED_LANGUAGES = None

    def __init__(self, language_name, max_num_threads=1):
        self.intent_classifier = None
        self.entity_extractor = None
        self.language_name = language_name
        self.max_num_threads = max_num_threads
        self.training_data = None
        self.ensure_language_support(language_name)

    def ensure_language_support(self, language_name):
        if language_name not in self.SUPPORTED_LANGUAGES:
            raise NotImplementedError("Selected backend currently does not support language '{}' (only '{}')."
                                      .format(language_name, "', '".join(self.SUPPORTED_LANGUAGES)))

    def train_entity_extractor(self, entity_examples):
        raise NotImplementedError()

    def train_intent_classifier(self, intent_examples, test_split_size=0.1):
        raise NotImplementedError()

    def train(self, data, test_split_size=0.1):
        self.training_data = data
        self.train_intent_classifier(data.intent_examples, test_split_size)

        if self.training_data.num_entity_examples > 0:
            self.train_entity_extractor(data.entity_examples)
<commit_msg>remove not implemented for languages and replaced with warning<commit_after>import logging

class Trainer(object):
    SUPPORTED_LANGUAGES = None

    def __init__(self, language_name, max_num_threads=1):
        self.intent_classifier = None
        self.entity_extractor = None
        self.language_name = language_name
        self.max_num_threads = max_num_threads
        self.training_data = None
        self.ensure_language_support(language_name)

    def ensure_language_support(self, language_name):
        if language_name not in self.SUPPORTED_LANGUAGES:
            supported = "', '".join(self.SUPPORTED_LANGUAGES)
            logging.warn("Selected backend currently does not officially support language " +
                         "'{}' (only '{}'). Things might break!".format(language_name, supported))

    def train_entity_extractor(self, entity_examples):
        raise NotImplementedError()

    def train_intent_classifier(self, intent_examples, test_split_size=0.1):
        raise NotImplementedError()

    def train(self, data, test_split_size=0.1):
        self.training_data = data
        self.train_intent_classifier(data.intent_examples, test_split_size)

        if self.training_data.num_entity_examples > 0:
            self.train_entity_extractor(data.entity_examples)
<|endoftext|>


<commit_before># Licensed under a 3-clause BSD style license - see LICENSE.rst
# This module implements the Spectrum1D class. It is eventually supposed to migrate to astropy core

# Python packages
from abc import ABCMeta, abstractmethod, abstractproperty
from copy import copy, deepcopy

# External packages
import numpy as np

# Local packages
from astropy.nddata import NDData

class SpectrumErrorArray(object):
    """ Represents the error axis of a Spectrum object

    .. warning::
        This is skeleton code!

    Spectral data errors can be represented in a variety of ways, e.g. 1-sigma
    errors, inverse variance, variance, etc. In order to provide generic functionality,
    the error data must carry with it some metadata and operations that help
    handle transformations of the error array.

    Parameters
    ----------
    array : `~numpy.ndarray`
        The error values

    Notes
    -----
    This class is abstract and can only be subclassed and used by
    classes that will access its API.

    """

    # This is an abstract class and can only be subclassed.
    __metaclass__ = ABCMeta

    def __init__(self, values):
        self.values = values

    @abstractmethod
    def interpolate(self, new_dispersion):
        pass

class InverseVarianceErrorArray(SpectrumErrorArray):
    # ...

    def interpolate(self, new_dispersion):
        pass

class VarianceErrorArray(SpectrumErrorArray):
    # ...

    def interpolate(self, new_dispersion):
        pass

class Spectrum1DBase(NDData):
    """ Base class for Spectrum1D objects.

    .. warning::
        This is skeleton code!

    `Spectrum1DBase` is a stripped-down superclass of the Spectrum1D object
    that allows for creation of a Spectrum-like object **without** an internal
    dispersion array. This allows for the possibility of creating a
    SpectrumCollection type class that could contain many spectra that share
    the same dispersion axis (e.g. SDSS spectra from the same plate).

    Parameters
    ----------
    flux : `~numpy.ndarray`
        The flux data as an array

    Notes
    -----
    This class should be 'private' in that it should really only be subclassed
    and used by Spectrum1D and SpectrumCollection

    """
    def __init__(self):
        pass # not yet implemented

    @property
    def spectrum1DBase(self):
        """ This method returns an object equivalent to this spectrum but as
            a Spectrum1DBase object, i.e. without a dispersion array.
            It is left to subclasses to handle this in more detail (e.g., see Spectrum1D).
        """
        return self

class Spectrum1D(Spectrum1DBase):
    """ Class for 1-dimensional Spectrum objects.

    .. warning::
        This is skeleton code!

    `Spectrum1D` provides a container for 1-dimensional spectral data as well
    as generic operations for this data.

    Parameters
    ----------
    flux : `~numpy.ndarray`
        The flux data as an array
    dispersion : `~numpy.ndarray`
        An array of data representing the dispersion axis of the spectral data,
        e.g. wavelength, frequency, energy, velocity, etc.
    <NDData parameters...>

    Notes
    -----

    """
    # This is an abstract class and can only be subclassed.
    __metaclass__ = ABCMeta

    @property
    def spectrum1DBase(self):
        """ Return a new Spectrum1DBase object from this spectrum.

            This basically is the same thing but without the dispersion array,
            but we'll see how this develops. """
        # There will be a better way to do this, but this is the general idea.
        # Basically I want to create a Spectrum1DBase object from a Spectrum1D object.
        spectrum_copy = deepcopy(self)
        spectrum_copy.dispersion = None
        return spectrum_copy

    def slice_dispersion(self, start=None, end=None):
        """ Slices the data arrays based on values in the dispersion array

            Parameters
            ----------
            start : any numeric type, optional
                The minimum value in the dispersion axis to slice on
            end : any numeric type, optional
                The maximum value in the dispersion axis to slice on
        """
        pass

    def slice_pixel(self, start=None, end=None):
        """ Slices the data arrays on pixel index (e.g. array index)

            Parameters
            ----------
            start : int, optional
                The starting index to slice the arrays on
            end : int, optional
                The ending index to slice the arrays on

            Notes
            -----
            This is equivalent to slicing each array like array[start:end]

        """
        pass

    def interpolate_linear(self, new_dispersion):
        """ Uses linear interpolation to resample the internal arrays onto
            the specified dispersion axis.

            Parameters
            ----------
            new_dispersion : `~numpy.ndarray`
                The new dispersion array to interpolate on to

        """
        pass

    def interpolate_bspline(self, new_dispersion):
        """ Uses B-spline interpolation to resample the internal arrays onto
            the specified dispersion axis.

            Parameters
            ----------
            new_dispersion : `~numpy.ndarray`
                The new dispersion array to interpolate on to

        """
        pass

    def smooth_boxcar(self, width):
        """ Uses boxcar smoothing to smooth the internal arrays.

            Parameters
            ----------
            width : integer
                The boxcar width in pixels.

            Reference: scipy.signal.boxcar

        """
        # I don't know if this is really the proper way to do this... should be tested!!
        from scipy.signal import convolve, boxcar
        self.flux = convolve(self.flux, boxcar(M=width))

    def smooth_custom(self):
        """ Allows for a user defined smoothing operation.

            This is simply a template function meant to be overwritten
            by a custom function e.g.:

            def so_smooth(self):
                # Do something with flux, dispersion, error

            spectrumObject.custom_smooth = so_smooth
            spectrumObject.custom_smooth()
        """
        pass

    def plot(self, **kwargs):
        """ Plotting utility for the spectrum.

            Parameters
            ----------
            axes : `~matplotlib.pyplot.Axes`, optional
                A matplotlib Axes() object to plot the spectrum on
            filename : str, optional
                A filename to save the plot to
            show_error : bool, optional
                Decide whether to use the error data when plotting

            Usage
            -----
            s = Spectrum1D(...)

            s.plot() - Attempt to display the object on screen
                       using matplotlib's `show()` command.

            s.plot(axes=ax) - Plot the data onto the specified
                              Axes() object.

            s.plot(axes=ax) - Plot the data onto the specified
                              Axes() object.

            s.plot(filename="myPlot.png") - Plot the data onto a new figure and
                                save to the specified filename and path
                                (default path if not specified).
                                The format of the file will be deduced from the
                                extension (e.g. ps, png, pdf).

            Notes
            -----
            Where it makes sense (i.e. not conflicting), multiple parameters
            can be specified. For example,

            s.plot(filename="myPlot.pdf", axes=ax)

            will both write a file to the disk and return a matplotlib.Axes() object.

        """
        pass

class Spectrum1DCollection(object):
    """ A collection object for spectra that share the same dispersion information.

    """
    def __init__(self):
        self.spectra = list()
        self.dispersion = None

    def append(self, spectrum):
        """ Add a spectrum (of type Spectrum1D or Spectrum1DBase) to this collection.

        """
        self.spectra.append(new_spectrum.spectrum1DBase)

    def len(self):
        return len(self.spectra)

    @property
    def dispersion(self):
        return self.dispersion

    @dispersion.setter
    def dispersion(self, new_dispersion):
        """ Set the dispersion array to be used for all spectra in this collection.

        The dispersion argument accepts both a numpy array and a Spectrum1D object.
        When the latter is specified, the dispersion is extracted from the object.
        """
        if is_instance(new_dispersion, Spectrum1D):
            self.dispersion = new_dispersion.dispersion
        elif is_instance(new_dispersion, list):
            self.dispersion = np.array(new_dispersion)
        elif is_instance(new_dispersion, np.array):
            self.dispersion = new_dispersion
        else:
            raise ValueError, "The dispersion specified could is not a known type. Should be a list, a numpy array, or a Spectrum1D object."
        # Add other types that unambiguously define a dispersion array.

<commit_msg>Fix raising exception syntax<commit_after># Licensed under a 3-clause BSD style license - see LICENSE.rst
# This module implements the Spectrum1D class. It is eventually supposed to migrate to astropy core

# Python packages
from abc import ABCMeta, abstractmethod, abstractproperty
from copy import copy, deepcopy

# External packages
import numpy as np

# Local packages
from astropy.nddata import NDData

class SpectrumErrorArray(object):
    """ Represents the error axis of a Spectrum object

    .. warning::
        This is skeleton code!

    Spectral data errors can be represented in a variety of ways, e.g. 1-sigma
    errors, inverse variance, variance, etc. In order to provide generic functionality,
    the error data must carry with it some metadata and operations that help
    handle transformations of the error array.

    Parameters
    ----------
    array : `~numpy.ndarray`
        The error values

    Notes
    -----
    This class is abstract and can only be subclassed and used by
    classes that will access its API.

    """

    # This is an abstract class and can only be subclassed.
    __metaclass__ = ABCMeta

    def __init__(self, values):
        self.values = values

    @abstractmethod
    def interpolate(self, new_dispersion):
        pass

class InverseVarianceErrorArray(SpectrumErrorArray):
    # ...

    def interpolate(self, new_dispersion):
        pass

class VarianceErrorArray(SpectrumErrorArray):
    # ...

    def interpolate(self, new_dispersion):
        pass

class Spectrum1DBase(NDData):
    """ Base class for Spectrum1D objects.

    .. warning::
        This is skeleton code!

    `Spectrum1DBase` is a stripped-down superclass of the Spectrum1D object
    that allows for creation of a Spectrum-like object **without** an internal
    dispersion array. This allows for the possibility of creating a
    SpectrumCollection type class that could contain many spectra that share
    the same dispersion axis (e.g. SDSS spectra from the same plate).

    Parameters
    ----------
    flux : `~numpy.ndarray`
        The flux data as an array

    Notes
    -----
    This class should be 'private' in that it should really only be subclassed
    and used by Spectrum1D and SpectrumCollection

    """
    def __init__(self):
        pass # not yet implemented

    @property
    def spectrum1DBase(self):
        """ This method returns an object equivalent to this spectrum but as
            a Spectrum1DBase object, i.e. without a dispersion array.
            It is left to subclasses to handle this in more detail (e.g., see Spectrum1D).
        """
        return self

class Spectrum1D(Spectrum1DBase):
    """ Class for 1-dimensional Spectrum objects.

    .. warning::
        This is skeleton code!

    `Spectrum1D` provides a container for 1-dimensional spectral data as well
    as generic operations for this data.

    Parameters
    ----------
    flux : `~numpy.ndarray`
        The flux data as an array
    dispersion : `~numpy.ndarray`
        An array of data representing the dispersion axis of the spectral data,
        e.g. wavelength, frequency, energy, velocity, etc.
    <NDData parameters...>

    Notes
    -----

    """
    # This is an abstract class and can only be subclassed.
    __metaclass__ = ABCMeta

    @property
    def spectrum1DBase(self):
        """ Return a new Spectrum1DBase object from this spectrum.

            This basically is the same thing but without the dispersion array,
            but we'll see how this develops. """
        # There will be a better way to do this, but this is the general idea.
        # Basically I want to create a Spectrum1DBase object from a Spectrum1D object.
        spectrum_copy = deepcopy(self)
        spectrum_copy.dispersion = None
        return spectrum_copy

    def slice_dispersion(self, start=None, end=None):
        """ Slices the data arrays based on values in the dispersion array

            Parameters
            ----------
            start : any numeric type, optional
                The minimum value in the dispersion axis to slice on
            end : any numeric type, optional
                The maximum value in the dispersion axis to slice on
        """
        pass

    def slice_pixel(self, start=None, end=None):
        """ Slices the data arrays on pixel index (e.g. array index)

            Parameters
            ----------
            start : int, optional
                The starting index to slice the arrays on
            end : int, optional
                The ending index to slice the arrays on

            Notes
            -----
            This is equivalent to slicing each array like array[start:end]

        """
        pass

    def interpolate_linear(self, new_dispersion):
        """ Uses linear interpolation to resample the internal arrays onto
            the specified dispersion axis.

            Parameters
            ----------
            new_dispersion : `~numpy.ndarray`
                The new dispersion array to interpolate on to

        """
        pass

    def interpolate_bspline(self, new_dispersion):
        """ Uses B-spline interpolation to resample the internal arrays onto
            the specified dispersion axis.

            Parameters
            ----------
            new_dispersion : `~numpy.ndarray`
                The new dispersion array to interpolate on to

        """
        pass

    def smooth_boxcar(self, width):
        """ Uses boxcar smoothing to smooth the internal arrays.

            Parameters
            ----------
            width : integer
                The boxcar width in pixels.

            Reference: scipy.signal.boxcar

        """
        # I don't know if this is really the proper way to do this... should be tested!!
        from scipy.signal import convolve, boxcar
        self.flux = convolve(self.flux, boxcar(M=width))

    def smooth_custom(self):
        """ Allows for a user defined smoothing operation.

            This is simply a template function meant to be overwritten
            by a custom function e.g.:

            def so_smooth(self):
                # Do something with flux, dispersion, error

            spectrumObject.custom_smooth = so_smooth
            spectrumObject.custom_smooth()
        """
        pass

    def plot(self, **kwargs):
        """ Plotting utility for the spectrum.

            Parameters
            ----------
            axes : `~matplotlib.pyplot.Axes`, optional
                A matplotlib Axes() object to plot the spectrum on
            filename : str, optional
                A filename to save the plot to
            show_error : bool, optional
                Decide whether to use the error data when plotting

            Usage
            -----
            s = Spectrum1D(...)

            s.plot() - Attempt to display the object on screen
                       using matplotlib's `show()` command.

            s.plot(axes=ax) - Plot the data onto the specified
                              Axes() object.

            s.plot(axes=ax) - Plot the data onto the specified
                              Axes() object.

            s.plot(filename="myPlot.png") - Plot the data onto a new figure and
                                save to the specified filename and path
                                (default path if not specified).
                                The format of the file will be deduced from the
                                extension (e.g. ps, png, pdf).

            Notes
            -----
            Where it makes sense (i.e. not conflicting), multiple parameters
            can be specified. For example,

            s.plot(filename="myPlot.pdf", axes=ax)

            will both write a file to the disk and return a matplotlib.Axes() object.

        """
        pass

class Spectrum1DCollection(object):
    """ A collection object for spectra that share the same dispersion information.

    """
    def __init__(self):
        self.spectra = list()
        self.dispersion = None

    def append(self, spectrum):
        """ Add a spectrum (of type Spectrum1D or Spectrum1DBase) to this collection.

        """
        self.spectra.append(new_spectrum.spectrum1DBase)

    def len(self):
        return len(self.spectra)

    @property
    def dispersion(self):
        return self.dispersion

    @dispersion.setter
    def dispersion(self, new_dispersion):
        """ Set the dispersion array to be used for all spectra in this collection.

        The dispersion argument accepts both a numpy array and a Spectrum1D object.
        When the latter is specified, the dispersion is extracted from the object.
        """
        if is_instance(new_dispersion, Spectrum1D):
            self.dispersion = new_dispersion.dispersion
        elif is_instance(new_dispersion, list):
            self.dispersion = np.array(new_dispersion)
        elif is_instance(new_dispersion, np.array):
            self.dispersion = new_dispersion
        else:
            raise ValueError("The dispersion specified could is not a known type. Should be a list, a numpy array, or a Spectrum1D object.")
        # Add other types that unambiguously define a dispersion array.

<|endoftext|>


<commit_before># Python module for scheduling bazaar project
# Includes: TLE class
#           load_gs()
#           load_tles()
#           get_passes()
#           calc_access_time()
#           plot_access_time()

# Import necessary libraries
from datetime import timedelta
from itertools import islice
import json
import math

import ephem
import matplotlib.pyplot as plt
# import seaborn

# TLE class definition
class TLE:
    """TLE class used for various tle attributes.

    Args: a three element list containing tle lines
    """
    def __init__(self, tle):
        self.tle = tle
        self.tle0 = tle[0]
        self.tle1 = tle[1]
        self.tle2 = tle[2]
        self.name = tle[0].rstrip()
        self.noradid = tle[1][2:7]
        self.epoch = tle[1][18:32]
        self.inclination = tle[2][8:16]

    def __str__(self):
        return '%s\n%s\n%s' % (self.tle0, self.tle1, self.tle2)

# load_tles() function definition
def load_tles(filename):
    """Returns a list of tle's from a file.

    Arguments:
    filename -- file name string containing unparsed tles
    """
    data = []
    with open(filename) as f:
        while True:
            # an iterator that returns the next N lines and stops
            tripleline = islice(f, 3)
            # loop over these N lines, removing trailing spaces and \n
            tle = [x.rstrip() for x in tripleline]

            # only accept complete data
            # the end of the file *should* have len(tle)==0 but
            # this also handles extra junk at the end
            if len(tle) == 3:
                data.append(tle)
            else:
                break
    return data

def load_gs(filename):
    """Returns a list of gs dicts from a file.

    Arguments:
    filename -- file name string containing unparsed gs
        either JSON format, read directly to the dict
        or a text file with 4 lines per station

    gs has four elements: name, lat, lon, alt
    """

    if filename.lower().endswith('.json'):
        with open(filename) as f:
            stations = json.load(f)
        # satnogs network API returns "lng" instead of "lon".  Make both
        # available.
        for gs in stations:
            gs['lon'] = gs['lng']
    else:
        stations = []
        with open(filename) as f:
            while True:
                # an iterator that returns the next N lines and stops
                fourline = islice(f, 4)
                # loop over these N lines, removing trailing spaces and \n
                gs = [x.rstrip() for x in fourline]

                # only accept complete data
                # the end of the file *should* have len(tle)==0 but
                # this also handles extra junk at the end
                if len(gs) == 4:
                    stations.append(
                        dict(name=gs[0],
                             lat=float(gs[1]),
                             lon=float(gs[2]),
                             lng=float(gs[2]),
                             altitude=float(gs[3]),
                             )
                        )
                else:
                    break
    return stations

# get_passes() function definition
def get_passes(observer, tle, start_time,
               num_passes=None, duration=None, horizon='10:00'):
    """Config obs and sat, Return pass data for all passes in given interval.

    Arguments:
    observer -- 4 element list containing desired [name,lat,lon,alt]
    tle -- 3 element list containing desired tle [line0,line1,line2]
    start_time -- ephem.date string formatted 'yyyy/mm/dd hr:min:sec'
    num_passes -- integer number of desired passes (defualt None)
    duration -- float number of hours or fraction of hours (default None)
    horizon -- str optional specification for observer horizon (defualt 0 deg)

    Specify either num_passes or duration.
    If both, use min(num_passes, duration).
    If neither, find passes for next 24 hours.
    """

    tle_line0, tle_line1, tle_line2 = tle

    # Set up location of observer
    ground_station = ephem.Observer()
    ground_station.name = observer['name']        # name string
    ground_station.lon = str(observer['lon'])          # in degrees (+E)
    ground_station.lat = str(observer['lat'])          # in degrees (+N)
    ground_station.elevation = observer['altitude']       # in meters
    ground_station.date = ephem.date(start_time)  # in UTC
    ground_station.horizon = horizon              # in degrees

    # Read in most recent satellite TLE data
    sat = ephem.readtle(tle_line0, tle_line1, tle_line2)

    contacts = []

    if duration is None and num_passes is None:
        # get passes for next 24 hrs
        duration = 24
        # set num_passes > max passes possible in duration.
        # duration is in hours, so 4 per hour is large
        # enough for duration to break out of loop.
        num_passes = 4 * int(duration)
        # set end_time longer than suggested length for tle's
        end_time = ephem.date(ground_station.date+5*365)
    if duration is not None and num_passes is None:
        # set num_passes > max passes possible in duration.
        # duration is in hours, so 4 per hour is large
        # enough for duration to break out of loop.
        num_passes = 4 * int(duration)
        end_time = ephem.date(ground_station.date+duration*ephem.hour)
    if duration is None and num_passes is not None:
        # set end_time longer than suggested length for tle's
        end_time = ephem.date(ground_station.date+5*365)
    if num_passes is not None and duration is not None:
        # if both are given, use minimum
        end_time = ephem.date(ground_station.date+duration*ephem.hour)

    try:
        for i in range(num_passes):
            if ground_station.date > end_time:
                break
            sat.compute(ground_station)  # compute all body attributes for sat
            # next pass command yields array with [0]=rise time,
            # [1]=rise azimuth, [2]=max alt time, [3]=max alt,
            # [4]=set time, [5]=set azimuth
            info = ground_station.next_pass(sat)
            rise_time, rise_az, max_alt_time, max_alt, set_time, set_az = info
            deg_per_rad = 180.0/math.pi           # use to conv azimuth to deg
            try:
                pass_duration = timedelta(days=set_time-rise_time)  # timedelta
                r_angle = (rise_az*deg_per_rad)
                s_angle = (set_az*deg_per_rad)
            except TypeError:
                # when no set or rise time
                pass
            try:
                rising = rise_time.datetime()
                setting = set_time.datetime()
                pass_seconds = timedelta.total_seconds(pass_duration)
                tca = max_alt_time.datetime()
                max_el = (max_alt * deg_per_rad)
            except AttributeError:
                # when no set or rise time
                pass

            pass_data = {
                'start': rising,
                'end': setting,
                'duration': pass_seconds,
                'rise_az': r_angle,
                'set_az': s_angle,
                'tca': tca,
                'max_el': max_el,
                'gs': observer['name'],
                'sat': tle_line0.strip(),
            }

            try:
                # only update if set time > rise time
                if set_time > rise_time:
                    # new obs time = prev set time
                    ground_station.date = set_time
                    if ground_station.date <= end_time:
                        contacts.append(pass_data)
            except TypeError:
                pass

            # increase by 1 min and look for next pass
            ground_station.date = ground_station.date + ephem.minute
    except ValueError:
        # No (more) visible passes
        pass
    return contacts

## calc_access_time() function definition
#def calc_access_time(start, gs, tle, days, horizon='00:00'):
#    """Calculates Access Time in seconds/day.
#
#    Arguments:
#    start -- string formatted 'yyyy/mm/dd HH:MM:SS'
#    gs -- 4 element list containing desired [name,lat,lon,alt]
#    tle -- 3 element list containing desired tle [line0,line1,line2]
#    days -- num of day to calc/plot access time
#    horizon -- str optional specification for observer horizon (defualt 0 deg)
#    """
#    time = days
#    start_time = ephem.date(start)
#    access_list = []
#    day_list = []
#
#    for days in range(time):
#        num_passes = None
#        duration = 24.0
#        gs_passes = {}
#
#        gs_passes[tle.noradid] = get_passes(gs, tle.tle, start_time,
#                                            num_passes=num_passes,
#                                            duration=duration,
#                                            horizon=horizon)
#
#        access_time = 0
#        for sat, passes in gs_passes.items():
#            for obs in passes:
#                access_time = access_time + obs['duration']
#        access_list.append(access_time)
#        day_list.append(days)
#        start_time = start_time + 1
#    return day_list, access_list

# plot_access_time() function definition
def plot_access_time(start, gs, tle, days, horizon='00:00'):
    """Plots Access Time in seconds/day.

    Arguments:
    start -- string formatted 'yyyy/mm/dd HH:MM:SS'
    gs -- 4 element list containing desired [name,lat,lon,alt]
    tle -- 3 element list containing desired tle [line0,line1,line2]
    days -- num of day to calc/plot access time
    horizon -- str optional specification for observer horizon (defualt 0 deg)
    """
    tle = TLE(tle)

    day_list, access_list = calc_access_time(start, gs, tle, days,
                                             horizon=horizon)

    fig = plt.figure(1)
    fig.suptitle('%s Access time for %s GS' % (tle.name, gs[0]))

    s1 = plt.subplot(221)
    s1.plot(day_list, access_list, 'b.')
    plt.xlabel('Days from %s' % (start))
    plt.ylabel('Access time (sec/day)')

    plt.subplot(222)
    plt.plot(day_list, access_list, 'b-')
    plt.xlabel('Days from %s' % (start))

    plt.show()
<commit_msg>try out pyorbital with get_passes2()<commit_after># Python module for scheduling bazaar project
# Includes: TLE class
#           load_gs()
#           load_tles()
#           get_passes()
#           calc_access_time()
#           plot_access_time()

# Import necessary libraries
from datetime import timedelta
from itertools import islice
import json
import math

import ephem
import matplotlib.pyplot as plt
# import seaborn

# TLE class definition
class TLE:
    """TLE class used for various tle attributes.

    Args: a three element list containing tle lines
    """
    def __init__(self, tle):
        self.tle = tle
        self.tle0 = tle[0]
        self.tle1 = tle[1]
        self.tle2 = tle[2]
        self.name = tle[0].rstrip()
        self.noradid = tle[1][2:7]
        self.epoch = tle[1][18:32]
        self.inclination = tle[2][8:16]

    def __str__(self):
        return '%s\n%s\n%s' % (self.tle0, self.tle1, self.tle2)

# load_tles() function definition
def load_tles(filename):
    """Returns a list of tle's from a file.

    Arguments:
    filename -- file name string containing unparsed tles
    """
    data = []
    with open(filename) as f:
        while True:
            # an iterator that returns the next N lines and stops
            tripleline = islice(f, 3)
            # loop over these N lines, removing trailing spaces and \n
            tle = [x.rstrip() for x in tripleline]

            # only accept complete data
            # the end of the file *should* have len(tle)==0 but
            # this also handles extra junk at the end
            if len(tle) == 3:
                data.append(tle)
            else:
                break
    return data

def load_gs(filename):
    """Returns a list of gs dicts from a file.

    Arguments:
    filename -- file name string containing unparsed gs
        either JSON format, read directly to the dict
        or a text file with 4 lines per station

    gs has four elements: name, lat, lon, alt
    """

    if filename.lower().endswith('.json'):
        with open(filename) as f:
            stations = json.load(f)
        # satnogs network API returns "lng" instead of "lon".  Make both
        # available.
        for gs in stations:
            gs['lon'] = gs['lng']
    else:
        stations = []
        with open(filename) as f:
            while True:
                # an iterator that returns the next N lines and stops
                fourline = islice(f, 4)
                # loop over these N lines, removing trailing spaces and \n
                gs = [x.rstrip() for x in fourline]

                # only accept complete data
                # the end of the file *should* have len(tle)==0 but
                # this also handles extra junk at the end
                if len(gs) == 4:
                    stations.append(
                        dict(name=gs[0],
                             lat=float(gs[1]),
                             lon=float(gs[2]),
                             lng=float(gs[2]),
                             altitude=float(gs[3]),
                             )
                        )
                else:
                    break
    return stations

# get_passes() function definition
def get_passes(observer, tle, start_time,
               num_passes=None, duration=None, horizon='10:00'):
    """Config obs and sat, Return pass data for all passes in given interval.

    Arguments:
    observer -- 4 element list containing desired [name,lat,lon,alt]
    tle -- 3 element list containing desired tle [line0,line1,line2]
    start_time -- ephem.date string formatted 'yyyy/mm/dd hr:min:sec'
    num_passes -- integer number of desired passes (defualt None)
    duration -- float number of hours or fraction of hours (default None)
    horizon -- str optional specification for observer horizon (defualt 0 deg)

    Specify either num_passes or duration.
    If both, use min(num_passes, duration).
    If neither, find passes for next 24 hours.
    """

    tle_line0, tle_line1, tle_line2 = tle

    # Set up location of observer
    ground_station = ephem.Observer()
    ground_station.name = observer['name']        # name string
    ground_station.lon = str(observer['lon'])          # in degrees (+E)
    ground_station.lat = str(observer['lat'])          # in degrees (+N)
    ground_station.elevation = observer['altitude']       # in meters
    ground_station.date = ephem.date(start_time)  # in UTC
    ground_station.horizon = horizon              # in degrees

    # Read in most recent satellite TLE data
    sat = ephem.readtle(tle_line0, tle_line1, tle_line2)

    contacts = []

    if duration is None and num_passes is None:
        # get passes for next 24 hrs
        duration = 24
        # set num_passes > max passes possible in duration.
        # duration is in hours, so 4 per hour is large
        # enough for duration to break out of loop.
        num_passes = 4 * int(duration)
        # set end_time longer than suggested length for tle's
        end_time = ephem.date(ground_station.date+5*365)
    if duration is not None and num_passes is None:
        # set num_passes > max passes possible in duration.
        # duration is in hours, so 4 per hour is large
        # enough for duration to break out of loop.
        num_passes = 4 * int(duration)
        end_time = ephem.date(ground_station.date+duration*ephem.hour)
    if duration is None and num_passes is not None:
        # set end_time longer than suggested length for tle's
        end_time = ephem.date(ground_station.date+5*365)
    if num_passes is not None and duration is not None:
        # if both are given, use minimum
        end_time = ephem.date(ground_station.date+duration*ephem.hour)

    try:
        for i in range(num_passes):
            if ground_station.date > end_time:
                break
            sat.compute(ground_station)  # compute all body attributes for sat
            # next pass command yields array with [0]=rise time,
            # [1]=rise azimuth, [2]=max alt time, [3]=max alt,
            # [4]=set time, [5]=set azimuth
            info = ground_station.next_pass(sat)
            rise_time, rise_az, max_alt_time, max_alt, set_time, set_az = info
            deg_per_rad = 180.0/math.pi           # use to conv azimuth to deg
            try:
                pass_duration = timedelta(days=set_time-rise_time)  # timedelta
                r_angle = (rise_az*deg_per_rad)
                s_angle = (set_az*deg_per_rad)
            except TypeError:
                # when no set or rise time
                pass
            try:
                rising = rise_time.datetime()
                setting = set_time.datetime()
                pass_seconds = timedelta.total_seconds(pass_duration)
                tca = max_alt_time.datetime()
                max_el = (max_alt * deg_per_rad)
            except AttributeError:
                # when no set or rise time
                pass

            pass_data = {
                'start': rising,
                'end': setting,
                'duration': pass_seconds,
                'rise_az': r_angle,
                'set_az': s_angle,
                'tca': tca,
                'max_el': max_el,
                'gs': observer['name'],
                'sat': tle_line0.strip(),
            }

            try:
                # only update if set time > rise time
                if set_time > rise_time:
                    # new obs time = prev set time
                    ground_station.date = set_time
                    if ground_station.date <= end_time:
                        contacts.append(pass_data)
            except TypeError:
                pass

            # increase by 1 min and look for next pass
            ground_station.date = ground_station.date + ephem.minute
    except ValueError:
        # No (more) visible passes
        pass
    return contacts

def get_passes2(observer, tle, start_time,
               num_passes=None, duration=None, horizon='10:00'):
    """Config obs and sat, Return pass data for all passes in given interval.

    Arguments:
    observer -- 4 element list containing desired [name,lat,lon,alt]
    tle -- 3 element list containing desired tle [line0,line1,line2]
    start_time -- ephem.date string formatted 'yyyy/mm/dd hr:min:sec'
    num_passes -- integer number of desired passes (defualt None)
    duration -- float number of hours or fraction of hours (default None)
    horizon -- str optional specification for observer horizon (defualt 0 deg)

    Specify either num_passes or duration.
    If both, use min(num_passes, duration).
    If neither, find passes for next 24 hours.
    """
    from pyorbital.orbital import Orbital
    import datetime

    sat = Orbital(tle[0], line1=tle[1], line2=tle[2])

    time = ephem.date(start_time).datetime()
    passes = sat.get_next_passes(time,
                                 duration,
                                 observer['lng'],
                                 observer['lat'],
                                 observer['altitude'],
                                 horizon=10.0
                                 )

    contacts = []
    for rise, fall, maxtime in passes:
        pass_duration = fall - rise  # timedelta
        pass_seconds = timedelta.total_seconds(pass_duration)
        tca = maxtime

        # ignore bogus passes
        if pass_seconds < 1.0:
            continue

        def azel(time):
            az, el = sat.get_observer_look(
                time,
                observer['lng'],
                observer['lat'],
                observer['altitude']
            )
            return az, el

        _, max_el = azel(maxtime)
        r_angle, _ = azel(rise)
        s_angle, _ = azel(fall)

        pass_data = {
            'start': rise,
            'end': fall,
            'duration': pass_seconds,
            'rise_az': r_angle,
            'set_az': s_angle,
            'tca': tca,
            'max_el': max_el,
            'gs': observer['name'],
            'sat': tle[0].strip(),
        }
        contacts.append(pass_data)
    return contacts

## calc_access_time() function definition
#def calc_access_time(start, gs, tle, days, horizon='00:00'):
#    """Calculates Access Time in seconds/day.
#
#    Arguments:
#    start -- string formatted 'yyyy/mm/dd HH:MM:SS'
#    gs -- 4 element list containing desired [name,lat,lon,alt]
#    tle -- 3 element list containing desired tle [line0,line1,line2]
#    days -- num of day to calc/plot access time
#    horizon -- str optional specification for observer horizon (defualt 0 deg)
#    """
#    time = days
#    start_time = ephem.date(start)
#    access_list = []
#    day_list = []
#
#    for days in range(time):
#        num_passes = None
#        duration = 24.0
#        gs_passes = {}
#
#        gs_passes[tle.noradid] = get_passes(gs, tle.tle, start_time,
#                                            num_passes=num_passes,
#                                            duration=duration,
#                                            horizon=horizon)
#
#        access_time = 0
#        for sat, passes in gs_passes.items():
#            for obs in passes:
#                access_time = access_time + obs['duration']
#        access_list.append(access_time)
#        day_list.append(days)
#        start_time = start_time + 1
#    return day_list, access_list

# plot_access_time() function definition
def plot_access_time(start, gs, tle, days, horizon='00:00'):
    """Plots Access Time in seconds/day.

    Arguments:
    start -- string formatted 'yyyy/mm/dd HH:MM:SS'
    gs -- 4 element list containing desired [name,lat,lon,alt]
    tle -- 3 element list containing desired tle [line0,line1,line2]
    days -- num of day to calc/plot access time
    horizon -- str optional specification for observer horizon (defualt 0 deg)
    """
    tle = TLE(tle)

    day_list, access_list = calc_access_time(start, gs, tle, days,
                                             horizon=horizon)

    fig = plt.figure(1)
    fig.suptitle('%s Access time for %s GS' % (tle.name, gs[0]))

    s1 = plt.subplot(221)
    s1.plot(day_list, access_list, 'b.')
    plt.xlabel('Days from %s' % (start))
    plt.ylabel('Access time (sec/day)')

    plt.subplot(222)
    plt.plot(day_list, access_list, 'b-')
    plt.xlabel('Days from %s' % (start))

    plt.show()
<|endoftext|>


<commit_before>from typing import Optional, Tuple, Union

import cProfile
import difflib
import logging
import pstats

from ambassador import Cache, Config, IR, EnvoyConfig
from ambassador.ir.ir import IRFileChecker
from ambassador.fetch import ResourceFetcher
from ambassador.utils import SecretHandler, NullSecretHandler, Timer

# Types
OptionalStats = Optional[pstats.Stats]

class Profiler:
    def __init__(self):
        self.pr = cProfile.Profile()

    def __enter__(self) -> None:
        self.pr.enable()

    def __exit__(self, *args) -> None:
        self.pr.disable()

    def stats(self) -> OptionalStats:
        return pstats.Stats(self.pr).sort_stats("tottime")

class NullProfiler(Profiler):
    def __init__(self):
        pass

    def __enter__(self) -> None:
        pass

    def __exit__(self, *args) -> None:
        pass

    def stats(self) -> OptionalStats:
        return None

class Madness:
    def __init__(self, snapshot_path: str,
                 logger: Optional[logging.Logger]=None,
                 secret_handler: Optional[SecretHandler]=None,
                 file_checker: Optional[IRFileChecker]=None) -> None:
        if not logger:
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s madness %(levelname)s: %(message)s",
                datefmt="%Y-%m-%d %H:%M:%S"
            )

            logger = logging.getLogger('mockery')

        self.logger = logger

        if not secret_handler:
            secret_handler = NullSecretHandler(logger, None, None, "0")

        if not file_checker:
            file_checker = lambda f: True

        self.secret_handler = secret_handler
        self.file_checker = file_checker

        self.reset_cache()

        self.aconf_timer = Timer("aconf")
        self.fetcher_timer = Timer("fetcher")
        self.ir_timer = Timer("ir")
        self.econf_timer = Timer("econf")

        self.aconf = Config()

        with self.fetcher_timer:
            self.fetcher = ResourceFetcher(self.logger, self.aconf)
            self.fetcher.parse_watt(open(snapshot_path, "r").read())

        with self.aconf_timer:
            self.aconf.load_all(self.fetcher.sorted())

    def reset_cache(self) -> None:
        self.cache = Cache(self.logger)

    def summarize(self) -> None:
        for timer in [
            self.fetcher_timer,
            self.aconf_timer,
            self.ir_timer,
            self.econf_timer,
        ]:
            if timer:
                self.logger.info(timer.summary())

    def build_ir(self,
                 cache=True, profile=False, summarize=True) -> Tuple[IR, OptionalStats]:
        self.ir_timer.reset()

        _cache = self.cache if cache else None
        _pr = Profiler() if profile else NullProfiler()

        with self.ir_timer:
            with _pr:
                ir = IR(self.aconf, cache=_cache,
                        secret_handler=self.secret_handler)

        if summarize:
            self.summarize()

        return (ir, _pr.stats())

    def build_econf(self, ir: Union[IR, Tuple[IR, OptionalStats]],
                    cache=True, profile=False, summarize=True) -> Tuple[EnvoyConfig, OptionalStats]:
        self.econf_timer.reset()

        _cache = self.cache if cache else None
        _pr = Profiler() if profile else NullProfiler()

        _ir: Optional[IR] = None

        if isinstance(ir, tuple):
            _ir = ir[0]
        else:
            _ir = ir

        assert ir is not None

        with self.econf_timer:
            with _pr:
                econf = EnvoyConfig.generate(_ir, "V2", cache=_cache)

        if summarize:
            self.summarize()

        return (econf, _pr.stats())

    def build(self, cache=True, profile=False) -> Tuple[IR, EnvoyConfig, OptionalStats]:
        _cache = self.cache if cache else None

        _pr = Profiler() if profile else NullProfiler()

        with _pr:
            ir, _ = self.build_ir(cache=_cache, profile=False, summarize=False)
            econf, _ = self.build_econf(ir, cache=_cache, profile=False, summarize=False)

        self.summarize()

        return (ir, econf, _pr.stats())

    def diff(self, *rsrcs) -> None:
        jsons = [ rsrc.as_json() for rsrc in rsrcs ]

        if len(set(jsons)) == 1:
            return

        for i in range(len(rsrcs) - 1):
            if jsons[i] != jsons[i+1]:
                l1 = jsons[i].split("\n")
                l2 = jsons[i+1].split("\n")

                n1 = f"rsrcs[{i}]"
                n2 = f"rsrcs[{i+1}]"

                print("\n--------")

                for line in difflib.context_diff(l1, l2, fromfile=n1, tofile=n2):
                    line = line.rstrip()
                    print(line)
<commit_msg>(from AES) Madness: make it possible to load K8s YAML as well as watt snapshots.<commit_after>from typing import Optional, Tuple, Union

import cProfile
import difflib
import logging
import pstats

from ambassador import Cache, Config, IR, EnvoyConfig
from ambassador.ir.ir import IRFileChecker
from ambassador.fetch import ResourceFetcher
from ambassador.utils import SecretHandler, NullSecretHandler, Timer

# Types
OptionalStats = Optional[pstats.Stats]

class Profiler:
    def __init__(self):
        self.pr = cProfile.Profile()

    def __enter__(self) -> None:
        self.pr.enable()

    def __exit__(self, *args) -> None:
        self.pr.disable()

    def stats(self) -> OptionalStats:
        return pstats.Stats(self.pr).sort_stats("tottime")

class NullProfiler(Profiler):
    def __init__(self):
        pass

    def __enter__(self) -> None:
        pass

    def __exit__(self, *args) -> None:
        pass

    def stats(self) -> OptionalStats:
        return None

class Madness:
    def __init__(self,
                 watt_path: Optional[str]=None,
                 yaml_path: Optional[str]=None,
                 logger: Optional[logging.Logger]=None,
                 secret_handler: Optional[SecretHandler]=None,
                 file_checker: Optional[IRFileChecker]=None) -> None:
        if not logger:
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s madness %(levelname)s: %(message)s",
                datefmt="%Y-%m-%d %H:%M:%S"
            )

            logger = logging.getLogger('mockery')

        self.logger = logger

        if not secret_handler:
            secret_handler = NullSecretHandler(logger, None, None, "0")

        if not file_checker:
            file_checker = lambda f: True

        self.secret_handler = secret_handler
        self.file_checker = file_checker

        self.reset_cache()

        self.aconf_timer = Timer("aconf")
        self.fetcher_timer = Timer("fetcher")
        self.ir_timer = Timer("ir")
        self.econf_timer = Timer("econf")

        self.aconf = Config()

        with self.fetcher_timer:
            self.fetcher = ResourceFetcher(self.logger, self.aconf)

            if watt_path:
                self.fetcher.parse_watt(open(watt_path, "r").read())
            elif yaml_path:
                self.fetcher.parse_yaml(open(yaml_path, "r").read(), k8s=True)
            else:
                raise RuntimeError("either watt_path or yaml_path must be provided")

        with self.aconf_timer:
            self.aconf.load_all(self.fetcher.sorted())

    def reset_cache(self) -> None:
        self.cache = Cache(self.logger)

    def summarize(self) -> None:
        for timer in [
            self.fetcher_timer,
            self.aconf_timer,
            self.ir_timer,
            self.econf_timer,
        ]:
            if timer:
                self.logger.info(timer.summary())

    def build_ir(self,
                 cache=True, profile=False, summarize=True) -> Tuple[IR, OptionalStats]:
        self.ir_timer.reset()

        _cache = self.cache if cache else None
        _pr = Profiler() if profile else NullProfiler()

        with self.ir_timer:
            with _pr:
                ir = IR(self.aconf, cache=_cache,
                        secret_handler=self.secret_handler)

        if summarize:
            self.summarize()

        return (ir, _pr.stats())

    def build_econf(self, ir: Union[IR, Tuple[IR, OptionalStats]],
                    cache=True, profile=False, summarize=True) -> Tuple[EnvoyConfig, OptionalStats]:
        self.econf_timer.reset()

        _cache = self.cache if cache else None
        _pr = Profiler() if profile else NullProfiler()

        _ir: Optional[IR] = None

        if isinstance(ir, tuple):
            _ir = ir[0]
        else:
            _ir = ir

        assert ir is not None

        with self.econf_timer:
            with _pr:
                econf = EnvoyConfig.generate(_ir, "V2", cache=_cache)

        if summarize:
            self.summarize()

        return (econf, _pr.stats())

    def build(self, cache=True, profile=False) -> Tuple[IR, EnvoyConfig, OptionalStats]:
        _cache = self.cache if cache else None

        _pr = Profiler() if profile else NullProfiler()

        with _pr:
            ir, _ = self.build_ir(cache=_cache, profile=False, summarize=False)
            econf, _ = self.build_econf(ir, cache=_cache, profile=False, summarize=False)

        self.summarize()

        return (ir, econf, _pr.stats())

    def diff(self, *rsrcs) -> None:
        jsons = [ rsrc.as_json() for rsrc in rsrcs ]

        if len(set(jsons)) == 1:
            return

        for i in range(len(rsrcs) - 1):
            if jsons[i] != jsons[i+1]:
                l1 = jsons[i].split("\n")
                l2 = jsons[i+1].split("\n")

                n1 = f"rsrcs[{i}]"
                n2 = f"rsrcs[{i+1}]"

                print("\n--------")

                for line in difflib.context_diff(l1, l2, fromfile=n1, tofile=n2):
                    line = line.rstrip()
                    print(line)
<|endoftext|>


<commit_before>#!/usr/bin/env python3
#* This file is part of the MOOSE framework
#* https://www.mooseframework.org
#*
#* All rights reserved, see COPYRIGHT for full restrictions
#* https://github.com/idaholab/moose/blob/master/COPYRIGHT
#*
#* Licensed under LGPL 2.1, please see LICENSE for details
#* https://www.gnu.org/licenses/lgpl-2.1.html

import os

class ImageDiffer(object):
    """
    A class for comparing images using the structural similarity index (SSIM).

    https://en.wikipedia.org/wiki/Structural_similarity

    Args:
      file1[str]: The base file to compare against (gold).
      file2[str]: The file to be compared.

    Kwargs:
      allowed[float]: (Default: 0.95) The allowed lower limit of the SSIM (1 is identical images).
    """

    def __init__(self, file1, file2, **kwargs):

        # Store the file names
        self.__files = [file1, file2]

        # Extract the optional arguments
        self.__allowed = float(kwargs.pop('allowed', 0.95))

        # Storage for error messages, each stored as a tuple: (error, message)
        self.__error = 0 # The computed error
        self.__errors = []

        # Read the image files
        self.__data = []
        self.__data.append(self.__readImage(self.__files[0]))
        self.__data.append(self.__readImage(self.__files[1]))

        # Perform comparison
        self.__compare()

    def fail(self):
        """
        Check the comparison status. (public)

        Returns:
           bool: True when the test failed.
        """
        return len(self.__errors) > 0

    def message(self, **kwargs):
        """
        Print the error message(s). (public)

        Returns:
          str: The output message as a single string.
        """

        # Header
        output = []
        output.append('Running ImageDiffer.py')
        output.append('            File 1: ' + self.__files[0])
        output.append('            File 2: ' + self.__files[1])
        output.append('    Allowed (SSIM): ' + str(self.__allowed))
        output.append('   Computed (SSIM): ' + str(self.__error))
        output.append('     No. of errors: ' + str(len(self.__errors)))

        # Errors
        cnt = 0
        for e in self.__errors:
            cnt += 1
            output.append('')
            output.append('ERROR ' + str(cnt) + ':')
            output.append('  ' + e[0])
            if e[1]:
                output.append('    ' + e[1])

        # Print the output
        if kwargs.pop('output', False):
            print('\n'.join(output))

        # Return the text, as a single string
        return '\n'.join(output)
        # Errors
        cnt = 0
        for e in self.__errors:
            cnt += 1
            output.append('')
            output.append('ERROR ' + str(cnt) + ':')
            output.append('  ' + e[0])
            if e[1]:
                output.append('    ' + e[1])

        # Print the output
        if kwargs.pop('output', False):
            print('\n'.join(output))

        # Return the text, as a single string
        return '\n'.join(output)

    def __compare(self):
        """
        Perform image comparison. (private)
        """

        # Do nothing if something failed to open
        if len(self.__errors) > 0:
            return

        # Check sizes
        if (self.__data[0].size != self.__data[1].size):
            err = 'The two images are different sizes'
            msg  =  ['  File 1: ' + self.__files[0]]
            msg +=  ['    size: ' + str(self.__data[0].size)]
            msg +=  ['  File 2: ' + self.__files[1]]
            msg +=  ['    size: ' + str(self.__data[1].size)]
            self.__addError(err, msg)
            return

        # Compute the error
        import skimage.measure
        self.__error = skimage.metrics.structural_similarity(self.__data[0], self.__data[1], multichannel=True)

        # Report the error
        if self.__error < self.__allowed:
            err = 'The files are different.'
            msg = ['The difference of the images exceeds the "allowed" SSIM.']
            msg += ['                 Allowed (SSIM): ' + str(self.__allowed)]
            msg += ['                Computed (SSIM): ' + str(self.__error)]
            msg += ['                Rel. difference: ' + str( abs(self.__allowed - self.__error) / self.__error)]

            self.__addError(err, msg)
            return

    def __readImage(self, filename):
        """
        A read function that appends an error message if the read fails. (private)
        """

        if not os.path.exists(filename):
            self.__addError('Failed to open ' + filename + ', the file does not exist.')
            return None

        import matplotlib.pyplot as plt
        return plt.imread(filename)

    def __addError(self, err, msg=[]):
        """
        Add an ImageError object to the storage vector (private)

        Args:
          err[str]: A string containing the error message.
          msg[list]: A detailed message for the error.
        """
        self.__errors.append((err, '\n'.join(msg)))

# This file is executable and allows for running the ImageDiffer via the command line
if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='Perform comparison of images.')
    parser.add_argument('files', type=str, nargs='+', help="The image(s) to compare. If a single image is provided the 'gold' version is used.")
    args = parser.parse_args()

    # Test files
    n = len(args.files)
    if n == 1:
        file1 = args.files[0]
        file0 = os.path.join(os.path.dirname(file1), 'gold', os.path.basename(file1))
    elif n == 2:
        file0 = args.files[0]
        file1 = args.files[1]
    else:
        print("You must specify one or two files for comparison, see -h")

    d = ImageDiffer(file0, file1)
    print('\n\n' + d.message())
<commit_msg>Support old version of skimage<commit_after>#!/usr/bin/env python3
#* This file is part of the MOOSE framework
#* https://www.mooseframework.org
#*
#* All rights reserved, see COPYRIGHT for full restrictions
#* https://github.com/idaholab/moose/blob/master/COPYRIGHT
#*
#* Licensed under LGPL 2.1, please see LICENSE for details
#* https://www.gnu.org/licenses/lgpl-2.1.html

import os

class ImageDiffer(object):
    """
    A class for comparing images using the structural similarity index (SSIM).

    https://en.wikipedia.org/wiki/Structural_similarity

    Args:
      file1[str]: The base file to compare against (gold).
      file2[str]: The file to be compared.

    Kwargs:
      allowed[float]: (Default: 0.95) The allowed lower limit of the SSIM (1 is identical images).
    """

    def __init__(self, file1, file2, **kwargs):

        # Store the file names
        self.__files = [file1, file2]

        # Extract the optional arguments
        self.__allowed = float(kwargs.pop('allowed', 0.95))

        # Storage for error messages, each stored as a tuple: (error, message)
        self.__error = 0 # The computed error
        self.__errors = []

        # Read the image files
        self.__data = []
        self.__data.append(self.__readImage(self.__files[0]))
        self.__data.append(self.__readImage(self.__files[1]))

        # Perform comparison
        self.__compare()

    def fail(self):
        """
        Check the comparison status. (public)

        Returns:
           bool: True when the test failed.
        """
        return len(self.__errors) > 0

    def message(self, **kwargs):
        """
        Print the error message(s). (public)

        Returns:
          str: The output message as a single string.
        """

        # Header
        output = []
        output.append('Running ImageDiffer.py')
        output.append('            File 1: ' + self.__files[0])
        output.append('            File 2: ' + self.__files[1])
        output.append('    Allowed (SSIM): ' + str(self.__allowed))
        output.append('   Computed (SSIM): ' + str(self.__error))
        output.append('     No. of errors: ' + str(len(self.__errors)))

        # Errors
        cnt = 0
        for e in self.__errors:
            cnt += 1
            output.append('')
            output.append('ERROR ' + str(cnt) + ':')
            output.append('  ' + e[0])
            if e[1]:
                output.append('    ' + e[1])

        # Print the output
        if kwargs.pop('output', False):
            print('\n'.join(output))

        # Return the text, as a single string
        return '\n'.join(output)
        # Errors
        cnt = 0
        for e in self.__errors:
            cnt += 1
            output.append('')
            output.append('ERROR ' + str(cnt) + ':')
            output.append('  ' + e[0])
            if e[1]:
                output.append('    ' + e[1])

        # Print the output
        if kwargs.pop('output', False):
            print('\n'.join(output))

        # Return the text, as a single string
        return '\n'.join(output)

    def __compare(self):
        """
        Perform image comparison. (private)
        """

        # Do nothing if something failed to open
        if len(self.__errors) > 0:
            return

        # Check sizes
        if (self.__data[0].size != self.__data[1].size):
            err = 'The two images are different sizes'
            msg  =  ['  File 1: ' + self.__files[0]]
            msg +=  ['    size: ' + str(self.__data[0].size)]
            msg +=  ['  File 2: ' + self.__files[1]]
            msg +=  ['    size: ' + str(self.__data[1].size)]
            self.__addError(err, msg)
            return

        # Compute the error using "Structural Similarity Index"
        try:
            # skimage version >= 0.17
            import skimage.metrics
            self.__error = skimage.metrics.structural_similarity(self.__data[0], self.__data[1], multichannel=True)
        except:
            # legacy support
            import skimage.measure
            self.__error = skimage.measure.compare_ssim(self.__data[0], self.__data[1], multichannel=True)

        # Report the error
        if self.__error < self.__allowed:
            err = 'The files are different.'
            msg = ['The difference of the images exceeds the "allowed" SSIM.']
            msg += ['                 Allowed (SSIM): ' + str(self.__allowed)]
            msg += ['                Computed (SSIM): ' + str(self.__error)]
            msg += ['                Rel. difference: ' + str( abs(self.__allowed - self.__error) / self.__error)]

            self.__addError(err, msg)
            return

    def __readImage(self, filename):
        """
        A read function that appends an error message if the read fails. (private)
        """

        if not os.path.exists(filename):
            self.__addError('Failed to open ' + filename + ', the file does not exist.')
            return None

        import matplotlib.pyplot as plt
        return plt.imread(filename)

    def __addError(self, err, msg=[]):
        """
        Add an ImageError object to the storage vector (private)

        Args:
          err[str]: A string containing the error message.
          msg[list]: A detailed message for the error.
        """
        self.__errors.append((err, '\n'.join(msg)))

# This file is executable and allows for running the ImageDiffer via the command line
if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='Perform comparison of images.')
    parser.add_argument('files', type=str, nargs='+', help="The image(s) to compare. If a single image is provided the 'gold' version is used.")
    args = parser.parse_args()

    # Test files
    n = len(args.files)
    if n == 1:
        file1 = args.files[0]
        file0 = os.path.join(os.path.dirname(file1), 'gold', os.path.basename(file1))
    elif n == 2:
        file0 = args.files[0]
        file1 = args.files[1]
    else:
        print("You must specify one or two files for comparison, see -h")

    d = ImageDiffer(file0, file1)
    print('\n\n' + d.message())
<|endoftext|>