2
ReducePyMatplotlibHistogram is a base class for classes that create
3
histograms using matplotlib.
5
# This file is part of MAUS: http://micewww.pp.rl.ac.uk:8080/projects/maus
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.
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.
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/>.
22
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
23
from matplotlib.figure import Figure
26
class ReducePyMatplotlibHistogram: # pylint: disable=R0903
28
@class ReducePyMatplotlibHistogram.PyMatplotlibHistogram is
29
a base class for classes that create histograms using matplotlib.
30
PyMatplotlibHistogram maintains a histogram created using
33
The histogram output is a JSON document of form:
36
{"image": {"content":"...a description of the image...",
39
"data": "...base 64 encoded image..."}}
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.
50
In case of errors the output document is just the input document
51
with an "errors" field containing the error e.g.
54
{"errors": {..., "bad_json_document": "unable to do json.loads on input"}}
55
{"errors": {..., "no_digits": "no digits"}}
58
The caller can configure the worker and specify:
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.
67
Sub-classes must override:
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
75
-_cleanup_at_death - to do any sub-class-specific cleanup.
80
Set initial attribute values.
81
@param self Object reference.
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.
90
def birth(self, config_json):
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.
98
config_doc = json.loads(config_json)
100
self._histogram = self._create_histogram()
102
key = "histogram_auto_number"
103
if key in config_doc:
104
self.auto_number = config_doc[key]
106
key = "histogram_image_type"
107
if key in config_doc:
108
self.image_type = config_doc[key]
110
self.image_type = "eps"
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" \
116
self._histogram.get_supported_filetypes().keys())
117
raise ValueError(error)
122
# Do sub-class-specific configuration.
123
return self._configure_at_birth(config_doc)
125
def _configure_at_birth(self, config_doc):
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.
135
def process(self, json_string):
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.
144
# Load and validate the JSON document.
146
json_doc = json.loads(json_string.rstrip())
148
json_doc = {"errors": {"bad_json_document":
149
"unable to do json.loads on input"} }
150
return json.dumps(json_doc)
152
result = self._update_histogram(json_doc)
154
return json.dumps(result)
156
return self._create_image_json()
158
def _update_histogram(self, json_doc):
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).
169
def death(self): #pylint: disable=R0201
171
Invokes _cleanup_at_death().
174
return self._cleanup_at_death()
176
def _cleanup_at_death(self):
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.
184
def _create_histogram(self): #pylint: disable=R0201
186
Create a histogram using matplotlib.
187
@param self Object reference.
188
@returns matplotlib FigureCanvas representing the histogram.
190
figure = Figure(figsize=(6, 6))
191
histogram = FigureCanvas(figure)
192
axes = figure.add_subplot(111)
193
axes.grid(True, linestyle="-", color="0.75")
196
def _rescale_axes(self, xmin, xmax, ymin, ymax, xfudge = 0.5, yfudge = 0.5): #pylint: disable=C0301, R0913
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.
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])
217
def _create_image_json(self):
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.
226
json_doc["image"] = {}
227
if (self.auto_number):
228
tag = "%s%06d" % (self._tag, self.spill_count)
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)
239
def __convert_to_binary(self, histogram): #pylint: disable=R0201
241
Convert histogram to binary format.
242
@param self Object reference.
243
@param histogram matplotlib FigureCanvas representing the
245
@returns representation of histogram in base 64-encoded image
248
data_file = StringIO.StringIO()
249
histogram.print_figure(data_file, dpi=500, format=self.image_type)
251
data = data_file.read()
252
return base64.b64encode(data)