gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
lal_histogramplot.py
1 # Copyright (C) 2009 Kipp Cannon
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation; either version 2 of the License, or (at your
6 # option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 # Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 
17 
18 #
19 # =============================================================================
20 #
21 # Preamble
22 #
23 # =============================================================================
24 #
25 
26 
27 import matplotlib
28 matplotlib.rcParams.update({
29  "font.size": 8.0,
30  "axes.titlesize": 10.0,
31  "axes.labelsize": 10.0,
32  "xtick.labelsize": 8.0,
33  "ytick.labelsize": 8.0,
34  "legend.fontsize": 8.0,
35  "figure.dpi": 100,
36  "savefig.dpi": 100,
37  "text.usetex": True,
38  "path.simplify": True
39 })
40 from matplotlib import figure
41 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
42 import numpy
43 
44 
45 import pygtk
46 pygtk.require("2.0")
47 import gobject
48 import pygst
49 pygst.require('0.10')
50 import gst
51 
52 
53 from gstlal import pipeio
54 from gstlal.elements import matplotlibcaps
55 
56 
57 __author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
58 __version__ = "FIXME"
59 __date__ = "FIXME"
60 
61 
62 #
63 # =============================================================================
64 #
65 # Element
66 #
67 # =============================================================================
68 #
69 
70 
71 class lal_histogramplot(gst.BaseTransform):
72  __gstdetails__ = (
73  "Histogram plot",
74  "Plots",
75  "Generates a video showing a histogram of the input time series",
76  __author__
77  )
78 
79  __gsttemplates__ = (
80  gst.PadTemplate("sink",
81  gst.PAD_SINK,
82  gst.PAD_ALWAYS,
83  gst.caps_from_string(
84  "audio/x-raw-float, " +
85  "rate = (int) [1, MAX], " +
86  "channels = (int) [1, MAX], " +
87  "endianness = (int) BYTE_ORDER, " +
88  "width = (int) {32, 64};" +
89  "audio/x-raw-int, " +
90  "rate = (int) [1, MAX], " +
91  "channels = (int) [1, MAX], " +
92  "endianness = (int) BYTE_ORDER, " +
93  "width = (int) 32," +
94  "depth = (int) 32," +
95  "signed = (bool) {true, false}; " +
96  "audio/x-raw-int, " +
97  "rate = (int) [1, MAX], " +
98  "channels = (int) [1, MAX], " +
99  "endianness = (int) BYTE_ORDER, " +
100  "width = (int) 64," +
101  "depth = (int) 64," +
102  "signed = (bool) {true, false}"
103  )
104  ),
105  gst.PadTemplate("src",
106  gst.PAD_SRC,
107  gst.PAD_ALWAYS,
108  gst.caps_from_string(
109  matplotlibcaps + ", " +
110  "width = (int) [1, MAX], " +
111  "height = (int) [1, MAX], " +
112  "framerate = (fraction) [0, MAX]"
113  )
114  )
115  )
116 
117 
118  def __init__(self):
119  gst.BaseTransform.__init__(self)
120  self.channels = None
121  self.in_rate = None
122  self.out_rate = None
123  self.out_width = 320 # default
124  self.out_height = 200 # default
125  self.instrument = None
126  self.channel_name = None
127  self.sample_units = None
128 
129 
130  def do_set_caps(self, incaps, outcaps):
131  channels = incaps[0]["channels"]
132  if channels != self.channels:
133  self.buf = numpy.zeros((0, channels), dtype = pipeio.numpy_dtype_from_caps(incaps))
134  self.channels = channels
135  self.in_rate = incaps[0]["rate"]
136  self.out_rate = outcaps[0]["framerate"]
137  self.out_width = outcaps[0]["width"]
138  self.out_height = outcaps[0]["height"]
139  return True
140 
141 
142  def do_start(self):
143  self.t0 = None
144  self.offset0 = None
145  self.next_out_offset = None
146  return True
147 
148 
149  def do_get_unit_size(self, caps):
150  return pipeio.get_unit_size(caps)
151 
152 
153  def do_event(self, event):
154  if event.type == gst.EVENT_TAG:
155  tags = pipeio.parse_framesrc_tags(event.parse_tag())
156  self.instrument = tags["instrument"]
157  self.channel_name = tags["channel-name"]
158  self.sample_units = tags["sample-units"]
159  return True
160 
161 
162  def make_frame(self, samples, outbuf):
163  #
164  # set metadata and advance output offset counter
165  #
166 
167  outbuf.offset = self.next_out_offset
168  self.next_out_offset += 1
169  outbuf.offset_end = self.next_out_offset
170  outbuf.timestamp = self.t0 + int(round(float(int(outbuf.offset - self.offset0) / self.out_rate) * gst.SECOND))
171  outbuf.duration = self.t0 + int(round(float(int(outbuf.offset_end - self.offset0) / self.out_rate) * gst.SECOND)) - outbuf.timestamp
172 
173  #
174  # generate histogram
175  #
176 
177  fig = figure.Figure()
178  FigureCanvas(fig)
179  fig.set_size_inches(self.out_width / float(fig.get_dpi()), self.out_height / float(fig.get_dpi()))
180  axes = fig.gca(yscale = "log", rasterized = True)
181  for channel in numpy.transpose(samples)[:]:
182  axes.hist(channel, bins = 101, histtype = "step")
183  axes.grid(True)
184  axes.set_xlabel(r"Amplitude (%s)" % ((self.sample_units is not None) and (str(self.sample_units) or "dimensionless") or "unkown units"))
185  axes.set_ylabel(r"Count")
186  axes.set_title(r"%s, %s (%.9g s -- %.9g s)" % (self.instrument or "Unknown Instrument", self.channel_name or "Unknown Channel", float(outbuf.timestamp) / gst.SECOND, float(outbuf.timestamp + outbuf.duration) / gst.SECOND))
187 
188  #
189  # extract pixel data
190  #
191 
192  fig.canvas.draw()
193  rgba_buffer = fig.canvas.buffer_rgba(0, 0)
194  rgba_buffer_size = len(rgba_buffer)
195 
196  #
197  # copy pixel data to output buffer
198  #
199 
200  outbuf[0:rgba_buffer_size] = rgba_buffer
201  outbuf.datasize = rgba_buffer_size
202 
203  #
204  # done
205  #
206 
207  return outbuf
208 
209 
210  def do_transform(self, inbuf, outbuf):
211  #
212  # make sure we have valid metadata
213  #
214 
215  if self.t0 is None:
216  self.t0 = inbuf.timestamp
217  self.offset0 = 0
218  self.next_out_offset = 0
219 
220  #
221  # append input to time series buffer
222  #
223 
224  self.buf = numpy.concatenate((self.buf, pipeio.array_from_audio_buffer(inbuf)))
225 
226  #
227  # number of samples required for output frame
228  #
229 
230  samples_per_frame = int(round(self.in_rate / float(self.out_rate)))
231 
232  #
233  # build output frame(s)
234  #
235 
236  if len(self.buf) < samples_per_frame:
237  # not enough data for output
238  # FIXME: should return
239  # GST_BASE_TRANSFORM_FLOW_DROPPED, don't know what
240  # that constant is, but I know it's #define'ed to
241  # GST_FLOW_CUSTOM_SUCCESS. figure out what the
242  # constant should be
243  return gst.FLOW_CUSTOM_SUCCESS
244 
245  while len(self.buf) >= 2 * samples_per_frame:
246  flow_return, newoutbuf = self.get_pad("src").alloc_buffer(self.next_out_offset, self.out_width * self.out_height * 4, outbuf.caps)
247  self.get_pad("src").push(self.make_frame(self.buf[:samples_per_frame], newoutbuf))
248  self.buf = self.buf[samples_per_frame:]
249  self.make_frame(self.buf[:samples_per_frame], outbuf)
250  self.buf = self.buf[samples_per_frame:]
251 
252  #
253  # done
254  #
255 
256  return gst.FLOW_OK
257 
258 
259  def do_transform_caps(self, direction, caps):
260  if direction == gst.PAD_SRC:
261  #
262  # convert src pad's caps to sink pad's
263  #
264 
265  return self.get_pad("sink").get_fixed_caps_func()
266 
267  elif direction == gst.PAD_SINK:
268  #
269  # convert sink pad's caps to src pad's
270  #
271 
272  return self.get_pad("src").get_fixed_caps_func()
273 
274  raise ValueError(direction)
275 
276 
277  def do_transform_size(self, direction, caps, size, othercaps):
278  samples_per_frame = int(round(float(self.in_rate / self.out_rate)))
279 
280  if direction == gst.PAD_SRC:
281  #
282  # convert byte count on src pad to sample count on
283  # sink pad (minus samples we already have)
284  #
285 
286  bytes_per_frame = caps[0]["width"] * caps[0]["height"] * caps[0]["bpp"] / 8
287  samples = int(size / bytes_per_frame) * samples_per_frame - len(self.buf)
288 
289  #
290  # convert to byte count on sink pad.
291  #
292 
293  if samples <= 0:
294  return 0
295  return samples * (othercaps[0]["width"] // 8) * othercaps[0]["channels"]
296 
297  elif direction == gst.PAD_SINK:
298  #
299  # convert byte count on sink pad plus samples we
300  # already have to frame count on src pad.
301  #
302 
303  frames = (int(size * 8 / caps[0]["width"]) // caps[0]["channels"] + len(self.buf)) / samples_per_frame
304 
305  #
306  # if there's enough for at least one frame, claim
307  # output size will be 1 frame. additional buffers
308  # will be created as needed
309  #
310 
311  if frames < 1:
312  return 0
313  # FIXME: why is othercaps not the *other* caps?
314  return self.out_width * self.out_height * 4
315  return othercaps[0]["width"] * othercaps[0]["height"] * othercaps[0]["bpp"] / 8
316 
317  raise ValueError(direction)
318 
319 
320 #
321 # register element class
322 #
323 
324 
325 gobject.type_register(lal_histogramplot)
326 
327 __gstelementfactory__ = (
328  lal_histogramplot.__name__,
329  gst.RANK_NONE,
330  lal_histogramplot
331 )