gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
lal_spectrumplot.py
1 # Copyright (C) 2009--2011 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 bisect
28 import math
29 import matplotlib
30 matplotlib.rcParams.update({
31  "font.size": 8.0,
32  "axes.titlesize": 10.0,
33  "axes.labelsize": 10.0,
34  "xtick.labelsize": 8.0,
35  "ytick.labelsize": 8.0,
36  "legend.fontsize": 8.0,
37  "figure.dpi": 100,
38  "savefig.dpi": 100,
39  "text.usetex": True,
40  "path.simplify": True
41 })
42 from matplotlib import figure
43 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
44 import numpy
45 
46 
47 import pygtk
48 pygtk.require("2.0")
49 import gobject
50 import pygst
51 pygst.require('0.10')
52 import gst
53 
54 
55 from gstlal import pipeio
56 from gstlal import reference_psd
57 from gstlal.elements import matplotlibcaps
58 
59 
60 __author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
61 __version__ = "FIXME"
62 __date__ = "FIXME"
63 
64 
65 #
66 # =============================================================================
67 #
68 # Element
69 #
70 # =============================================================================
71 #
72 
73 
74 class lal_spectrumplot(gst.BaseTransform):
75  __gstdetails__ = (
76  "Power spectrum plot",
77  "Plots",
78  "Generates a video showing a power spectrum (e.g., as measured by lal_whiten)",
79  __author__
80  )
81 
82  __gproperties__ = {
83  "f-min": (
84  gobject.TYPE_DOUBLE,
85  "f_{min}",
86  "Lower bound of plot in Hz.",
87  0, gobject.G_MAXDOUBLE, 10.0,
88  gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT
89  ),
90  "f-max": (
91  gobject.TYPE_DOUBLE,
92  "f_{max}",
93  "Upper bound of plot in Hz.",
94  0, gobject.G_MAXDOUBLE, 4000.0,
95  gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT
96  )
97  }
98 
99  __gsttemplates__ = (
100  gst.PadTemplate("sink",
101  gst.PAD_SINK,
102  gst.PAD_ALWAYS,
103  gst.caps_from_string(
104  "audio/x-raw-float, " +
105  "delta-f = (double) [0, MAX], " +
106  "channels = (int) [1, MAX], " +
107  "endianness = (int) BYTE_ORDER, " +
108  "rate = (fraction) [0/1, 2147483647/1], " +
109  "width = (int) 64"
110  )
111  ),
112  gst.PadTemplate("src",
113  gst.PAD_SRC,
114  gst.PAD_ALWAYS,
115  gst.caps_from_string(
116  matplotlibcaps + ", " +
117  "width = (int) [1, MAX], " +
118  "height = (int) [1, MAX], " +
119  "framerate = (fraction) [0/1, 2147483647/1]"
120  )
121  )
122  )
123 
124 
125  def __init__(self):
126  gst.BaseTransform.__init__(self)
127  self.channels = None
128  self.delta_f = None
129  self.out_width = 320 # default
130  self.out_height = 200 # default
131  self.instrument = None
132  self.channel_name = None
133  self.sample_units = None
134 
135 
136  def do_set_property(self, prop, val):
137  if prop.name == "f-min":
138  self.f_min = val
139  elif prop.name == "f-max":
140  self.f_max = val
141  else:
142  raise AssertionError("no property %s" % prop.name)
143 
144 
145  def do_get_property(self, prop):
146  if prop.name == "f-min":
147  return self.f_min
148  elif prop.name == "f-max":
149  return self.f_max
150  else:
151  raise AssertionError("no property %s" % prop.name)
152 
153 
154  def do_set_caps(self, incaps, outcaps):
155  self.channels = incaps[0]["channels"]
156  self.delta_f = incaps[0]["delta-f"]
157  self.out_width = outcaps[0]["width"]
158  self.out_height = outcaps[0]["height"]
159  return True
160 
161 
162  def do_get_unit_size(self, caps):
163  return pipeio.get_unit_size(caps)
164 
165 
166  def do_event(self, event):
167  if event.type == gst.EVENT_TAG:
168  tags = pipeio.parse_framesrc_tags(event.parse_tag())
169  self.instrument = tags["instrument"]
170  self.channel_name = tags["channel-name"]
171  self.sample_units = tags["sample-units"]
172  return True
173 
174 
175  def do_transform(self, inbuf, outbuf):
176  #
177  # generate spectrum plot
178  #
179 
180  fig = figure.Figure()
181  FigureCanvas(fig)
182  fig.set_size_inches(self.out_width / float(fig.get_dpi()), self.out_height / float(fig.get_dpi()))
183  axes = fig.gca(rasterized = True)
184 
185  data = numpy.transpose(pipeio.array_from_audio_buffer(inbuf))
186  f = numpy.arange(len(data[0]), dtype = "double") * self.delta_f
187 
188  imin = bisect.bisect_left(f, self.f_min)
189  imax = bisect.bisect_right(f, self.f_max)
190 
191  for psd in data[:]:
192  axes.loglog(f[imin:imax], psd[imin:imax], alpha = 0.7, label = "%s:%s (%.4g Mpc BNS horizon)" % ((self.instrument or "Unknown Instrument"), (self.channel_name or "Unknown Channel").replace("_", r"\_"), reference_psd.horizon_distance(reference_psd.laltypes.REAL8FrequencySeries(f0 = f[0], deltaF = self.delta_f, data = psd), 1.4, 1.4, 8.0, 10.0)))
193 
194  axes.grid(True)
195  axes.set_xlim((self.f_min, self.f_max))
196  axes.set_title(r"Spectral Density at %.11g s" % (float(inbuf.timestamp) / gst.SECOND))
197  axes.set_xlabel(r"Frequency (Hz)")
198  axes.set_ylabel(r"Spectral Density (%s)" % self.sample_units)
199  axes.legend(loc = "lower left")
200 
201  #
202  # extract pixel data
203  #
204 
205  fig.canvas.draw()
206  rgba_buffer = fig.canvas.buffer_rgba(0, 0)
207  rgba_buffer_size = len(rgba_buffer)
208 
209  #
210  # copy pixel data to output buffer
211  #
212 
213  outbuf[0:rgba_buffer_size] = rgba_buffer
214  outbuf.datasize = rgba_buffer_size
215 
216  #
217  # set metadata on output buffer
218  #
219 
220  outbuf.offset_end = outbuf.offset = gst.BUFFER_OFFSET_NONE
221  outbuf.timestamp = inbuf.timestamp
222  outbuf.duration = gst.CLOCK_TIME_NONE
223 
224  #
225  # done
226  #
227 
228  return gst.FLOW_OK
229 
230 
231  def do_transform_caps(self, direction, caps):
232  if direction == gst.PAD_SRC:
233  #
234  # convert src pad's caps to sink pad's
235  #
236 
237  rate, = [struct["framerate"] for struct in caps]
238  result = gst.Caps()
239  for struct in self.get_pad("sink").get_pad_template_caps():
240  struct = struct.copy()
241  struct["rate"] = rate
242  result.append_structure(struct)
243  return result
244 
245  elif direction == gst.PAD_SINK:
246  #
247  # convert sink pad's caps to src pad's
248  #
249 
250  rate, = [struct["rate"] for struct in caps]
251  result = gst.Caps()
252  for struct in self.get_pad("src").get_pad_template_caps():
253  struct = struct.copy()
254  struct["framerate"] = rate
255  result.append_structure(struct)
256  return result
257 
258  raise ValueError(direction)
259 
260 
261  def do_transform_size(self, direction, caps, size, othercaps):
262  if direction == gst.PAD_SRC:
263  #
264  # compute frame count on src pad
265  #
266 
267  frames = size * 8 // (caps[0]["bpp"] * caps[0]["width"] * caps[0]["height"])
268 
269  #
270  # if greater than 1, ask for 1 byte. lal_whiten can
271  # only provide whole PSD buffer, so any non-zero
272  # amount should produce a full PSD. and lal_whiten
273  # only operates in push mode so this is a non-issue
274  #
275 
276  if frames < 1:
277  return 0
278  return 1
279 
280  elif direction == gst.PAD_SINK:
281  #
282  # any buffer on sink pad is turned into exactly
283  # one frame on source pad
284  #
285 
286  # FIXME: figure out whats wrong with this
287  # function, why is othercaps not right!?
288  othercaps = self.get_pad("src").get_allowed_caps()
289  return othercaps[0]["width"] * othercaps[0]["height"] * othercaps[0]["bpp"] // 8
290 
291  raise ValueError(direction)
292 
293 
294 #
295 # register element class
296 #
297 
298 
299 gobject.type_register(lal_spectrumplot)
300 
301 __gstelementfactory__ = (
302  lal_spectrumplot.__name__,
303  gst.RANK_NONE,
304  lal_spectrumplot
305 )