gstlal 1.13.0
Loading...
Searching...
No Matches
pipeio.py
Go to the documentation of this file.
1# Copyright (C) 2009--2016 Kipp Cannon
2# Copyright (C) 2016 Chad Hanna
3# Copyright (C) 2016 Patrick Brockill
4# Copyright (C) 2016 Sarah Caudill
5# Copyright (C) 2015 Ryan Everett
6# Copyright (C) 2010 Leo Singer
7#
8# This program is free software; you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by the
10# Free Software Foundation; either version 2 of the License, or (at your
11# option) any later version.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16# Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
22
23
24
25
26#
27# =============================================================================
28#
29# Preamble
30#
31# =============================================================================
32#
33
34from collections.abc import Iterable
35
36
37import numpy
38
39
40import gi
41gi.require_version('Gst', '1.0')
42gi.require_version('GstAudio', '1.0')
43from gi.repository import GObject
44from gi.repository import Gst
45from gi.repository import GstAudio
46GObject.threads_init()
47Gst.init(None)
48
49
50import lal
51from ligo.segments import segment
52
53
54__author__ = "Kipp Cannon <kipp.cannon@ligo.org>, Chad Hanna <chad.hanna@ligo.org>, Drew Keppel <drew.keppel@ligo.org>"
55__version__ = "FIXME"
56__date__ = "FIXME"
57
58
59#
60# =============================================================================
61#
62# Properties
63#
64# =============================================================================
65#
66
67
68def repack_complex_array_to_real(arr):
69 """
70 Repack a complex-valued array into a real-valued array with twice
71 as many columns. Used to set complex arrays as values on elements
72 that expose them as real-valued array properties (gobject doesn't
73 understand complex numbers). The return value is a view into the
74 input array.
75 """
76 # FIXME: this function shouldn't exist, we should add complex
77 # types to gobject
78 if arr.dtype.kind != "c":
79 raise TypeError(arr)
80 assert arr.dtype.itemsize % 2 == 0
81 return arr.view(dtype = numpy.dtype("f%d" % (arr.dtype.itemsize // 2)))
82
83
84def repack_real_array_to_complex(arr):
85 """
86 Repack a real-valued array into a complex-valued array with half as
87 many columns. Used to retrieve complex arrays from elements that
88 expose them as real-valued array properties (gobject doesn't
89 understand complex numbers). The return value is a view into the
90 input array.
91 """
92 # FIXME: this function shouldn't exist, we should add complex
93 # types to gobject
94 if arr.dtype.kind != "f":
95 raise TypeError(arr)
96 return arr.view(dtype = numpy.dtype("c%d" % (arr.dtype.itemsize * 2)))
97
98
99#
100# =============================================================================
101#
102# Buffers
103#
104# =============================================================================
105#
106
107
108def get_unit_size(caps):
109 struct = caps[0]
110 name = struct.get_name()
111 if name == "audio/x-raw":
112 try:
113 info = GstAudio.AudioInfo()
114 info.from_caps(caps)
115 except NotImplementedError: # removed since gstreamer 1.22
116 success, info = GstAudio.audio_info_from_caps(caps)
117 assert success
118 return info.bpf
119 elif name == "video/x-raw" and struct["format"] in ("RGB", "RGBA", "ARGB", "ABGR"):
120 return struct["width"] * struct["height"] * (3 if struct["format"] == "RGB" else 4)
121 raise ValueError(caps)
122
123
124def numpy_dtype_from_caps(caps):
125 formats_dict = {
126 GstAudio.AudioFormat.F32: numpy.dtype("float32"),
127 GstAudio.AudioFormat.F64: numpy.dtype("float64"),
128 GstAudio.AudioFormat.S8: numpy.dtype("int8"),
129 GstAudio.AudioFormat.U8: numpy.dtype("uint8"),
130 GstAudio.AudioFormat.S16: numpy.dtype("int16"),
131 GstAudio.AudioFormat.U16: numpy.dtype("uint16"),
132 GstAudio.AudioFormat.S32: numpy.dtype("int32"),
133 GstAudio.AudioFormat.U32: numpy.dtype("uint32")
134 }
135
136 custom_formats_dict = {
137 "Z64LE" : numpy.dtype("complex64"),
138 "Z128LE": numpy.dtype("complex128")
139 }
140
141 try:
142 info = GstAudio.AudioInfo()
143 info.from_caps(caps)
144 except NotImplementedError: # removed since gstreamer 1.22
145 success, info = GstAudio.audio_info_from_caps(caps)
146 assert success
147
148 if info.finfo.format in formats_dict:
149 return formats_dict[info.finfo.format]
150 elif caps.get_structure(0).get_string("format") in custom_formats_dict:
151 return custom_formats_dict[caps.get_structure(0).get_string("format")]
152 else:
153 raise ValueError("unknown GstAudioFormat : %s" % caps.get_structure(0).get_string("format"))
154
155
156def format_string_from_numpy_dtype(dtype, formats_dict = {
157 numpy.dtype("float32"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.F32),
158 numpy.dtype("float64"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.F64),
159 numpy.dtype("int8"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.S8),
160 numpy.dtype("uint8"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.U8),
161 numpy.dtype("int16"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.S16),
162 numpy.dtype("uint16"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.U16),
163 numpy.dtype("int32"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.S32),
164 numpy.dtype("uint32"): GstAudio.AudioFormat.to_string(GstAudio.AudioFormat.U32),
165 numpy.dtype("complex64") : "Z64LE",
166 numpy.dtype("complex128") : "Z128LE"
167 }):
168 return formats_dict[dtype]
169
170
171def caps_from_array(arr, rate = None):
172 return Gst.Caps.from_string("audio/x-raw, format=(string)%s, rate=(int)%d, channels=(int)%d, layout=(string)interleaved, channel-mask=(bitmask)0" % (format_string_from_numpy_dtype(arr.dtype), rate, arr.shape[1]))
173
174
175def array_from_audio_sample(sample):
176 caps = sample.get_caps()
177 success, channels = caps.get_structure(0).get_int("channels")
178 assert success
179
180 buf = sample.get_buffer()
181 success, mapinfo = buf.map(Gst.MapFlags.READ)
182 assert success
183
184 a = numpy.frombuffer(mapinfo.data, dtype = numpy_dtype_from_caps(caps))
185 buf.unmap(mapinfo)
186 a.shape = len(a) // channels, channels
187
188 return a
189
190
191def audio_buffer_from_array(arr, timestamp, offset, rate):
192 buf = Gst.Buffer.new_wrapped(arr.tobytes())
193 buf.pts = timestamp
194 buf.duration = (Gst.SECOND * arr.shape[0] + rate // 2) // rate
195 buf.offset = offset
196 buf.offset_end = offset + arr.shape[0]
197 return buf
198
199
200#
201# =============================================================================
202#
203# Messages
204#
205# =============================================================================
206#
207
208
209def parse_spectrum_message(message):
210 """
211 Parse a "spectrum" message from the lal_whiten element, return a
212 LAL REAL8FrequencySeries containing the strain spectral density.
213 """
214 s = message.get_structure()
215 psd = lal.CreateREAL8FrequencySeries(
216 name = s["instrument"] if s.has_field("instrument") else "",
217 epoch = lal.LIGOTimeGPS(0, message.timestamp),
218 f0 = 0.0,
219 deltaF = s["delta-f"],
220 sampleUnits = lal.Unit(s["sample-units"].strip()),
221 length = len(s["magnitude"])
222 )
223 psd.data.data = numpy.array(s["magnitude"])
224 return psd
225
226
227#
228# =============================================================================
229#
230# Tags
231#
232# =============================================================================
233#
234
235
236def parse_framesrc_tags(taglist):
237 try:
238 instrument = taglist["instrument"]
239 except KeyError:
240 instrument = None
241 try:
242 channel_name = taglist["channel-name"]
243 except KeyError:
244 channel_name = None
245 if "units" in taglist:
246 sample_units = lal.Unit(taglist["units"].strip())
247 else:
248 sample_units = None
249 return {
250 "instrument": instrument,
251 "channel-name": channel_name,
252 "sample-units": sample_units
253 }
254
255
256# From Patrick Godwin
257# https://git.ligo.org/lscsoft/spiir/-/issues/70#note_602061
258def format_property(prop):
259 """
260 Formats a property suitable for use in a GStreamer element.
261 Used to convert 2-dimensional data structures to an appropriate
262 type as needed, since the mechanics of how they are treated differ
263 between versions of pygobject. Acts as a no-op depending on the
264 property type and version of pygobject.
265 """
266 # NOTE FIXME hopefully pygobject will get fixed and we can add a greater
267 # than in this test
268 if GObject.pygobject_version < (3, 29, 0):
269 return prop
270
271 elif is_nested_listlike(prop):
272 if isinstance(prop, numpy.ndarray):
273 prop = prop.tolist()
274 return [to_gvalue_array(row) for row in prop]
275
276 elif is_listlike(prop):
277 return to_gvalue_array(prop)
278
279 else:
280 return prop
281
282
283# From Patrick Godwin
284# https://git.ligo.org/lscsoft/spiir/-/issues/70#note_602061
285def to_gvalue_array(arr):
286 """
287 Converts a list-like object to a GValueArray.
288 """
289 # handle segments as guint64
290 if isinstance(arr, segment):
291 st = Gst.Structure(f"converter, array=(guint64) < {arr[0]:d}, {arr[1]:d} >")
292 elif isinstance(arr, numpy.ndarray):
293 arr = arr.tolist()
294 st = Gst.Structure.new_empty("converter")
295 st["array"] = Gst.ValueArray(list(arr))
296 else:
297 st = Gst.Structure.new_empty("converter")
298 st["array"] = Gst.ValueArray(list(arr))
299 result, val = st.get_array("array")
300 if not result:
301 raise ValueError("could not convert input to GValueArray")
302 return val
303
304
305# From Patrick Godwin
306# https://git.ligo.org/lscsoft/spiir/-/issues/70#note_602061
307def is_nested_listlike(obj):
308 """
309 Check if object is a nested list-like object.
310 """
311 if isinstance(obj, numpy.ndarray) and obj.ndim > 2:
312 raise ValueError("Only 1D or 2D numpy arrays are supported")
313 elif isinstance(obj, numpy.ndarray) and obj.ndim == 2:
314 return True
315 elif is_listlike(obj):
316 return any(is_listlike(row) for row in obj)
317 else:
318 return False
319
320
321# From Patrick Godwin
322# https://git.ligo.org/lscsoft/spiir/-/issues/70#note_602061
323def is_listlike(obj):
324 """
325 Check if object is a list-like object.
326 """
327 if isinstance(obj, numpy.ndarray) and obj.ndim == 1:
328 return True
329 else:
330 return isinstance(obj, Iterable) and not isinstance(obj, str)