gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
multirate_datasource.py
Go to the documentation of this file.
1 # Copyright (C) 2009--2013 Kipp Cannon, Chad Hanna, Drew Keppel
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 sys
28 import optparse
29 import math
30 
31 # The following snippet is taken from http://gstreamer.freedesktop.org/wiki/FAQ#Mypygstprogramismysteriouslycoredumping.2Chowtofixthis.3F
32 import pygtk
33 pygtk.require("2.0")
34 import gobject
35 gobject.threads_init()
36 import pygst
37 pygst.require('0.10')
38 import gst
39 
40 from gstlal import bottle
41 from gstlal import pipeparts
42 from gstlal import reference_psd
43 from gstlal import datasource
44 
45 ##
46 # @file
47 #
48 # A file that contains the multirate_datasource module code
49 #
50 # ###Review Status
51 #
52 # | Names | Hash | Date |
53 # | ------------------------------------------- | ------------------------------------------- | ---------- |
54 # | Florent, Sathya, Duncan Me, Jolien, Kipp, Chad | 8a6ea41398be79c00bdc27456ddeb1b590b0f68e | 2014-06-18 |
55 #
56 # #### Actions
57 #
58 # - Is the h(t) gate really necessary? It shouldn't really be used unless
59 # there is something really wrong with the data. Wishlist: Tee off from
60 # the control panel and record on/off (This is already done).
61 #
62 # - Task for the review team: Check what data was analysed and how much
63 # data was "lost" due to application of internal data quality.
64 #
65 # - There seems to be a bug in resampler (even) in the latest version of gstreamer;
66 # (produces one few sample). We need to better understand the consequence of this bug.
67 
68 ##
69 # @package python.multirate_datasource
70 #
71 # multirate_datasource module
72 
73 ## #### produced whitened h(t) at (possibly) multiple sample rates
74 # ##### Gstreamer graph describing this function
75 #
76 # @dot
77 # digraph mkbasicsrc {
78 # rankdir = LR;
79 # compound=true;
80 # node [shape=record fontsize=10 fontname="Verdana"];
81 # edge [fontsize=8 fontname="Verdana"];
82 #
83 # capsfilter1 [URL="\ref pipeparts.mkcapsfilter()"];
84 # audioresample [URL="\ref pipeparts.mkresample()"];
85 # capsfilter2 [URL="\ref pipeparts.mkcapsfilter()"];
86 # nofakedisconts [URL="\ref pipeparts.mknofakedisconts()"];
87 # reblock [URL="\ref pipeparts.mkreblock()"];
88 # whiten [URL="\ref pipeparts.mkwhiten()"];
89 # audioconvert [URL="\ref pipeparts.mkaudioconvert()"];
90 # capsfilter3 [URL="\ref pipeparts.mkcapsfilter()"];
91 # "segmentsrcgate()" [URL="\ref datasource.mksegmentsrcgate()", label="segmentsrcgate() \n [iff veto segment list provided]", style=filled, color=lightgrey];
92 # tee [URL="\ref pipeparts.mktee()"];
93 # audioamplifyr1 [URL="\ref pipeparts.mkaudioamplify()"];
94 # capsfilterr1 [URL="\ref pipeparts.mkcapsfilter()"];
95 # nofakediscontsr1 [URL="\ref pipeparts.mknofakedisconts()"];
96 # htgater1 [URL="\ref datasource.mkhtgate()", label="htgate() \n [iff ht gate specified]", style=filled, color=lightgrey];
97 # tee1 [URL="\ref pipeparts.mktee()"];
98 # audioamplifyr2 [URL="\ref pipeparts.mkaudioamplify()"];
99 # capsfilterr2 [URL="\ref pipeparts.mkcapsfilter()"];
100 # nofakediscontsr2 [URL="\ref pipeparts.mknofakedisconts()"];
101 # htgater2 [URL="\ref datasource.mkhtgate()", label="htgate() \n [iff ht gate specified]", style=filled, color=lightgrey];
102 # tee2 [URL="\ref pipeparts.mktee()"];
103 # audioamplify_rn [URL="\ref pipeparts.mkaudioamplify()"];
104 # capsfilter_rn [URL="\ref pipeparts.mkcapsfilter()"];
105 # nofakedisconts_rn [URL="\ref pipeparts.mknofakedisconts()"];
106 # htgate_rn [URL="\ref datasource.mkhtgate()", style=filled, color=lightgrey, label="htgate() \n [iff ht gate specified]"];
107 # tee [URL="\ref pipeparts.mktee()"];
108 #
109 # // nodes
110 #
111 # "?" -> capsfilter1 -> audioresample;
112 # audioresample -> capsfilter2;
113 # capsfilter2 -> nofakedisconts;
114 # nofakedisconts -> reblock;
115 # reblock -> whiten;
116 # whiten -> audioconvert;
117 # audioconvert -> capsfilter3;
118 # capsfilter3 -> "segmentsrcgate()";
119 # "segmentsrcgate()" -> tee;
120 #
121 # tee -> audioamplifyr1 [label="Rate 1"];
122 # audioamplifyr1 -> capsfilterr1;
123 # capsfilterr1 -> nofakediscontsr1;
124 # nofakediscontsr1 -> htgater1;
125 # htgater1 -> tee1 -> "? 1";
126 #
127 # tee -> audioamplifyr2 [label="Rate 2"];
128 # audioamplifyr2 -> capsfilterr2;
129 # capsfilterr2 -> nofakediscontsr2;
130 # nofakediscontsr2 -> htgater2;
131 # htgater2 -> tee2 -> "? 2";
132 #
133 # tee -> audioamplify_rn [label="Rate N"];
134 # audioamplify_rn -> capsfilter_rn;
135 # capsfilter_rn -> nofakedisconts_rn;
136 # nofakedisconts_rn -> htgate_rn;
137 # htgate_rn -> tee_n -> "? 3";
138 #
139 # }
140 # @enddot
141 def mkwhitened_multirate_src(pipeline, src, rates, instrument, psd = None, psd_fft_length = 8, ht_gate_threshold = float("inf"), veto_segments = None, seekevent = None, nxydump_segment = None, track_psd = False, block_duration = 1 * gst.SECOND, zero_pad = 0, width = 64, unit_normalize = True):
142  """!
143  Build pipeline stage to whiten and downsample h(t).
144 
145  - pipeline: the gstreamer pipeline to add this to
146  - src: the gstreamer element that will be providing data to this
147  - rates: a list of the requested sample rates, e.g., [512,1024].
148  - instrument: the instrument to process
149  - psd: a psd frequency series
150  - psd_fft_length: length of fft used for whitening
151  - ht_gate_threshold: gate h(t) if it crosses this value
152  - veto_segments: segments to mark as gaps after whitening
153  - track_psd: decide whether to dynamically track the spectrum or use the fixed spectrum provided
154  - width: type convert to either 32 or 64 bit float
155  """
156 
157  #
158  # down-sample to highest of target sample rates. we include a caps
159  # filter upstream of the resampler to ensure that this is, infact,
160  # *down*-sampling. if the source time series has a lower sample
161  # rate than the highest target sample rate the resampler will
162  # become an upsampler, and the result will likely interact poorly
163  # with the whitener as it tries to ampify the non-existant
164  # high-frequency components, possibly adding significant numerical
165  # noise to its output. if you see errors about being unable to
166  # negotiate a format from this stage in the pipeline, it is because
167  # you are asking for output sample rates that are higher than the
168  # sample rate of your data source.
169  #
170 
171  quality = 9
172  head = pipeparts.mkcapsfilter(pipeline, src, "audio/x-raw-float, rate=[%d,MAX]" % max(rates))
173  head = pipeparts.mkcapsfilter(pipeline, pipeparts.mkresample(pipeline, head, quality = quality), "audio/x-raw-float, rate=%d" % max(rates))
174  head = pipeparts.mknofakedisconts(pipeline, head) # FIXME: remove when resampler is patched
175  head = pipeparts.mkchecktimestamps(pipeline, head, "%s_timestamps_%d_hoft" % (instrument, max(rates)))
176 
177  #
178  # add a reblock element. the whitener's gap support isn't 100% yet
179  # and giving it smaller input buffers works around the remaining
180  # weaknesses (namely that when it sees a gap buffer large enough to
181  # drain its internal history, it doesn't know enough to produce a
182  # short non-gap buffer to drain its history followed by a gap
183  # buffer, it just produces one huge non-gap buffer that's mostly
184  # zeros).
185  #
186 
187  head = pipeparts.mkreblock(pipeline, head, block_duration = block_duration)
188 
189  #
190  # construct whitener.
191  #
192 
193  head = whiten = pipeparts.mkwhiten(pipeline, head, fft_length = psd_fft_length, zero_pad = zero_pad, average_samples = 64, median_samples = 7, expand_gaps = True, name = "lal_whiten_%s" % instrument)
194  head = pipeparts.mkaudioconvert(pipeline, head)
195  head = pipeparts.mkcapsfilter(pipeline, head, "audio/x-raw-float, width=%d, rate=%d, channels=1" % (width, max(rates)))
196 
197  if psd is None:
198  # use running average PSD
199  whiten.set_property("psd-mode", 0)
200  else:
201  if track_psd:
202  # use running average PSD
203  whiten.set_property("psd-mode", 0)
204  else:
205  # use fixed PSD
206  whiten.set_property("psd-mode", 1)
207 
208  #
209  # install signal handler to retrieve \Delta f and
210  # f_{Nyquist} whenever they are known and/or change,
211  # resample the user-supplied PSD, and install it into the
212  # whitener.
213  #
214 
215  def psd_resolution_changed(elem, pspec, psd):
216  # get frequency resolution and number of bins
217  delta_f = elem.get_property("delta-f")
218  n = int(round(elem.get_property("f-nyquist") / delta_f) + 1)
219  # interpolate and install PSD
220  psd = reference_psd.interpolate_psd(psd, delta_f)
221  elem.set_property("mean-psd", psd.data[:n])
222 
223  whiten.connect_after("notify::f-nyquist", psd_resolution_changed, psd)
224  whiten.connect_after("notify::delta-f", psd_resolution_changed, psd)
225  head = pipeparts.mkchecktimestamps(pipeline, head, "%s_timestamps_%d_whitehoft" % (instrument, max(rates)))
226 
227  #
228  # optionally add vetoes
229  #
230 
231  if veto_segments is not None:
232  head = datasource.mksegmentsrcgate(pipeline, head, veto_segments, seekevent=seekevent, invert_output=True)
233 
234  #
235  # optional gate on whitened h(t) amplitude. attack and hold are
236  # made to be 1/4 second or 1 sample, whichever is larger
237  #
238 
239  # FIXME: this could be omitted if ht_gate_threshold is None, but
240  # we need to collect whitened h(t) segments, however something
241  # could be done to collect those if these gates aren't here.
242  ht_gate_window = max(max(rates) // 4, 1) # samples
243  head = datasource.mkhtgate(pipeline, head, threshold = ht_gate_threshold if ht_gate_threshold is not None else float("+inf"), hold_length = ht_gate_window, attack_length = ht_gate_window, name = "%s_ht_gate" % instrument)
244  # emit signals so that a user can latch on to them
245  head.set_property("emit-signals", True)
246 
247  #
248  # tee for highest sample rate stream
249  #
250 
251  head = {max(rates): pipeparts.mktee(pipeline, head)}
252 
253  #
254  # down-sample whitened time series to remaining target sample rates
255  # while applying an amplitude correction to adjust for low-pass
256  # filter roll-off. we also scale by \sqrt{original rate / new
257  # rate}. this is done to preserve the square magnitude of the time
258  # series --- the inner product of the time series with itself.
259  # really what we want is for
260  #
261  # \int v_{1}(t) v_{2}(t) \diff t
262  # \approx \sum v_{1}(t) v_{2}(t) \Delta t
263  #
264  # to be preserved across different sample rates, i.e. for different
265  # \Delta t. what we do is rescale the time series and ignore
266  # \Delta t, so we put 1/2 factor of the ratio of the \Delta t's
267  # into the h(t) time series here, and, later, another 1/2 factor
268  # into the template when it gets downsampled.
269  #
270  # by design, the output of the whitener is a unit-variance time
271  # series. however, downsampling it reduces the variance due to the
272  # removal of some frequency components. we require the input to
273  # the orthogonal filter banks to be unit variance, therefore a
274  # correction factor is applied via an audio amplify element to
275  # adjust for the reduction in variance due to the downsampler.
276  #
277 
278  for rate in sorted(set(rates))[:-1]:
279  # downsample. make sure each output stream is unit
280  # normalized, otherwise the audio resampler removes power
281  # according to the rate difference and filter rolloff
282  if unit_normalize:
283  head[rate] = pipeparts.mkaudioamplify(pipeline, head[max(rates)], 1. / math.sqrt(pipeparts.audioresample_variance_gain(quality, max(rates), rate)))
284  else:
285  head[rate] = head[max(rates)]
286  head[rate] = pipeparts.mkcapsfilter(pipeline, pipeparts.mkresample(pipeline, head[rate], quality = quality), caps = "audio/x-raw-float, rate=%d" % rate)
287  head[rate] = pipeparts.mknofakedisconts(pipeline, head[rate]) # FIXME: remove when resampler is patched
288  head[rate] = pipeparts.mkchecktimestamps(pipeline, head[rate], "%s_timestamps_%d_whitehoft" % (instrument, rate))
289 
290  head[rate] = pipeparts.mktee(pipeline, head[rate])
291 
292  #
293  # done. return value is a dictionary of tee elements indexed by
294  # sample rate
295  #
296 
297  return head
298