File: parse.py

package info (click to toggle)
glueviz 0.9.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 17,180 kB
  • ctags: 6,728
  • sloc: python: 37,111; makefile: 134; sh: 60
file content (273 lines) | stat: -rw-r--r-- 8,323 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
from __future__ import absolute_import, division, print_function

import re
import random

from glue.core.component_link import ComponentLink
from glue.core.subset import Subset, SubsetState
from glue.core.data import ComponentID


TAG_RE = re.compile('\{\s*(?P<tag>\S+)\s*\}')

__all__ = ['ParsedCommand', 'ParsedSubsetState']


def _ensure_only_component_references(cmd, references):
    """ Search through tag references in a command, ensure that
    they all reference ComponentIDs

    Parameters
    ----------
    cmd : string. A template command
    referenes : a mapping from tags to substitution objects

    Raises
    ------
    TypeError, if cmd does not refer only to ComponentIDs
    """
    for match in TAG_RE.finditer(cmd):
        tag = match.group('tag')
        if tag not in references or not \
                isinstance(references[tag], ComponentID):
            raise TypeError(
                "Reference to %s, which is not a ComponentID" % tag)


def _reference_list(cmd, references):
    """ Return a list of the values in the references mapping whose
    keys appear in the command

    Parameters
    ----------
    cmd : string. A template command
    references : a mapping from tags to substitution objects

    Returns
    -------
    A list of the unique values in references that appear in the command

    Examples
    --------
    >>> cmd = '{g} - {r} + {g}'
    >>> references = {'g' : g_object, 'r' : r_object, 'i' : i_object}
    >>> _reference_list(cmd, references)
    [g_object, r_object]

    Raises
    ------
    KeyError: if tags in the command aren't in the reference mapping
    """
    try:
        return list(set(references[m.group('tag')]
                        for m in TAG_RE.finditer(cmd)))
    except KeyError:
        raise KeyError("Tags from command not in reference mapping")


def _dereference(cmd, references):
    """ Dereference references in the template command, to refer
    to objects in the reference mapping

    Parameters
    ----------
    cmd : Command string
    references : mapping from template tags to objects

    Returns
    -------
    A new command, where all the tags have been subsituted as follows:
      "{tag}" -> 'data[references["tag"], __view]', if references[tag] is a ComponentID
      "{tag}" -> 'references["tag"].to_mask(__view)' if references[tag] is a Subset

    __view is a placeholder variable referencing the view
    passed to data.__getitem__ and subset.to_mask

    Raises
    ------
    TypeError, if a tag in the command maps to something other than
    a ComponentID or Subset object
    """
    def sub_func(match):
        tag = match.group('tag')
        if isinstance(references[tag], ComponentID):
            return 'data[references["%s"], __view]' % tag
        elif isinstance(references[tag], Subset):
            return 'references["%s"].to_mask(__view)' % tag
        else:
            raise TypeError("Tag %s maps to unrecognized type: %s" %
                            (tag, type(references[tag])))
    return TAG_RE.sub(sub_func, cmd)


def _dereference_random(cmd):
    """
    Dereference references in the template command, to refer
    to random floating-point values. This is used to quickly test that the
    command evaluates without errors.

    Parameters
    ----------
    cmd : str
        Command string

    Returns
    -------
    A new command, where all the tags have been subsituted by floating point values
    """
    def sub_func(match):
        tag = match.group('tag')
        return str(random.random())
    return TAG_RE.sub(sub_func, cmd)


class InvalidTagError(ValueError):
    def __init__(self, tag, references):
        msg = ("Tag %s not in reference mapping: %s" %
               (tag, sorted(references.keys())))
        self.tag = tag
        self.references = references
        super(InvalidTagError, self).__init__(msg)


def _validate(cmd, references):
    """ Make sure all references in the command are in the reference mapping

    Raises
    ------
    TypeError, if a tag is missing from references
    """
    for match in TAG_RE.finditer(cmd):
        tag = match.group('tag')
        if tag not in references:
            raise InvalidTagError(tag, references)


class ParsedCommand(object):

    """ Class to manage commands that define new components and subsets """

    def __init__(self, cmd, references):
        """ Create a new parsed command object

        Parameters
        ----------
        cmd : str. A template command. Can only reference ComponentID objects
        references : mapping from command templates to substitution objects
        """
        _validate(cmd, references)
        self._cmd = cmd
        self._references = references

    def ensure_only_component_references(self):
        _ensure_only_component_references(self._cmd, self._references)

    @property
    def reference_list(self):
        return _reference_list(self._cmd, self._references)

    def evaluate(self, data, view=None):
        from glue import env
        # pylint: disable=W0613, W0612
        references = self._references
        cmd = _dereference(self._cmd, self._references)

        scope = vars(env)
        scope['__view'] = view

        global_variables = vars(env)

        # We now import math modules if not already defined in local or
        # global variables
        if 'numpy' not in global_variables and 'numpy' not in locals():
            import numpy
        if 'np' not in global_variables and 'np' not in locals():
            import numpy as np
        if 'math' not in global_variables and 'math' not in locals():
            import math

        return eval(cmd, global_variables, locals())  # careful!

    def evaluate_test(self, view=None):
        from glue import env
        cmd = _dereference_random(self._cmd)

        scope = vars(env)
        scope['__view'] = view

        global_variables = vars(env)

        # We now import math modules if not already defined in local or
        # global variables
        if 'numpy' not in global_variables and 'numpy' not in locals():
            import numpy
        if 'np' not in global_variables and 'np' not in locals():
            import numpy as np
        if 'math' not in global_variables and 'math' not in locals():
            import math

        return eval(cmd, global_variables, locals())  # careful!
        
    def __gluestate__(self, context):
        return dict(cmd=self._cmd,
                    references=dict((k, context.id(v))
                                    for k, v in self._references.items()))

    @classmethod
    def __setgluestate__(cls, rec, context):
        cmd = rec['cmd']
        ref = dict((k, context.object(v))
                   for k, v in rec['references'].items())
        return cls(cmd, ref)


class ParsedComponentLink(ComponentLink):

    """ Class to create a new ComponentLink from a ParsedCommand object. """

    def __init__(self, to_, parsed):
        """ Create a new link

        Parameters
        ----------
        to_ : ComponentID instance to associate with the new component
        parsed : A ParsedCommand object
        """
        parsed.ensure_only_component_references()
        super(ParsedComponentLink, self).__init__(
            parsed.reference_list, to_, lambda: None)
        self._parsed = parsed

    def compute(self, data, view=None):
        return self._parsed.evaluate(data, view)

    def __gluestate__(self, context):
        return dict(parsed=context.do(self._parsed),
                    to=context.id(self.get_to_id()))

    @classmethod
    def __setgluestate__(cls, rec, context):
        return cls(context.object(rec['to']),
                   context.object(rec['parsed']))


class ParsedSubsetState(SubsetState):

    """ A SubsetState defined by a ParsedCommand object """

    def __init__(self, parsed):
        """ Create a new object

        Parameters
        ----------
        parsed : A ParsedCommand object
        """
        super(ParsedSubsetState, self).__init__()
        self._parsed = parsed

    def to_mask(self, data, view=None):
        """ Calculate the new mask by evaluating the dereferenced command """
        result = self._parsed.evaluate(data)
        if view is not None:
            result = result[view]
        return result