~vpec/maus/tof_calib_read

« back to all changes in this revision

Viewing changes to src/reduce/ReducePyMatplotlibHistogram/ReducePyMatplotlibHistogram.py

reverse merge from trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
ReducePyMatplotlibHistogram is a base class for classes that create
 
3
histograms using matplotlib.
 
4
"""
 
5
#  This file is part of MAUS: http://micewww.pp.rl.ac.uk:8080/projects/maus
 
6
 
7
#  MAUS is free software: you can redistribute it and/or modify
 
8
#  it under the terms of the GNU General Public License as published by
 
9
#  the Free Software Foundation, either version 3 of the License, or
 
10
#  (at your option) any later version.
 
11
 
12
#  MAUS is distributed in the hope that it will be useful,
 
13
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#  GNU General Public License for more details.
 
16
 
17
#  You should have received a copy of the GNU General Public License
 
18
#  along with MAUS.  If not, see <http://www.gnu.org/licenses/>.
 
19
 
 
20
import base64
 
21
import json
 
22
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
 
23
from matplotlib.figure import Figure
 
24
import StringIO
 
25
 
 
26
class ReducePyMatplotlibHistogram: # pylint: disable=R0903 
 
27
    """
 
28
    @class ReducePyMatplotlibHistogram.PyMatplotlibHistogram is 
 
29
    a base class for classes that create histograms using matplotlib. 
 
30
    PyMatplotlibHistogram maintains a histogram created using
 
31
    matplotlib.
 
32
 
 
33
    The histogram output is a JSON document of form:
 
34
 
 
35
    @verbatim
 
36
    {"image": {"content":"...a description of the image...",
 
37
               "tag": TAG,
 
38
               "image_type": "eps", 
 
39
               "data": "...base 64 encoded image..."}}
 
40
    @endverbatim
 
41
 
 
42
    where "TAG" is specified by the sub-class. If 
 
43
    "histogram_auto_number" (see below) is "true" then the TAG will
 
44
    have a number N appended where N means that the histogram was
 
45
    produced as a consequence of the (N + 1)th spill processed 
 
46
    by the worker. The number will be zero-padded to form a six digt
 
47
    string e.g. "00000N". If "histogram_auto_number" is false 
 
48
    then no such number is appended.
 
49
 
 
50
    In case of errors the output document is just the input document
 
51
    with an "errors" field containing the error e.g.
 
52
 
 
53
    @verbatim
 
54
    {"errors": {..., "bad_json_document": "unable to do json.loads on input"}}
 
55
    {"errors": {..., "no_digits": "no digits"}}
 
56
    @endverbatim
 
57
 
 
58
    The caller can configure the worker and specify:
 
59
 
 
60
    -Image type ("histogram_image_type"). Must be one of those
 
61
     supported by matplot lib (currently "svg", "ps", "emf", "rgba",
 
62
     "raw", "svgz", "pdf", "eps", "png"). Default: "eps".
 
63
    -Auto-number ("histogram_auto_number"). Default: false. Flag
 
64
     that determines if the image tag (see above) has the spill count
 
65
     appended to it or not.
 
66
 
 
67
    Sub-classes must override:
 
68
 
 
69
    -_configure_at_birth - to extract any additional
 
70
     sub-class-specific configuration from data cards.
 
71
    -_update_histogram. This should update the histogram, axes, scale
 
72
     and make any other desired changes. The _content attribute can
 
73
     be updated with a textual description of the content of the
 
74
     histogram.
 
75
    -_cleanup_at_death - to do any sub-class-specific cleanup.
 
76
    """
 
77
 
 
78
    def __init__(self):
 
79
        """
 
80
        Set initial attribute values.
 
81
        @param self Object reference.
 
82
        """
 
83
        self.spill_count = 0 # Number of spills processed to date.
 
84
        self.image_type = "eps"
 
85
        self.auto_number = False
 
86
        self._histogram = None # matplotlib histogram.
 
87
        self._tag = "graph" # Histogram name tag.
 
88
        self._content = "" # Description of histogram content.
 
89
 
 
90
    def birth(self, config_json):
 
91
        """
 
92
        Configure worker from data cards. If "image_type" is not
 
93
        in those supported then a ValueError is thrown.
 
94
        @param self Object reference.
 
95
        @param config_json JSON document string.
 
96
        @returns True if configuration succeeded. 
 
97
        """
 
98
        config_doc = json.loads(config_json)
 
99
 
 
100
        self._histogram = self._create_histogram()
 
101
 
 
102
        key = "histogram_auto_number"
 
103
        if key in config_doc:
 
104
            self.auto_number = config_doc[key]
 
105
 
 
106
        key = "histogram_image_type"
 
107
        if key in config_doc:
 
108
            self.image_type = config_doc[key]
 
109
        else:
 
110
            self.image_type = "eps"
 
111
 
 
112
        if self.image_type not in \
 
113
            self._histogram.get_supported_filetypes().keys():
 
114
            error = "Unsupported histogram image type: %s Expect one of %s" \
 
115
                % (self.image_type, 
 
116
                   self._histogram.get_supported_filetypes().keys())
 
117
            raise ValueError(error)
 
118
 
 
119
        self.spill_count = 0
 
120
        self._content = ""
 
121
 
 
122
        # Do sub-class-specific configuration.
 
123
        return self._configure_at_birth(config_doc)
 
124
 
 
125
    def _configure_at_birth(self, config_doc):
 
126
        """
 
127
        Perform sub-class-specific configuration from data cards. When
 
128
        this is called a sub-class can assume that self._histogram 
 
129
        points to a matplotlib FigureCanvas that they can then customise.
 
130
        @param self Object reference.
 
131
        @param config_json JSON document.
 
132
        @returns True if configuration succeeded. 
 
133
        """
 
134
 
 
135
    def process(self, json_string):
 
136
        """
 
137
        Update the histogram with data from the current spill
 
138
        and output the histogram.        
 
139
        @param self Object reference.
 
140
        @param json_string String with current JSON document.
 
141
        @returns JSON document containing current histogram.
 
142
        """
 
143
 
 
144
        # Load and validate the JSON document.
 
145
        try:
 
146
            json_doc = json.loads(json_string.rstrip())
 
147
        except ValueError:
 
148
            json_doc = {"errors": {"bad_json_document":
 
149
                                "unable to do json.loads on input"} }
 
150
            return json.dumps(json_doc)
 
151
 
 
152
        result = self._update_histogram(json_doc)
 
153
        if (result != None):
 
154
            return json.dumps(result)
 
155
 
 
156
        return self._create_image_json()
 
157
 
 
158
    def _update_histogram(self, json_doc):
 
159
        """
 
160
        Update the histogram with data from the current spill
 
161
        and output the histogram. Sub-classes must define this.
 
162
        @param self Object reference.
 
163
        @param json_doc Current spill..
 
164
        @returns None if histogram was created or a JSON document with
 
165
        error messages if there were problems (e.g. information was
 
166
        missing from the spill).
 
167
        """
 
168
 
 
169
    def death(self): #pylint: disable=R0201
 
170
        """
 
171
        Invokes _cleanup_at_death().
 
172
        @returns True
 
173
        """
 
174
        return self._cleanup_at_death()
 
175
 
 
176
    def _cleanup_at_death(self):
 
177
        """
 
178
        A no-op. Can be overridden by sub-classes for sub-class-specific
 
179
        clean-up at death time.
 
180
        @param self Object reference.
 
181
        @returns True
 
182
        """
 
183
 
 
184
    def _create_histogram(self): #pylint: disable=R0201
 
185
        """
 
186
        Create a histogram using matplotlib.
 
187
        @param self Object reference.
 
188
        @returns matplotlib FigureCanvas representing the histogram.
 
189
        """
 
190
        figure = Figure(figsize=(6, 6))
 
191
        histogram = FigureCanvas(figure)
 
192
        axes = figure.add_subplot(111)
 
193
        axes.grid(True, linestyle="-", color="0.75")
 
194
        return histogram
 
195
 
 
196
    def _rescale_axes(self, xmin, xmax, ymin, ymax, xfudge = 0.5, yfudge = 0.5): #pylint: disable=C0301, R0913
 
197
        """
 
198
        Rescale the X and Y axes of the histogram to show the given
 
199
        axis ranges. Fudge factors are used avoid matplotlib warning
 
200
        about "Attempting to set identical bottom==top" which arises
 
201
        if the axes are set to be exactly the maximum of the data.
 
202
        @param self Object reference.
 
203
        @param xmin Minimum X value.
 
204
        @param xmax Maximum X value.
 
205
        @param ymin Minimum Y value.
 
206
        @param ymin Maximum Y value.
 
207
        @param xfudge X fudge factor.
 
208
        @param yfudge Y fudge factor.
 
209
        @returns matplotlib FigureCanvas representing the histogram.
 
210
        """
 
211
        # Fudge factors are used
 
212
        self._histogram.figure.get_axes()[0].set_xlim( \
 
213
            [xmin, xmax + xfudge])
 
214
        self._histogram.figure.get_axes()[0].set_ylim( \
 
215
            [ymin, ymax + yfudge])
 
216
 
 
217
    def _create_image_json(self):
 
218
        """
 
219
        Create JSON document for output by the worker with the
 
220
        content description, image type, tag and base-64 encoded
 
221
        data from the histogram.
 
222
        @param self Object reference.
 
223
        @returns JSON document containing current histogram.
 
224
        """
 
225
        json_doc = {}
 
226
        json_doc["image"] = {}
 
227
        if (self.auto_number):
 
228
            tag = "%s%06d" % (self._tag, self.spill_count)
 
229
        else:
 
230
            tag = "%s" % (self._tag)
 
231
        data = self.__convert_to_binary(self._histogram)
 
232
        json_doc["image"]["content"] = self._content
 
233
        json_doc["image"]["tag"] = tag
 
234
        json_doc["image"]["image_type"] = self.image_type
 
235
        json_doc["image"]["data"] = data
 
236
        self.spill_count += 1
 
237
        return json.dumps(json_doc)
 
238
 
 
239
    def __convert_to_binary(self, histogram): #pylint: disable=R0201
 
240
        """
 
241
        Convert histogram to binary format.
 
242
        @param self Object reference.
 
243
        @param histogram matplotlib FigureCanvas representing the
 
244
        histogram. 
 
245
        @returns representation of histogram in base 64-encoded image 
 
246
        type format.
 
247
        """
 
248
        data_file = StringIO.StringIO() 
 
249
        histogram.print_figure(data_file, dpi=500, format=self.image_type)
 
250
        data_file.seek(0)
 
251
        data = data_file.read()
 
252
        return base64.b64encode(data)
 
253