gstlal 1.13.0
Loading...
Searching...
No Matches
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
27import bisect
28import math
29import matplotlib
30matplotlib.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})
42from matplotlib import figure
43from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
44import 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
54import gi
55gi.require_version('Gst', '1.0')
56gi.require_version('GstAudio', '1.0')
57gi.require_version('GstBase', '1.0')
58from gi.repository import GObject
59from gi.repository import Gst
60from gi.repository import GstAudio
61from gi.repository import GstBase
62
63
64GObject.threads_init()
65Gst.init(None)
66
67import lal
68
69from gstlal import pipeio
70from gstlal.psd import HorizonDistance
71from gstlal.elements import matplotlibcaps
72
73
74__author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
75__version__ = "FIXME"
76__date__ = "FIXME"
77
78
79#
80# =============================================================================
81#
82# Element
83#
84# =============================================================================
85#
86
87
88class lal_spectrumplot(GstBase.BaseTransform):
89 __gstmetadata__ = (
90 "Power spectrum plot",
91 "Plots",
92 "Generates a video showing a power spectrum (e.g., as measured by lal_whiten)",
93 __author__
94 )
95
96 __gproperties__ = {
97 "f-min": (
98 GObject.TYPE_DOUBLE,
99 "f_{min}",
100 "Lower bound of plot in Hz.",
101 0, GObject.G_MAXDOUBLE, 10.0,
102 GObject.PARAM_READWRITE | GObject.PARAM_CONSTRUCT
103 ),
104 "f-max": (
105 GObject.TYPE_DOUBLE,
106 "f_{max}",
107 "Upper bound of plot in Hz.",
108 0, GObject.G_MAXDOUBLE, 4000.0,
109 GObject.PARAM_READWRITE | GObject.PARAM_CONSTRUCT
110 )
111 }
112
113 __gsttemplates__ = (
114 Gst.PadTemplate.new("sink",
115 Gst.PadDirection.SINK,
116 Gst.PadPresence.ALWAYS,
117 Gst.caps_from_string(
118 "audio/x-raw, " +
119 "rate = " + GstAudio.AUDIO_RATE_RANGE + ", " +
120 "channels = " + GstAudio.AUDIO_CHANNELS_RANGE + ", " +
121 "format = (string) { F64NE }, " +
122 "delta-f = (double) [0, MAX], " +
123 "endianness = (int) BYTE_ORDER, " +
124 "width = (int) 64"
125 )
126 ),
127 Gst.PadTemplate.new("src",
128 Gst.PadDirection.SRC,
129 Gst.PadPresence.ALWAYS,
130 Gst.caps_from_string(
131 matplotlibcaps + ", " +
132 "width = (int) [1, MAX], " +
133 "height = (int) [1, MAX], " +
134 "framerate = (fraction) [0/1, 2147483647/1]"
135 )
136 )
137 )
138
139
140 def __init__(self):
141 super(lal_spectrumplot, self).__init__()
142 self.channels = None
143 self.delta_f = None
144 self.out_width = 320 # default
145 self.out_height = 200 # default
146 self.instrument = None
147 self.channel_name = None
148 self.sample_units = None
149
150
151 def do_set_property(self, prop, val):
152 if prop.name == "f-min":
153 self.f_min = val
154 elif prop.name == "f-max":
155 self.f_max = val
156 else:
157 raise AssertionError("no property %s" % prop.name)
158
159
160 def do_get_property(self, prop):
161 if prop.name == "f-min":
162 return self.f_min
163 elif prop.name == "f-max":
164 return self.f_max
165 else:
166 raise AssertionError("no property %s" % prop.name)
167
168
169 def do_set_caps(self, incaps, outcaps):
170 # self.channels = incaps[0]["channels"]
171 # self.delta_f = incaps[0]["delta-f"]
172 # self.out_width = outcaps[0]["width"]
173 # self.out_height = outcaps[0]["height"]
174 # return True
175 info = GstAudio.AudioInfo()
176 if info.from_caps(incaps):
177 self.unit_size = info.bpf
178 self.units_per_second = info.rate
179 return True
180 s = incaps.get_structure(0)
181 if not s or s.get_name() != "audio/x-raw":
182 return False
183 success, chnls = s.get_int("channels")
184 if success:
185 success, rate = s.get_int("rate")
186 if not success:
187 return False
188 fmt = s.get_string("format")
189 if fmt == "Z64LE":
190 self.unit_size = 8 * chnls
191 self.units_per_second = rate
192 return True
193 elif fmt == "Z128LE":
194 self.unit_size = 16 * chnls
195 self.units_per_second = rate
196 return True
197 return False
198
199
200 def do_get_unit_size(self, caps):
201 return pipeio.get_unit_size(caps)
202
203
204 def do_event(self, event):
205 if event.type == Gst.EVENT_TAG:
206 tags = pipeio.parse_framesrc_tags(event.parse_tag())
207 self.instrument = tags["instrument"]
208 self.channel_name = tags["channel-name"]
209 self.sample_units = tags["sample-units"]
210 return True
211
212
213 def do_transform(self, inbuf, outbuf):
214 #
215 # generate spectrum plot
216 #
217
218 fig = figure.Figure()
219 FigureCanvas(fig)
220 fig.set_size_inches(self.out_width / float(fig.get_dpi()), self.out_height / float(fig.get_dpi()))
221 axes = fig.gca(rasterized = True)
222
223 data = numpy.transpose(pipeio.array_from_audio_buffer(inbuf))
224 f = numpy.arange(len(data[0]), dtype = "double") * self.delta_f
225
226 imin = bisect.bisect_left(f, self.f_min)
227 imax = bisect.bisect_right(f, self.f_max)
228
229 for psd in data[:]:
230 fseries = lal.CreateREAL8FrequencySeries(
231 name = "psd",
232 epoch = 0,
233 f0 = f[0],
234 deltaF = self.delta_f,
235 sampleUnits = lal.Unit("s strain^2"),
236 length = len(f),
237 )
238 fseries.data.data[:] = psd
239 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"\_"), HorizonDistance(self.f_min, self.f_max, self.delta_f, 1.4, 1.4)(fseries)))
240
241 axes.grid(True)
242 axes.set_xlim((self.f_min, self.f_max))
243 axes.set_title(r"Spectral Density at %.11g s" % (float(inbuf.timestamp) / Gst.SECOND))
244 axes.set_xlabel(r"Frequency (Hz)")
245 axes.set_ylabel(r"Spectral Density (%s)" % self.sample_units)
246 axes.legend(loc = "lower left")
247
248 #
249 # extract pixel data
250 #
251
252 fig.canvas.draw()
253 rgba_buffer = fig.canvas.buffer_rgba(0, 0)
254 rgba_buffer_size = len(rgba_buffer)
255
256 #
257 # copy pixel data to output buffer
258 #
259
260 outbuf[0:rgba_buffer_size] = rgba_buffer
261 outbuf.datasize = rgba_buffer_size
262
263 #
264 # set metadata on output buffer
265 #
266
267 outbuf.offset_end = outbuf.offset = Gst.BUFFER_OFFSET_NONE
268 outbuf.timestamp = inbuf.timestamp
269 outbuf.duration = Gst.CLOCK_TIME_NONE
270
271 #
272 # done
273 #
274
275 return Gst.FlowReturn.OK
276
277
278 def do_transform_caps(self, direction, caps):
279 if direction == Gst.PAD_SRC:
280 #
281 # convert src pad's caps to sink pad's
282 #
283
284 rate, = [struct["framerate"] for struct in caps]
285 result = Gst.Caps()
286 for struct in self.get_pad("sink").get_pad_template_caps():
287 struct = struct.copy()
288 struct["rate"] = rate
289 result.append_structure(struct)
290 return result
291
292 elif direction == Gst.PAD_SINK:
293 #
294 # convert sink pad's caps to src pad's
295 #
296
297 rate, = [struct["rate"] for struct in caps]
298 result = Gst.Caps()
299 for struct in self.get_pad("src").get_pad_template_caps():
300 struct = struct.copy()
301 struct["framerate"] = rate
302 result.append_structure(struct)
303 return result
304
305 raise ValueError(direction)
306
307
308 def do_transform_size(self, direction, caps, size, othercaps):
309 if direction == Gst.PadDirection.SRC:
310 #
311 # compute frame count on src pad
312 #
313
314 frames = size * 8 // (caps[0]["bpp"] * caps[0]["width"] * caps[0]["height"])
315
316 #
317 # if greater than 1, ask for 1 byte. lal_whiten can
318 # only provide whole PSD buffer, so any non-zero
319 # amount should produce a full PSD. and lal_whiten
320 # only operates in push mode so this is a non-issue
321 #
322
323 if frames < 1:
324 return 0
325 return 1
326
327 elif direction == Gst.PadDirection.SINK:
328 #
329 # any buffer on sink pad is turned into exactly
330 # one frame on source pad
331 #
332
333 # FIXME: figure out whats wrong with this
334 # function, why is othercaps not right!?
335 othercaps = self.get_pad("src").get_allowed_caps()
336 return othercaps[0]["width"] * othercaps[0]["height"] * othercaps[0]["bpp"] // 8
337
338 raise ValueError(direction)
339
340
341#
342# register element class
343#
344
345
346GObject.type_register(lal_spectrumplot)
347
348__gstelementfactory__ = (
349 lal_spectrumplot.__name__,
350 Gst.Rank.NONE,
351 lal_spectrumplot
352)