gstlal 1.13.0
Loading...
Searching...
No Matches
datasource.py
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__doc__ = """Gravitational wave datasource utilities, including abstractions for storing the required
19information needed to connect to data sources. The main elements of the included API are:
20
21 1. DataSourceInfo class for storing necessary information to connect to datasource
22 2. Useful constants to help configuring a DataSourceInfo, like the Detector and DataSource classes
23 3. Pipeline building utilities
24 4. Command-line utilities for parsing and converting values into DataSource Info
25"""
26
27# Potential alternate copyright method
28# from gstlal.utilities import admin
29# admin.add_copyright(authors=['Kipp Cannon', 'Chad Hanna', 'Drew Keppel'], start_year=2009, end_year=2013)
30
31
32import collections
33import enum
34import optparse
35import os
36from pathlib import Path
37import tempfile
38import time
39import types
40from typing import Union, Dict
41
42import lal
43from lal import LIGOTimeGPS
44from ligo import segments
45from ligo.lw import utils as ligolw_utils
46from ligo.lw.utils import segments as ligolw_segments
47
48import gi
49gi.require_version('Gst', '1.0')
50from gi.repository import GObject
51from gi.repository import Gst
52
53try:
54 import bottle
55 if tuple(map(int, bottle.__version__.split("."))) < (0, 13):
56 # FIXME: see
57 # https://git.ligo.org/lscsoft/gstlal/-/merge_requests/146
58 # if the required patch is added to the distro-supplied
59 # bottle before a 0.13 is released, update the version
60 # check to the correct version
61 raise ImportError
62except ImportError:
63 # FIXME: remove after system-wide isntall can be relied on
64 from gstlal import bottle
65from gstlal import datafind
66from gstlal import pipeparts
67from gstlal.utilities import admin
68
69GObject.threads_init()
70Gst.init(None)
71
72DEFAULT_BLOCK_SIZE = 16384 * 8 * 512
73
74
75
78
79
80class Detector(str, enum.Enum):
81 """Enumeration of available detectors
82 """
83 G1 = 'G1'
84 H1 = 'H1'
85 H2 = 'H2'
86 K1 = 'K1'
87 L1 = 'L1'
88 V1 = 'V1'
89
90
91class DataSource(str, enum.Enum):
92 """Enumeration of available data sources
93
94 TODO: include descriptions of the options
95 """
96 ALIGO = 'AdvLIGO'
97 AVirgo = 'AdvVirgo'
98 Frames = 'frames'
99 FrameXMIT = 'framexmit'
100 LIGO = 'LIGO'
101 LVSHM = 'lvshm'
102 DEVSHM = 'devshm'
103 NDS = 'nds'
104 Silence = 'silence'
105 White = 'white'
106
107
108KNOWN_DATASOURCES = [
109 DataSource.ALIGO,
110 DataSource.AVirgo,
111 DataSource.Frames,
112 DataSource.FrameXMIT,
113 DataSource.LIGO,
114 DataSource.LVSHM,
115 DataSource.DEVSHM,
116 DataSource.NDS,
117 DataSource.Silence,
118 DataSource.White,
119]
120KNOWN_LIVE_DATASOURCES = [
121 DataSource.FrameXMIT,
122 DataSource.LVSHM,
123 DataSource.DEVSHM,
124]
125DEFAULT_SHARED_MEMORY_PARTITION = {
126 Detector.H1.value: "LHO_Data",
127 Detector.L1.value: "LLO_Data",
128 Detector.V1.value: "VIRGO_Data"
129}
130DEFAULT_SHARED_MEMORY_DIR = {
131 Detector.H1.value: "/dev/shm/kafka/H1_Data",
132 Detector.L1.value: "/dev/shm/kafka/L1_Data",
133 Detector.V1.value: "/dev/shm/kafka/V1_Data"
134}
135
136DEFAULT_STATE_CHANNEL = {detector.value: "LLD-DQ_VECTOR" for detector in Detector}
137DEFAULT_DQ_CHANNEL = {detector.value: "DMT-DQ_VECTOR" for detector in Detector}
138DEFAULT_FRAMXMIT_ADDR = {
139 Detector.H1.value: ("224.3.2.1", 7096),
140 Detector.L1.value: ("224.3.2.2", 7097),
141 Detector.V1.value: ("224.3.2.3", 7098),
142}
143DEFAULT_STATE_VECTOR_ON_OFF = {
144 Detector.H1.value: [0x7, 0x160],
145 Detector.H2.value: [0x7, 0x160],
146 Detector.L1.value: [0x7, 0x160],
147 Detector.V1.value: [0x67, 0x100]
148}
149DEFAULT_DQ_VECTOR_ON_OFF = {
150 Detector.H1.value: [0x7, 0x0],
151 Detector.H2.value: [0x7, 0x0],
152 Detector.L1.value: [0x7, 0x0],
153 Detector.V1.value: [0x7, 0x0]
154}
155
156HostInfo = collections.namedtuple('DataFindServerInfo', 'host port')
157
158
160 """Enumeration of available datafind servers"""
161 GeneralProp = HostInfo('datafind.ligo.org', 443)
162
163
164class DataSourceConfigError(ValueError):
165 """Error subclass for configuration errors"""
166
167
168
171
172
173class DataSourceInfo:
174 """A pythonic representation of a datasource with configured settings necessary for usage in pipelines
175 """
176
177 def __init__(self, data_source: Union[str, DataSource], channel_name: Dict[Detector, str],
178 gps_start_time: Union[int, LIGOTimeGPS] = None, gps_end_time: Union[int, LIGOTimeGPS] = None,
179 shared_memory_partition: Dict[Detector, str] = None, shared_memory_dir: Dict[Detector, str] = None,
180 frame_segments_name: str = None, state_vector_on_bits: Dict[Detector, str] = None,
181 state_vector_off_bits: Dict[Detector, str] = None, dq_vector_on_bits: str = None, dq_vector_off_bits: str = None, frame_cache: Union[str, Path] = None,
182 injections: Union[str, Path] = None, nds_host: str = None, nds_port: int = None, nds_channel_type: str = 'online', shared_memory_assumed_duration: int = 4,
183 shared_memory_block_size: int = 4096, frame_segments_file: Union[str, Path] = None, block_size: int = DEFAULT_BLOCK_SIZE, frame_type: Dict[Detector, str] = None,
184 data_find_server: Union[str or DataFindServer] = None, framexmit_addr: str = None, framexmit_iface: str = None,
185 state_channel_name: Dict[Detector, str] = None, dq_channel_name: Dict[Detector, str] = None,
186 idq_channel_name: Dict[Detector, str] = None,
187 idq_state_channel_name: Dict[Detector, str] = None):
188 """Create a DataSource information object that contains the necessary details to produce GStreamer elements
189 for loading gravitational wave data from a variety of possible sources.
190
191 Args:
192 data_source:
193 str or DataSource, the data source from [frames|framexmit|lvshm|nds|silence|white] which to download data
194 gps_start_time:
195 int or LIGOTimeGPS, the start time of the segment to analyze in GPS seconds. Required unless data_source=lvshm
196 gps_end_time:
197 int or LIGOTimeGPS, the end time of the segment to analyze in GPS seconds. Required unless data_source=lvshm
198 channel_name:
199 Dict[Detector, str] or Dict[Detector, HostInfo], the name of the channels to process per detector
200 framexmit_addr:
201 Dict[Detector, str] or Dict[Detector, HostInfo], the address of the framexmit service.
202 framexmit_iface:
203 str, the multicast interface address of the framexmit service.
204 state_channel_name:
205 Dict[Detector, str], the name of the state vector channel. This channel will be used to control the flow of data via the on/off bits.
206 dq_channel_name:
207 Dict[Detector, str], the name of the data quality channel. This channel will be used to control the flow of data via the on/off bits.
208 idq_channel_name:
209 Dict[Detector, str], the name of the idq channel. This channel will be used to create the idq_series information.
210 idq_state_channel_name:
211 Dict[Detector, str], the name of the idq state channel. This channel will be used to gate the idq_series information created by idq_channel_name.
212 shared_memory_partition:
213 Dict[Detector, str], the name of the shared memory partition for a given detector.
214 shared_memory_dir:
215 Dict[Detector, str], the name of the shared memory directory for a given detector.
216 frame_segments_name:
217 str, the name of the segments to extract from the segment tables. Required iff frame_segments_file is given
218 state_vector_on_bits:
219 Dict[Detector, str], default None, the state vector on bits to process (optional). The default is 0x7 for all detectors. Override with {Detector:bits} Only
220 currently has meaning for online (lvshm) data.
221 state_vector_off_bits:
222 Dict[Detector, str], default None, the state vector on bits to process (optional). The default is 0x160 for all detectors. Override with {Detector:bits} Only
223 currently has meaning for online (lvshm) data.
224 dq_vector_on_bits:
225 Dict[Detector, str], default None, the dq vector on bits to process (optional). The default is 0x7 for all detectors. Override with {Detector:bits} Only
226 currently has meaning for online (lvshm) data.
227 dq_vector_off_bits:
228 Dict[Detector, str], default None, the dq vector off bits to process (optional). The default is 0x160 for all detectors. Override with {Detector:bits} Only
229 currently has meaning for online (lvshm) data.
230 frame_cache:
231 str or Path, the name of the LAL cache listing the LIGO-Virgo .gwf frame files
232 injections:
233 str or Path, default None, the name of the LIGO light-weight XML file from which to load injections (optional)
234 nds_host:
235 str, the NDS server address. only used if data_source=nds
236 nds_port:
237 int, the NDS server port. only used if data_source=nds
238 nds_channel_type:
239 str, default 'online', the NDS channel type. Only used if data_source=nds
240 shared_memory_assumed_duration:
241 int, default 4, the assumed span of files in seconds. Default = 4.
242 shared_memory_block_size:
243 int, default 4096, the byte size to read per buffer.
244 frame_segments_file:
245 str or Path, the name of the LIGO light-weight XML file from which to load frame segments. Optional iff data_source=frames
246 block_size:
247 int, default 16384 * 8 * 512 (512 seconds of double precision data at 16384 Hz), Data block size to read in bytes. This parameter is only used if
248 data_source is one of {white, silence, AdvVirgo, LIGO, AdvLIGO, nds}.
249 frame_type:
250 Dict[Detector, str], default None, a dictionary setting the frame type (string) for each detector, e.g. {Detector.H1: 'H1_GWOSC_O2_16KHZ_R1'}. Used with data_source='frames'.
251 data_find_server:
252 str, default None, the data find server for LIGO data discovery. Used with data_source=frames.
253 """
254 self.data_source = data_source.name if isinstance(data_source, DataSourceInfo) else data_source
255 self.block_size = block_size
256 self.frame_cache = frame_cache.as_posix() if isinstance(frame_cache, Path) else frame_cache
257 self.frame_type = {} if frame_type is None else frame_type
258 self.data_find_server = '{}:{:d}'.format(data_find_server.host, data_find_server.port) if isinstance(data_find_server, HostInfo) else data_find_server
259 self.gps_start_time = gps_start_time.gpsSeconds if isinstance(gps_start_time, LIGOTimeGPS) else gps_start_time
260 self.gps_end_time = gps_end_time.gpsSeconds if isinstance(gps_end_time, LIGOTimeGPS) else gps_end_time
261 self.injections = injections.as_posix() if isinstance(injections, Path) else injections
262 self.channel_name = {} if channel_name is None else channel_name
263 self.nds_host = nds_host
264 self.nds_port = nds_port
265 self.nds_channel_type = nds_channel_type
266 self.framexmit_addr = DEFAULT_FRAMXMIT_ADDR.copy()
267 self.framexmit_iface = framexmit_iface
268 self.state_channel_name = DEFAULT_STATE_CHANNEL.copy()
269 self.dq_channel_name = DEFAULT_DQ_CHANNEL.copy()
270 self.idq_channel_name = {} if idq_channel_name is None else idq_channel_name
271 self.idq_state_channel_name = {} if idq_state_channel_name is None else idq_state_channel_name
272 self.shared_memory_partition = DEFAULT_SHARED_MEMORY_PARTITION.copy()
273 self.shared_memory_assumed_duration = shared_memory_assumed_duration
274 self.shared_memory_block_size = shared_memory_block_size
275 self.shared_memory_dir = DEFAULT_SHARED_MEMORY_DIR.copy()
276 self.frame_segments_file = frame_segments_file
277 self.frame_segments_name = frame_segments_name
278 self.state_vector_on_bits = {} if state_vector_on_bits is None else state_vector_on_bits
279 self.state_vector_off_bits = {} if state_vector_off_bits is None else state_vector_off_bits
280 self.dq_vector_on_bits = {} if dq_vector_on_bits is None else dq_vector_on_bits
281 self.dq_vector_off_bits = {} if dq_vector_off_bits is None else dq_vector_off_bits
282
283 if state_channel_name:
284 self.state_channel_name.update(state_channel_name)
285
286 if dq_channel_name:
287 self.dq_channel_name.update(dq_channel_name)
288
289 if framexmit_addr:
290 self.framexmit_addr.update(framexmit_addr)
291
292 if shared_memory_partition:
293 self.shared_memory_partition.update(shared_memory_partition)
294
295 if shared_memory_dir:
296 self.shared_memory_dir.update(shared_memory_dir)
297
298 self.validate()
299
300 # Set legacy attribute aliases
302 self.channel_dict = self.channel_name
309
310 self.seg = None
311 if self.gps_start_time is not None:
312 start = LIGOTimeGPS(self.gps_start_time)
313 end = LIGOTimeGPS(self.gps_end_time)
314 self.seg = segments.segment(start, end)
315
316 if self.frame_segments_file is not None:
317 self.frame_segments = ligolw_segments.segmenttable_get_by_name(
318 ligolw_utils.load_filename(self.frame_segments_file, contenthandler=ligolw_segments.LIGOLWContentHandler), self.frame_segments_name).coalesce()
319 if self.seg is not None:
320 # Clip frame segments to seek segment if it
321 # exists (not required, just saves some
322 # memory and I/O overhead)
323 self.frame_segments = segments.segmentlistdict((detector, seglist & segments.segmentlist([self.seg])) for detector, seglist in self.frame_segments.items())
324 else:
325
326 self.frame_segments = segments.segmentlistdict((detector, None) for detector in self.channel_dict)
327
328 if self.data_source == DataSource.Frames and (self.frame_cache is None and self.frame_type):
329 frame_type_dict = self.frame_type
330 _frame_cache = datafind.load_frame_cache(start, end, frame_type_dict, host=self.data_find_server)
331
332 self._frame_cache_fileobj = tempfile.NamedTemporaryFile(suffix=".cache", dir=os.getenv("_CONDOR_SCRATCH_DIR", tempfile.gettempdir()))
333 self.frame_cache = self._frame_cache_fileobj.name
334 with open(self.frame_cache, "w") as f:
335 for cacheentry in _frame_cache:
336 print(str(cacheentry), file=f)
337
338 self.state_vector_on_off_bits = zip_dict_values(self.state_vector_on_bits, self.state_vector_off_bits, defaults=DEFAULT_STATE_VECTOR_ON_OFF)
339 self.dq_vector_on_off_bits = zip_dict_values(self.dq_vector_on_bits, self.dq_vector_off_bits, defaults=DEFAULT_DQ_VECTOR_ON_OFF)
340
341 def validate(self):
342 """Validation of configuration"""
343 # Validate data_source
344 if self.data_source not in KNOWN_DATASOURCES:
345 raise DataSourceConfigError('Unknown datasource {}, must be one of: {}'.format(self.data_source, ', '.join(KNOWN_DATASOURCES)))
346
347 # Validate data_source == frames
348 if self.data_source == DataSource.Frames and (self.frame_cache is None and self.frame_type is None):
349 raise DataSourceConfigError("frame_cache or frame_type must be specified when using data_source='frames'")
350
351 # Validate channel_name not empty
352 if not self.channel_name:
353 raise DataSourceConfigError("Must specify at least one channel as {Detector: name}")
354
355 # Validate frame_segments_file
356 if self.frame_segments_file is not None and self.data_source != DataSource.Frames:
357 raise DataSourceConfigError("Can only give frame_segments_file if data_source='frames'")
358
359 # Validate frame_segments_name
360 if self.frame_segments_name is not None and self.frame_segments_file is None:
361 raise DataSourceConfigError("Can only specify frame_segments_name if frame_segments_file is given")
362
363 # Validate idq_channel_name comes with idq_state_channel_name
364 if self.idq_channel_name is not None and self.idq_state_channel_name is None:
365 raise DataSourceConfigError("Can only specify --idq-channel-name if --idq-state-channel-name is given")
366 elif self.idq_channel_name is None and self.idq_state_channel_name is not None:
367 raise DataSourceConfigError("Can only specify --idq-state-channel-name if --idq-channel-name is given")
368
369 # Validate NDS arguments
370 if self.data_source == DataSource.NDS:
371 if self.nds_host is None or self.nds_port is None:
372 raise DataSourceConfigError("Must specify nds_host and nds_port if data_source='nds'")
373
374 # Validate start and end arguments
375 if self.data_source in KNOWN_LIVE_DATASOURCES and (self.gps_start_time is not None or self.gps_end_time is not None):
376 raise DataSourceConfigError("Cannot set gps_start_time or gps_end_time for live source: {}".format(self.data_source))
377
378 if self.data_source not in KNOWN_LIVE_DATASOURCES and (self.gps_start_time is None or self.gps_end_time is None):
379 raise DataSourceConfigError("Must set gps_start_time and gps_end_time for non-live source: {}".format(self.data_source))
380
381 if self.gps_start_time is not None and self.gps_end_time is not None:
382 try:
383 start = LIGOTimeGPS(self.gps_start_time)
384 except ValueError as e:
385 raise DataSourceConfigError("invalid gps_start_time {:d}".format(self.gps_start_time)) from e
386 try:
387 end = LIGOTimeGPS(self.gps_end_time)
388 except ValueError as e:
389 raise DataSourceConfigError("invalid gps_end_time {:d}".format(self.gps_end_time)) from e
390 if start >= end:
391 raise DataSourceConfigError('Must specify gps_start_time < gps_end_time')
392
393 @staticmethod
394 def from_optparse(options: optparse.Values):
395 """Construct a DataSourceInfo object from an optparer.OptionParser
396
397 Args:
398 options:
399 Values, with all of the arguments defined in append_options
400
401 Returns:
402 DataSourceInfo object
403 """
404 channel_dict = parse_list_to_dict(options.channel_name)
405 frame_type = parse_list_to_dict(options.frame_type)
406 shared_memory_part = parse_list_to_dict(options.shared_memory_partition)
407 shared_memory_dir = parse_list_to_dict(options.shared_memory_dir)
408 state_channel_name = parse_list_to_dict(options.state_channel_name)
409 dq_channel_name = parse_list_to_dict(options.dq_channel_name)
410 idq_channel_name = parse_list_to_dict(options.idq_channel_name)
411 idq_state_channel_name = parse_list_to_dict(options.idq_state_channel_name)
412 state_vector_on_bits = parse_list_to_dict(options.state_vector_on_bits, value_transform=parse_int)
413 state_vector_off_bits = parse_list_to_dict(options.state_vector_off_bits, value_transform=parse_int)
414 dq_vector_on_bits = parse_list_to_dict(options.dq_vector_on_bits, value_transform=parse_int)
415 dq_vector_off_bits = parse_list_to_dict(options.dq_vector_off_bits, value_transform=parse_int)
416
417 return DataSourceInfo(data_source=options.data_source,
418 block_size=options.block_size,
419 frame_cache=options.frame_cache,
420 frame_type=frame_type,
421 data_find_server=options.data_find_server,
422 gps_start_time=options.gps_start_time,
423 gps_end_time=options.gps_end_time,
424 injections=options.injection_file,
425 channel_name=channel_dict,
426 nds_host=options.nds_host if options.data_source == DataSource.NDS else None,
427 nds_port=options.nds_port if options.data_source == DataSource.NDS else None,
428 nds_channel_type=options.nds_channel_type if options.data_source == DataSource.NDS else None,
429 framexmit_addr=options.framexmit_addr,
430 framexmit_iface=options.framexmit_iface,
431 state_channel_name=state_channel_name,
432 dq_channel_name=dq_channel_name,
433 idq_channel_name=idq_channel_name,
434 idq_state_channel_name=idq_state_channel_name,
435 shared_memory_partition=shared_memory_part,
436 shared_memory_assumed_duration=options.shared_memory_assumed_duration,
437 shared_memory_block_size=options.shared_memory_block_size,
438 shared_memory_dir=shared_memory_dir,
439 frame_segments_file=options.frame_segments_file,
440 frame_segments_name=options.frame_segments_name,
441 state_vector_on_bits=state_vector_on_bits,
442 state_vector_off_bits=state_vector_off_bits,
443 dq_vector_on_bits=dq_vector_on_bits,
444 dq_vector_off_bits=dq_vector_off_bits)
445
446
447
450
451
452def parse_host(host: str):
453 """Value transform for use with parse_list_to_dict that splits host into name and port tuple"""
454 name, port = host.split(':')
455 return (name, int(port))
456
457
458def parse_int(inp: str) -> int:
459 """Value transform for use with parse_list_to_dict that coerces string input to int"""
460 if inp.startswith('0x'):
461 return int(inp, 16)
462 return int(inp)
463
464
465def parse_list_to_dict(lst: list, value_transform: types.FunctionType = None, sep: str = '=', key_is_range: bool = False, range_sep: str = ':') -> dict:
466 """A general list to dict argument parsing coercion function
467
468 Args:
469 lst:
470 list, a list of the form ['A=V1', 'B=V2', ...], where "=" only has to match the sep_str argument
471 value_transform:
472 Function, default None. An optional transformation function to apply on values of the dictionary
473 sep:
474 str, default '=', the separator string between dict keys and values in list elements
475 key_is_range:
476 bool, default False. If True, the keys of the list are compound and contain range information e.g. "start:stop:remaining,list,of,items"
477 range_sep:
478 str, default ':' the separator string for range key information
479
480 Returns:
481 dict of the form {'A': value_transform('V1'), ...}
482
483 Examples:
484 >>> parse_list_to_dict(["H1=LSC-STRAIN", "H2=SOMETHING-ELSE"]) # doctest: +SKIP
485 {'H1': 'LSC-STRAIN', 'H2': 'SOMETHING-ELSE'}
486
487 >>> parse_list_to_dict(["0000:0002:H1=LSC_STRAIN_1,L1=LSC_STRAIN_2", "0002:0004:H1=LSC_STRAIN_3,L1=LSC_STRAIN_4", "0004:0006:H1=LSC_STRAIN_5,L1=LSC_STRAIN_6"], key_is_range=True) # doctest: +SKIP
488 {'0000': {'H1': 'LSC_STRAIN_1', 'L1': 'LSC_STRAIN_2'}, '0001': {'H1': 'LSC_STRAIN_1', 'L1': 'LSC_STRAIN_2'}, '0002': {'H1': 'LSC_STRAIN_3', 'L1': 'LSC_STRAIN_4'}, '0003': {'H1': 'LSC_STRAIN_3', 'L1': 'LSC_STRAIN_4'}, '0004': {'H1': 'LSC_STRAIN_5', 'L1': 'LSC_STRAIN_6'}, '0005': {'H1': 'LSC_STRAIN_5', 'L1': 'LSC_STRAIN_6'}}
489 """
490 if lst is None:
491 return
492 coerced = {}
493 if key_is_range:
494 # This will produce tuples (start, stop, str-to-dict)
495 splits = [e.split(range_sep) for e in lst]
496 for start, stop, val in splits:
497 for i in range(int(start), int(stop)):
498 key = str(i).zfill(4)
499 coerced[key] = parse_list_to_dict([l.strip() for l in val.split(',')], value_transform=value_transform, sep=sep, key_is_range=False)
500 else:
501 if len(lst) == 1 and sep not in lst[0]: # non-dict entry
502 return lst[0]
503 coerced = dict([e.split(sep) for e in lst])
504 if value_transform is not None:
505 for k in coerced:
506 coerced[k] = value_transform(coerced[k])
507 return coerced
508 return coerced
509
510
511def ravel_host(host: tuple) -> str:
512 """Value transform for use with ravel_dict_to_list that ravels a host name and port into a string"""
513 return '{}:{}'.format(host[0], str(host[1]))
514
515
516def ravel_dict_to_list(dct: dict, value_transform: types.FunctionType = str, sep: str = '=', key_is_range: bool = False, range_sep: str = ':', join_arg: str = None,
517 unzip: bool = False, filter_keys: list = None, range_elem: int = None) -> list:
518 """Functional inverse of parse_list_to_dict.
519
520 TODO: This function exists to work around pipeline.py's inability to give the same option more than once by producing a string to pass as an argument that encodes the other instances of the option.
521
522 Args:
523 dct:
524 dict, the dict to transform into a list
525 value_transform:
526 Function, default str, the way to transform values of the dict before adding to list
527 sep:
528 str, default '=' separator character to join key-value pairs
529 key_is_range:
530 bool, default False. If True, aggregate keys into ranges
531 range_sep:
532 str, default ':', the range separator to join range key min and max values
533 unzip:
534 bool, default False, if True unzip the dct arg and ravel each single-valued dict separately
535
536 Returns:
537 List[str] the dict converted into lists of strings
538 """
539
540 if key_is_range:
541 if range_elem is None:
542 raise NotImplementedError
543
544 dct = dct[str(range_elem).zfill(4)]
545
546 if filter_keys:
547 dct = dict([(k, v) for k, v in dct.items() if k in filter_keys])
548
549 if unzip:
550 dicts = unzip_dict_values(dct)
551 if not isinstance(join_arg, list):
552 join_arg = len(dicts) * [join_arg]
553 return [ravel_dict_to_list(d, value_transform=value_transform, sep=sep, key_is_range=key_is_range, range_sep=range_sep, join_arg=j, unzip=False) for d, j in
554 zip(dicts, join_arg)]
555 else:
556 lst = ['{}{}{}'.format(k, sep, value_transform(v)) for k, v in sorted(dct.items(), key=lambda x: x[0])]
557 if len(lst) == 0:
558 return lst
559 if join_arg is not None:
560 if len(lst) == 1:
561 return lst[0]
562 lst = lst[0] + ' ' + ' '.join('--' + join_arg + '=' + l for l in lst[1:])
563 return lst
564
565
566def zip_dict_values(*dicts, defaults: dict = None, key_union: bool = True):
567 """Zip dict values by matching keys
568
569 Args:
570 *dicts:
571 Iterable[dict], a collection of dicts whose values will be grouped by key in the order given
572 defaults:
573 dict, default None, if specified fill in missing values with these defaults
574 key_union:
575 bool, default True, if True then use a union of keys, else use intersection.
576
577 Examples:
578
579 >>> on_bit_list = parse_list_to_dict(["V1=7", "H1=7", "L1=7"], value_transform=int) # doctest: +SKIP
580 >>> off_bit_list = parse_list_to_dict(["V1=256", "H1=352", "L1=352"], value_transform=int) # doctest: +SKIP
581 >>> zip_dict_values(on_bit_list, off_bit_list, defaults=DEFAULT_STATE_VECTOR_ON_OFF) # doctest: +SKIP
582 {'V1': [7, 256], 'H1': [7, 352], 'H2': [7, 352], 'L1': [7, 352]}
583
584 >>> zip_dict_values(on_bit_list, off_bit_list,{}) # doctest: +SKIP
585 {'V1': [7, 256], 'H1': [7, 352], 'L1': [7, 352]}
586
587 >>> on_bit_list = parse_list_to_dict(["V1=0x7", "H1=0x7", "L1=0x7"], value_transform=lambda x: int(x, 16)) # doctest: +SKIP
588 >>> off_bit_list = parse_list_to_dict(["V1=0x256", "H1=0x352", "L1=0x352"], value_transform=lambda x: int(x, 16)) # doctest: +SKIP
589 >>> zip_dict_values(on_bit_list, off_bit_list,{}) # doctest: +SKIP
590 {'V1': [7, 598], 'H1': [7, 850], 'L1': [7, 850]}
591 """
592 keys = list(getattr(set(), 'union' if key_union else 'intersection')(*([set(d.keys()) for d in dicts] + ([] if defaults is None else [set(defaults.keys())]))))
593 unified = {}
594 for k in keys:
595 ordered_value = [d[k] for d in dicts if k in d]
596 if not ordered_value: # the only way this can happen is if k was in defaults but none of dicts, so we can assume defaults is not None
597 ordered_value = defaults[k]
598 unified[k] = ordered_value
599 return unified
600
601
602def unzip_dict_values(dct: dict):
603 """Split multi-valued dict into ordered collection of single-valued dicts with common keys
604 All values should be of same length.
605 """
606 num_vals = len(list(dct.values())[0])
607 if any(len(v) != num_vals for v in dct.values()):
608 raise ValueError('All dict values must have same length.')
609 dicts = [dict() for n in range(num_vals)]
610 for k in sorted(list(dct.keys())):
611 ordered_vals = list(dct[k])
612 for n in range(num_vals):
613 dicts[n][k] = ordered_vals[n]
614 return dicts
615
616
617def append_options(parser):
618 """
619 Append generic data source options to an OptionParser object in order
620 to have consistent an unified command lines and parsing throughout the project
621 for applications that read GW data.
622
623- --data-source [string]
624 Set the data source from [frames|framexmit|lvshm|nds|silence|white].
625
626- --block-size [int] (bytes)
627 Data block size to read in bytes. Default 16384 * 8 * 512 which is 512 seconds of double
628 precision data at 16384 Hz. This parameter is only used if --data-source is one of
629 white, silence, AdvVirgo, LIGO, AdvLIGO, nds.
630
631- --frame-cache [filename]
632 Set the name of the LAL cache listing the LIGO-Virgo .gwf frame files (optional).
633
634- --frame-type [string]
635 Set the frame type for a given instrument.
636 Can be given multiple times as --frame-type=IFO=FRAME-TYPE
637
638- --gps-start-time [int] (seconds)
639 Set the start time of the segment to analyze in GPS seconds.
640 Required unless --data-source is lvshm or framexmit
641
642- --gps-end-time [int] (seconds)
643 Set the end time of the segment to analyze in GPS seconds.
644 Required unless --data-source in lvshm,framexmit
645
646- --injection-file [filename]
647 Set the name of the LIGO light-weight XML file from which to load injections (optional).
648
649- --channel-name [string]
650 Set the name of the channels to process.
651 Can be given multiple times as --channel-name=IFO=CHANNEL-NAME
652
653- --nds-host [hostname]
654 Set the remote host or IP address that serves nds data.
655 This is required iff --data-source is nds
656
657- --nds-port [portnumber]
658 Set the port of the remote host that serves nds data, default = 31200.
659 This is required iff --data-source is nds
660
661- --nds-channel-type [string] type
662 FIXME please document
663
664- --framexmit-addr [string]
665 Set the address of the framexmit service. Can be given
666 multiple times as --framexmit-addr=IFO=xxx.xxx.xxx.xxx:port
667
668- --framexmit-iface [string]
669 Set the address of the framexmit interface.
670
671- --state-channel-name [string]
672 Set the name of the state vector channel.
673 This channel will be used to control the flow of data via the on/off bits.
674 Can be given multiple times as --state-channel-name=IFO=STATE-CHANNEL-NAME
675
676- --dq-channel-name [string]
677 Set the name of the data quality channel.
678 This channel will be used to control the flow of data via the on/off bits.
679 Can be given multiple times as --state-channel-name=IFO=DQ-CHANNEL-NAME
680
681- --shared-memory-partition [string]
682 Set the name of the shared memory partition for a given instrument.
683 Can be given multiple times as --shared-memory-partition=IFO=PARTITION-NAME
684
685- --shared-memory-dir [string]
686 Set the name of the shared memory directory for a given instrument.
687 Can be given multiple times as --shared-memory-dir=IFO=DIR-NAME
688
689- --shared-memory-assumed-duration [int]
690 Set the assumed span of files in seconds. Default = 4 seconds.
691
692- --shared-memory-block-size [int]
693 Set the byte size to read per buffer. Default = 4096 bytes.
694
695- --frame-segments-file [filename]
696 Set the name of the LIGO light-weight XML file from which to load frame segments.
697 Optional iff --data-source is frames
698
699- --frame-segments-name [string]
700 Set the name of the segments to extract from the segment tables.
701 Required iff --frame-segments-file is given
702
703- --state-vector-on-bits [hex]
704 Set the state vector on bits to process (optional).
705 The default is 0x7 for all detectors. Override with IFO=bits can be given multiple times.
706 Only currently has meaning for online (lvshm, framexmit) data
707
708- --state-vector-off-bits [hex]
709 Set the state vector off bits to process (optional).
710 The default is 0x160 for all detectors. Override with IFO=bits can be given multiple times.
711 Only currently has meaning for online (lvshm, framexmit) data
712
713- --dq-vector-on-bits [hex]
714 Set the state vector on bits to process (optional).
715 The default is 0x7 for all detectors. Override with IFO=bits can be given multiple times.
716 Only currently has meaning for online (lvshm, framexmit) data
717
718- --dq-vector-off-bits [hex]
719 Set the dq vector off bits to process (optional).
720 The default is 0x0 for all detectors. Override with IFO=bits can be given multiple times.
721 Only currently has meaning for online (lvshm, framexmit) data
722
723 **Typical usage case examples**
724
725 1. Reading data from frames::
726
727 --data-source=frames --gps-start-time=999999000 --gps-end-time=999999999 \\
728 --channel-name=H1=LDAS-STRAIN --frame-segments-file=segs.xml \\
729 --frame-segments-name=datasegments
730
731 2. Reading data from a fake LIGO source::
732
733 --data-source=LIGO --gps-start-time=999999000 --gps-end-time=999999999 \\
734 --channel-name=H1=FAIKE-STRAIN
735
736 3. Reading online data via framexmit::
737
738 --data-source=framexmit --channel-name=H1=FAIKE-STRAIN
739
740 4. Many other combinations possible, please add some!
741 """
742 group = optparse.OptionGroup(parser, "Data source options", "Use these options to set up the appropriate data source")
743 group.add_option("--data-source", metavar="source", help="Set the data source from [frames|framexmit|lvshm|nds|silence|white]. Required.")
744 group.add_option("--block-size", type="int", metavar="bytes", default=16384 * 8 * 512,
745 help="Data block size to read in bytes. Default 16384 * 8 * 512 (512 seconds of double precision data at 16384 Hz. This parameter is only used if --data-source is one of white, silence, AdvVirgo, LIGO, AdvLIGO, nds.")
746 group.add_option("--frame-cache", metavar="filename", help="Set the name of the LAL cache listing the LIGO-Virgo .gwf frame files (optional).")
747 group.add_option("--frame-type", metavar="name", action="append",
748 help="Set the frame type for a given instrument. Can be given multiple times as --frame-type=IFO=FRAME-TYPE. Used with --data-source=frames")
749 group.add_option("--data-find-server", metavar="url", help="Set the data find server for LIGO data discovery. Used with --data-source=frames")
750 group.add_option("--gps-start-time", metavar="seconds", help="Set the start time of the segment to analyze in GPS seconds. Required unless --data-source=lvshm")
751 group.add_option("--gps-end-time", metavar="seconds", help="Set the end time of the segment to analyze in GPS seconds. Required unless --data-source=lvshm")
752 group.add_option("--injection-file", metavar="filename", help="Set the name of the LIGO light-weight XML file from which to load injections (optional).")
753 group.add_option("--channel-name", metavar="name", action="append",
754 help="Set the name of the channels to process. Can be given multiple times as --channel-name=IFO=CHANNEL-NAME")
755 group.add_option("--idq-channel-name", metavar="idqname", action="append", help="iDQ channel names to process. Must also provide idq-state-channel-name. Can be given multiple times as --idq-channel-name=IFO=IDQ-CHANNEL-NAME")
756 group.add_option("--idq-state-channel-name", metavar="idqstatename", action="append", help="iDQ state channel names to process. Can be given multiple times as --idq-state-channel-name=IFO=IDQ-STATE-CHANNEL-NAME")
757 group.add_option("--nds-host", metavar="hostname", help="Set the remote host or IP address that serves nds data. This is required iff --data-source=nds")
758 group.add_option("--nds-port", metavar="portnumber", type=int, default=31200,
759 help="Set the port of the remote host that serves nds data. This is required iff --data-source=nds")
760 group.add_option("--nds-channel-type", metavar="type", default="online",
761 help="Set the port of the remote host that serves nds data. This is required only if --data-source=nds. default==online")
762 group.add_option("--framexmit-addr", metavar="name", action="append",
763 help="Set the address of the framexmit service. Can be given multiple times as --framexmit-addr=IFO=xxx.xxx.xxx.xxx:port")
764 group.add_option("--framexmit-iface", metavar="name", help="Set the multicast interface address of the framexmit service.")
765 group.add_option("--state-channel-name", metavar="name", action="append",
766 help="Set the name of the state vector channel. This channel will be used to control the flow of data via the on/off bits. Can be given multiple times as --channel-name=IFO=CHANNEL-NAME")
767 group.add_option("--dq-channel-name", metavar="name", action="append",
768 help="Set the name of the data quality channel. This channel will be used to control the flow of data via the on/off bits. Can be given multiple times as --channel-name=IFO=CHANNEL-NAME")
769 group.add_option("--shared-memory-partition", metavar="name", action="append",
770 help="Set the name of the shared memory partition for a given instrument. Can be given multiple times as --shared-memory-partition=IFO=PARTITION-NAME")
771 group.add_option("--shared-memory-dir", metavar="name", action="append",
772 help="Set the name of the shared memory directory for a given instrument. Can be given multiple times as --shared-memory-dir=IFO=DIR-NAME")
773 group.add_option("--shared-memory-assumed-duration", type="int", default=4, help="Set the assumed span of files in seconds. Default = 4.")
774 group.add_option("--shared-memory-block-size", type="int", default=4096, help="Set the byte size to read per buffer. Default = 4096.")
775 group.add_option("--frame-segments-file", metavar="filename",
776 help="Set the name of the LIGO light-weight XML file from which to load frame segments. Optional iff --data-source=frames")
777 group.add_option("--frame-segments-name", metavar="name", help="Set the name of the segments to extract from the segment tables. Required iff --frame-segments-file is given")
778 group.add_option("--state-vector-on-bits", metavar="bits", default=[], action="append",
779 help="Set the state vector on bits to process (optional). The default is 0x7 for all detectors. Override with IFO=bits can be given multiple times. Only currently has meaning for online (lvshm) data.")
780 group.add_option("--state-vector-off-bits", metavar="bits", default=[], action="append",
781 help="Set the state vector off bits to process (optional). The default is 0x160 for all detectors. Override with IFO=bits can be given multiple times. Only currently has meaning for online (lvshm) data.")
782 group.add_option("--dq-vector-on-bits", metavar="bits", default=[], action="append",
783 help="Set the DQ vector on bits to process (optional). The default is 0x7 for all detectors. Override with IFO=bits can be given multiple times. Only currently has meaning for online (lvshm) data.")
784 group.add_option("--dq-vector-off-bits", metavar="bits", default=[], action="append",
785 help="Set the DQ vector off bits to process (optional). The default is 0x160 for all detectors. Override with IFO=bits can be given multiple times. Only currently has meaning for online (lvshm) data.")
786 parser.add_option_group(group)
787
788
789
792
793
794def pipeline_seek_for_gps(pipeline, gps_start_time, gps_end_time, flags=Gst.SeekFlags.FLUSH):
795 """
796 Create a new seek event, i.e., Gst.Event.new_seek() for a given
797 gps_start_time and gps_end_time, with optional flags.
798
799 @param gps_start_time start time as LIGOTimeGPS, double or float
800 @param gps_end_time start time as LIGOTimeGPS, double or float
801 """
802
803 def seek_args_for_gps(gps_time):
804 """!
805 Convenience routine to convert a GPS time to a seek type and a
806 GStreamer timestamp.
807 """
808
809 if gps_time is None or gps_time == -1:
810 return (Gst.SeekType.NONE, -1) # -1 == Gst.CLOCK_TIME_NONE
811 elif hasattr(gps_time, 'ns'):
812 return (Gst.SeekType.SET, gps_time.ns())
813 else:
814 return (Gst.SeekType.SET, int(float(gps_time) * Gst.SECOND))
815
816 start_type, start_time = seek_args_for_gps(gps_start_time)
817 stop_type, stop_time = seek_args_for_gps(gps_end_time)
818
819 # FIXME: should seek whole pipeline, but there are several
820 # problems preventing us from doing that.
821 #
822 # because the framecpp demuxer has no source pads until decoding
823 # begins, the bottom halves of pipelines start out disconnected
824 # from the top halves of pipelines, which means the seek events
825 # (which are sent to sink elements) don't make it all the way to
826 # the source elements. dynamic pipeline building will not fix the
827 # problem because the dumxer does not carry the "SINK" flag so even
828 # though it starts with only a sink pad and no source pads it still
829 # won't be sent the seek event. gstreamer's own demuxers must
830 # somehow have a solution to this problem, but I don't know what it
831 # is. I notice that many implement the send_event() method
832 # override, and it's possible that's part of the solution.
833 #
834 # seeking the pipeline can only be done in the PAUSED state. the
835 # GstBaseSrc baseclass seeks itself to 0 when changing to the
836 # paused state, and the preroll is performed before the seek event
837 # we send to the pipeline is processed, so the preroll occurs with
838 # whatever random data a seek to "0" causes source elements to
839 # produce. for us, when processing GW data, this leads to the
840 # whitener element's initial spectrum estimate being initialized
841 # from that random data, and a non-zero chance of even getting
842 # triggers out of it, all of which is very bad.
843 #
844 # the only way we have at the moment to solve both problems --- to
845 # ensure seek events arrive at source elements and to work around
846 # GstBaseSrc's initial seek to 0 --- is to send seek events
847 # directly to the source elements ourselves before putting the
848 # pipeline into the PAUSED state. the elements are happy to
849 # receive seek events in the READY state, and GstBaseSrc updtes its
850 # current segment using that seek so that when it transitions to
851 # the PAUSED state and does its intitial seek it seeks to our
852 # requested time, not to 0.
853 #
854 # So: this function needs to be called with the pipeline in the
855 # READY state in order to guarantee the data stream starts at the
856 # requested start time, and does not get prerolled with random
857 # data. For safety we include a check of the pipeline's current
858 # state.
859 #
860 # if in the future we find some other solution to these problems
861 # the story might change and the pipeline state required on entry
862 # into this function might change.
863
864 # pipeline.seek(1.0, Gst.Format(Gst.Format.TIME), flags, start_type, start_time, stop_type, stop_time)
865
866 if pipeline.current_state != Gst.State.READY:
867 raise ValueError("pipeline must be in READY state")
868
869 for elem in pipeline.iterate_sources():
870 elem.seek(1.0, Gst.Format(Gst.Format.TIME), flags, start_type, start_time, stop_type, stop_time)
871
872
873def mksegmentsrcgate(pipeline, src, segment_list, invert_output=False, rate=1, **kwargs):
874 """
875 Takes a segment list and produces a gate driven by it. Hook up your own input and output.
876
877 @param kwargs passed through to pipeparts.mkgate(), e.g., used to set the gate's name.
878
879 Gstreamer graph describing this function:
880
881 .. graphviz::
882
883 digraph G {
884 compound=true;
885 node [shape=record fontsize=10 fontname="Verdana"];
886 rankdir=LR;
887 lal_segmentsrc;
888 lal_gate;
889 in [label="<src>"];
890 out [label="<return value>"];
891 in -> lal_gate -> out;
892 lal_segmentsrc -> lal_gate;
893 }
894
895 """
896 return pipeparts.mkgate(pipeline, src, threshold=1, control=pipeparts.mkcapsfilter(pipeline, pipeparts.mksegmentsrc(pipeline, segment_list, invert_output=invert_output),
897 caps="audio/x-raw, rate=%d" % rate), **kwargs)
898
899
900def mkbasicsrc(pipeline, gw_data_source_info, instrument, verbose=False):
901 """
902 All the conditionals and stupid pet tricks for reading real or
903 simulated h(t) data in one place.
904
905 Consult the append_options() function and the GWDataSourceInfo class
906
907 This src in general supports only one instrument although
908 GWDataSourceInfo contains dictionaries of multi-instrument things. By
909 specifying the instrument when calling this function you will get ony a single
910 instrument source. A code wishing to have multiple basicsrcs will need to call
911 this function for each instrument.
912
913 **Gstreamer Graph**
914
915 .. graphviz::
916
917 digraph mkbasicsrc {
918 compound=true;
919 node [shape=record fontsize=10 fontname="Verdana"];
920 subgraph clusterfakesrc {
921 fake_0 [label="fakesrc: white, silence, AdvVirgo, LIGO, AdvLIGO"];
922 color=black;
923 label="Possible path #1";
924 }
925 subgraph clusterframes {
926 color=black;
927 frames_0 [label="lalcachesrc: frames"];
928 frames_1 [label ="framecppchanneldemux"];
929 frames_2 [label ="queue"];
930 frames_3 [label ="gate (if user provides segments)", style=filled, color=lightgrey];
931 frames_4 [label ="audiorate"];
932 frames_0 -> frames_1 -> frames_2 -> frames_3 ->frames_4;
933 label="Possible path #2";
934 }
935 subgraph clusteronline {
936 color=black;
937 online_0 [label="lvshmsrc|framexmit"];
938 online_1 [label ="framecppchanneldemux"];
939 online_2a [label ="strain queue"];
940 online_2b [label ="statevector queue"];
941 online_3 [label ="statevector"];
942 online_4 [label ="gate"];
943 online_5 [label ="audiorate"];
944 online_6 [label ="queue"];
945 online_0 -> online_1;
946 online_1 -> online_2a;
947 online_1 -> online_2b;
948 online_2b -> online_3;
949 online_2a -> online_4;
950 online_3 -> online_4 -> online_5 -> online_6;
951 label="Possible path #3";
952 }
953 subgraph clusternds {
954 nds_0 [label="ndssrc"];
955 color=black;
956 label="Possible path #4";
957 }
958 audioconv [label="audioconvert"];
959 progress [label="progressreport (if verbose)", style=filled, color=lightgrey];
960 sim [label="lalsimulation (if injections requested)", style=filled, color=lightgrey];
961 queue [label="queue (if injections requested)", style=filled, color=lightgrey];
962
963 // The connections
964 fake_0 -> audioconv [ltail=clusterfakesrc];
965 frames_4 -> audioconv [ltail=clusterframes];
966 online_6 -> audioconv [ltail=clusteronline];
967 nds_0 -> audioconv [ltail=clusternds];
968 audioconv -> progress -> sim -> queue -> "?";
969 }
970
971 """
972 statevector = dqvector = None
973 idqseries = None
974 idqstatevector = None
975
976 # NOTE: timestamp_offset is a hack to allow seeking with fake
977 # sources, a real solution should be fixing the general timestamp
978 # problem which would allow seeking to work properly
979 if gw_data_source_info.data_source == DataSource.White:
980 src = pipeparts.mkfakesrc(pipeline, instrument, gw_data_source_info.channel_dict[instrument], blocksize=gw_data_source_info.block_size, volume=1.0,
981 timestamp_offset=int(gw_data_source_info.seg[0]) * Gst.SECOND)
982 elif gw_data_source_info.data_source == DataSource.Silence:
983 src = pipeparts.mkfakesrc(pipeline, instrument, gw_data_source_info.channel_dict[instrument], blocksize=gw_data_source_info.block_size, wave=4,
984 timestamp_offset=int(gw_data_source_info.seg[0]) * Gst.SECOND)
985 elif gw_data_source_info.data_source == DataSource.LIGO:
986 src = pipeparts.mkfakeLIGOsrc(pipeline, instrument=instrument, channel_name=gw_data_source_info.channel_dict[instrument], blocksize=gw_data_source_info.block_size)
987 elif gw_data_source_info.data_source == DataSource.ALIGO:
988 src = pipeparts.mkfakeadvLIGOsrc(pipeline, instrument=instrument, channel_name=gw_data_source_info.channel_dict[instrument], blocksize=gw_data_source_info.block_size)
989 elif gw_data_source_info.data_source == DataSource.AVirgo:
990 src = pipeparts.mkfakeadvvirgosrc(pipeline, instrument=instrument, channel_name=gw_data_source_info.channel_dict[instrument], blocksize=gw_data_source_info.block_size)
991 elif gw_data_source_info.data_source == DataSource.Frames:
992 if instrument == Detector.V1:
993 # FIXME Hack because virgo often just uses "V" in
994 # the file names rather than "V1". We need to
995 # sieve on "V"
996 src = pipeparts.mklalcachesrc(pipeline, location=gw_data_source_info.frame_cache, cache_src_regex="V")
997 else:
998 src = pipeparts.mklalcachesrc(pipeline, location=gw_data_source_info.frame_cache, cache_src_regex=instrument[0], cache_dsc_regex=instrument)
999 demux = pipeparts.mkframecppchanneldemux(pipeline, src, do_file_checksum=False, channel_list=list(map("%s:%s".__mod__, gw_data_source_info.channel_dict.items())))
1000 pipeparts.framecpp_channeldemux_set_units(demux, dict.fromkeys(demux.get_property("channel-list"), "strain"))
1001 # allow frame reading and decoding to occur in a diffrent
1002 # thread
1003 src = pipeparts.mkqueue(pipeline, None, max_size_buffers=0, max_size_bytes=0, max_size_time=8 * Gst.SECOND)
1004 pipeparts.src_deferred_link(demux, "%s:%s" % (instrument, gw_data_source_info.channel_dict[instrument]), src.get_static_pad("sink"))
1005 if gw_data_source_info.frame_segments[instrument] is not None:
1006 # FIXME: make segmentsrc generate segment samples
1007 # at the sample rate of h(t)?
1008 # FIXME: make gate leaky when I'm certain that
1009 # will work.
1010 src = pipeparts.mkgate(pipeline, src, threshold=1, control=pipeparts.mksegmentsrc(pipeline, gw_data_source_info.frame_segments[instrument]),
1011 name="%s_frame_segments_gate" % instrument)
1012 pipeparts.framecpp_channeldemux_check_segments.set_probe(src.get_static_pad("src"), gw_data_source_info.frame_segments[instrument])
1013 # FIXME: remove this when pipeline can handle disconts
1014 src = pipeparts.mkaudiorate(pipeline, src, skip_to_first=True, silent=False)
1015 elif gw_data_source_info.data_source in (DataSource.FrameXMIT.value, DataSource.LVSHM.value, DataSource.DEVSHM.value):
1016 # See https://wiki.ligo.org/DAC/ER2DataDistributionPlan#LIGO_Online_DQ_Channel_Specifica
1017 state_vector_on_bits, state_vector_off_bits = gw_data_source_info.state_vector_on_off_bits[instrument]
1018 dq_vector_on_bits, dq_vector_off_bits = gw_data_source_info.dq_vector_on_off_bits[instrument]
1019
1020 if gw_data_source_info.data_source == DataSource.LVSHM:
1021 # FIXME make wait_time adjustable through web
1022 # interface or command line or both
1023 src = pipeparts.mklvshmsrc(pipeline, shm_name=gw_data_source_info.shm_part_dict[instrument], assumed_duration=gw_data_source_info.shm_assumed_duration,
1024 blocksize=gw_data_source_info.shm_block_size, wait_time=120)
1025 elif gw_data_source_info.data_source == DataSource.FrameXMIT:
1026 src = pipeparts.mkframexmitsrc(pipeline, multicast_iface=gw_data_source_info.framexmit_iface, multicast_group=gw_data_source_info.framexmit_addr[instrument][0],
1027 port=gw_data_source_info.framexmit_addr[instrument][1], wait_time=120)
1028 elif gw_data_source_info.data_source == DataSource.DEVSHM:
1029 src = pipeparts.mkdevshmsrc(pipeline, shm_dirname=gw_data_source_info.shared_memory_dir[instrument], wait_time=60, watch_suffix='.gwf')
1030 else:
1031 # impossible code path
1032 raise ValueError(gw_data_source_info.data_source)
1033
1034 # 10 minutes of buffering, then demux
1035 src = pipeparts.mkqueue(pipeline, src, max_size_buffers=0, max_size_bytes=0, max_size_time=Gst.SECOND * 60 * 10)
1036 src = pipeparts.mkframecppchanneldemux(pipeline, src, do_file_checksum=False, skip_bad_files=True)
1037
1038 # extract state vector and DQ vector and convert to
1039 # booleans
1040 if gw_data_source_info.dq_channel_dict[instrument] == gw_data_source_info.state_channel_dict[instrument]:
1041 dqstatetee = pipeparts.mktee(pipeline, None)
1042 statevectorelem = statevector = pipeparts.mkstatevector(pipeline, dqstatetee, required_on=state_vector_on_bits, required_off=state_vector_off_bits,
1043 name="%s_state_vector" % instrument)
1044 dqvectorelem = dqvector = pipeparts.mkstatevector(pipeline, dqstatetee, required_on=dq_vector_on_bits, required_off=dq_vector_off_bits,
1045 name="%s_dq_vector" % instrument)
1046 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.state_channel_dict[instrument]), dqstatetee.get_static_pad("sink"))
1047 else:
1048 # DQ and state vector are distinct channels
1049 # first DQ
1050 dqvectorelem = dqvector = pipeparts.mkstatevector(pipeline, None, required_on=dq_vector_on_bits, required_off=dq_vector_off_bits, name="%s_dq_vector" % instrument)
1051 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.dq_channel_dict[instrument]), dqvector.get_static_pad("sink"))
1052 # then State
1053 statevectorelem = statevector = pipeparts.mkstatevector(pipeline, None, required_on=state_vector_on_bits, required_off=state_vector_off_bits,
1054 name="%s_state_vector" % instrument)
1055 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.state_channel_dict[instrument]), statevector.get_static_pad("sink"))
1056
1057 @bottle.route("/%s/statevector_on.txt" % instrument)
1058 def state_vector_state(elem=statevectorelem):
1059 t = float(lal.UTCToGPS(time.gmtime()))
1060 on = elem.get_property("on-samples")
1061 return "%.9f %d" % (t, on)
1062
1063 @bottle.route("/%s/statevector_off.txt" % instrument)
1064 def state_vector_state(elem=statevectorelem):
1065 t = float(lal.UTCToGPS(time.gmtime()))
1066 off = elem.get_property("off-samples")
1067 return "%.9f %d" % (t, off)
1068
1069 @bottle.route("/%s/statevector_gap.txt" % instrument)
1070 def state_vector_state(elem=statevectorelem):
1071 t = float(lal.UTCToGPS(time.gmtime()))
1072 gap = elem.get_property("gap-samples")
1073 return "%.9f %d" % (t, gap)
1074
1075 @bottle.route("/%s/dqvector_on.txt" % instrument)
1076 def dq_vector_state(elem=dqvectorelem):
1077 t = float(lal.UTCToGPS(time.gmtime()))
1078 on = elem.get_property("on-samples")
1079 return "%.9f %d" % (t, on)
1080
1081 @bottle.route("/%s/dqvector_off.txt" % instrument)
1082 def dq_vector_state(elem=dqvectorelem):
1083 t = float(lal.UTCToGPS(time.gmtime()))
1084 off = elem.get_property("off-samples")
1085 return "%.9f %d" % (t, off)
1086
1087 @bottle.route("/%s/dqvector_gap.txt" % instrument)
1088 def dq_vector_state(elem=dqvectorelem):
1089 t = float(lal.UTCToGPS(time.gmtime()))
1090 gap = elem.get_property("gap-samples")
1091 return "%.9f %d" % (t, gap)
1092
1093 # extract strain with 1 buffer of buffering
1094 strain = pipeparts.mkqueue(pipeline, None, max_size_buffers=1, max_size_bytes=0, max_size_time=0)
1095 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.channel_dict[instrument]), strain.get_static_pad("sink"))
1096 pipeparts.framecpp_channeldemux_set_units(src, {"%s:%s" % (instrument, gw_data_source_info.channel_dict[instrument]): "strain"})
1097
1098 # extract idq series and idqstatevector
1099 if instrument in gw_data_source_info.idq_channel_name:
1100 idqseries = pipeparts.mkqueue(pipeline, None, max_size_buffers=1, max_size_bytes=0, max_size_time=0)
1101 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.idq_channel_name[instrument]), idqseries.get_static_pad("sink"))
1102 if instrument in gw_data_source_info.idq_state_channel_name:
1103 idqstatevector = pipeparts.mkqueue(pipeline, None, max_size_buffers=1, max_size_bytes=0, max_size_time=0)
1104 pipeparts.src_deferred_link(src, "%s:%s" % (instrument, gw_data_source_info.idq_state_channel_name[instrument]), idqstatevector.get_static_pad("sink"))
1105
1106 # fill in holes, skip duplicate data
1107 statevector = pipeparts.mkreblock(pipeline, pipeparts.mkaudiorate(pipeline, statevector, skip_to_first=True, silent=False), block_duration = Gst.SECOND // 4)
1108 dqvector = pipeparts.mkreblock(pipeline, pipeparts.mkaudiorate(pipeline, dqvector, skip_to_first=True, silent=False), block_duration = Gst.SECOND // 4)
1109 src = pipeparts.mkreblock(pipeline, pipeparts.mkaudiorate(pipeline, strain, skip_to_first=True, silent=False, name="%s_strain_audiorate" % instrument), block_duration = Gst.SECOND // 4)
1110 if instrument in gw_data_source_info.idq_channel_name:
1111 idqseries = pipeparts.mkreblock(pipeline, pipeparts.mkaudiorate(pipeline, idqseries, skip_to_first=True, silent=False), block_duration = Gst.SECOND // 4)
1112 if instrument in gw_data_source_info.idq_state_channel_name:
1113 idqstatevector = pipeparts.mkreblock(pipeline, pipeparts.mkaudiorate(pipeline, idqstatevector, skip_to_first=True, silent=False), block_duration = Gst.SECOND // 4)
1114
1115 @bottle.route("/%s/strain_dropped.txt" % instrument)
1116 # FIXME don't hard code the sample rate
1117 def strain_add(elem=src, rate=16384):
1118 t = float(lal.UTCToGPS(time.gmtime()))
1119 # yes I realize we are reading the "add" property for a
1120 # route called dropped. That is because the data which
1121 # is "dropped" on route is "added" by the audiorate
1122 # element
1123 add = elem.get_property("add")
1124 return "%.9f %d" % (t, add // rate)
1125
1126 # use state vector and DQ vector to gate strain. the sizes
1127 # of the queues on the control inputs are not important.
1128 # they must be large enough to buffer the state vector
1129 # streams until they are needed, but the streams will be
1130 # consumed immediately when needed so there is no risk that
1131 # these queues actually fill up or add latency. be
1132 # generous.
1133 statevector = pipeparts.mktee(pipeline, statevector)
1134 dqvector = pipeparts.mktee(pipeline, dqvector)
1135 src = pipeparts.mkgate(pipeline, src, threshold=1, control=pipeparts.mkqueue(pipeline, statevector, max_size_buffers=0, max_size_bytes=0, max_size_time=0),
1136 default_state=False, name="%s_state_vector_gate" % instrument)
1137 src = pipeparts.mkgate(pipeline, src, threshold=1, control=pipeparts.mkqueue(pipeline, dqvector, max_size_buffers=0, max_size_bytes=0, max_size_time=0),
1138 default_state=False, name="%s_dq_vector_gate" % instrument)
1139 # extract idq state vector
1140 # use the idq state vector, state and dq vectors to gate idq series.
1141 if instrument in gw_data_source_info.idq_channel_name:
1142 idqseries = pipeparts.mkgate(pipeline, idqseries, threshold=1, control=pipeparts.mkqueue(pipeline, statevector, max_size_buffers=0, max_size_bytes=0, max_size_time=0),
1143 default_state=False, name="%s_idq_state_vector_gate" % instrument)
1144 idqseries = pipeparts.mkgate(pipeline, idqseries, threshold=1, control=pipeparts.mkqueue(pipeline, dqvector, max_size_buffers=0, max_size_bytes=0, max_size_time=0),
1145 default_state=False, name="%s_idq_dq_vector_gate" % instrument)
1146 if instrument in gw_data_source_info.idq_state_channel_name:
1147 idqseries = pipeparts.mkgate(pipeline, idqseries, threshold=1, control=pipeparts.mkqueue(pipeline, idqstatevector, max_size_buffers=0, max_size_bytes=0, max_size_time=0),
1148 default_state=False, name="%s_idq_idqstate_vector_gate" % instrument)
1149 elif gw_data_source_info.data_source == DataSource.NDS:
1150 src = pipeparts.mkndssrc(pipeline, gw_data_source_info.nds_host, instrument, gw_data_source_info.channel_dict[instrument], gw_data_source_info.nds_channel_type,
1151 blocksize=gw_data_source_info.block_size, port=gw_data_source_info.nds_port)
1152 else:
1153 raise ValueError("invalid data_source: %s" % gw_data_source_info.data_source)
1154
1155 #
1156 # provide an audioconvert element to allow Virgo data (which is
1157 # single-precision) to be adapted into the pipeline
1158 #
1159
1160 src = pipeparts.mkaudioconvert(pipeline, src)
1161
1162 #
1163 # progress report
1164 #
1165
1166 if verbose:
1167 src = pipeparts.mkprogressreport(pipeline, src, "progress_src_%s" % instrument)
1168
1169 #
1170 # optional injections
1171 #
1172
1173 if gw_data_source_info.injection_filename is not None:
1174 src = pipeparts.mkinjections(pipeline, src, gw_data_source_info.injection_filename)
1175 # let the injection code run in a different thread than the
1176 # whitener, etc.,
1177 src = pipeparts.mkqueue(pipeline, src, max_size_bytes=0, max_size_buffers=0, max_size_time=Gst.SECOND * 64)
1178
1179 #
1180 # done
1181 #
1182
1183 return src, statevector, dqvector, idqseries
1184
1185
1186def mkhtgate(pipeline, src, control=None, threshold=8.0, attack_length=128, hold_length=128, **kwargs):
1187 """
1188 A convenience function to provide thresholds on input data. This can
1189 be used to remove large spikes / glitches etc. Of course you can use it for
1190 other stuff by plugging whatever you want as input and ouput
1191
1192 NOTE: the queues constructed by this code assume the attack and
1193 hold lengths combined are less than 1 second in duration.
1194
1195 **Gstreamer Graph**
1196
1197 .. graphviz::
1198
1199 digraph G {
1200 compound=true;
1201 node [shape=record fontsize=10 fontname="Verdana"];
1202 rankdir=LR;
1203 tee ;
1204 inputqueue ;
1205 lal_gate ;
1206 in [label="<src>"];
1207 out [label="<return>"];
1208 in -> tee -> inputqueue -> lal_gate -> out;
1209 tee -> lal_gate;
1210 }
1211
1212 """
1213 # FIXME someday explore a good bandpass filter
1214 # src = pipeparts.mkaudiochebband(pipeline, src, low_frequency, high_frequency)
1215 if control is None:
1216 control = src = pipeparts.mktee(pipeline, src)
1217 src = pipeparts.mkqueue(pipeline, src, max_size_time=Gst.SECOND, max_size_bytes=0, max_size_buffers=0)
1218 return pipeparts.mkgate(pipeline, src, control=control, threshold=threshold, attack_length=-attack_length, hold_length=-hold_length, invert_control=True, **kwargs)
1219
1220
1221
1224
1225
1226@admin.deprecated('replaced by parse_list_to_dict')
1227def channel_dict_from_channel_list(channel_list):
1228 """
1229 Given a list of channels, produce a dictionary keyed by ifo of channel names:
1230
1231 The list here typically comes from an option parser with options that
1232 specify the "append" action.
1233
1234 Examples:
1235 >>> channel_dict_from_channel_list(["H1=LSC-STRAIN", "H2=SOMETHING-ELSE"]) # doctest: +SKIP
1236 {'H1': 'LSC-STRAIN', 'H2': 'SOMETHING-ELSE'}
1237 """
1238 return parse_list_to_dict(channel_list)
1239
1240
1241@admin.deprecated('replaced by parse_list_to_dict with key_is_range=True')
1242def channel_dict_from_channel_list_with_node_range(channel_list):
1243 """
1244 Given a list of channels with a range of mass bins, produce a dictionary
1245 keyed by ifo of channel names:
1246
1247 The list here typically comes from an option parser with options that
1248 specify the "append" action.
1249
1250 Examples:
1251 >>> channel_dict_from_channel_list_with_node_range(["0000:0002:H1=LSC_STRAIN_1,L1=LSC_STRAIN_2", "0002:0004:H1=LSC_STRAIN_3,L1=LSC_STRAIN_4", "0004:0006:H1=LSC_STRAIN_5,L1=LSC_STRAIN_6"]) # doctest: +SKIP
1252 {'0000': {'H1': 'LSC_STRAIN_1', 'L1': 'LSC_STRAIN_2'}, '0001': {'H1': 'LSC_STRAIN_1', 'L1': 'LSC_STRAIN_2'}, '0002': {'H1': 'LSC_STRAIN_3', 'L1': 'LSC_STRAIN_4'}, '0003': {'H1': 'LSC_STRAIN_3', 'L1': 'LSC_STRAIN_4'}, '0004': {'H1': 'LSC_STRAIN_5', 'L1': 'LSC_STRAIN_6'}, '0005': {'H1': 'LSC_STRAIN_5', 'L1': 'LSC_STRAIN_6'}}
1253 """
1254 outdict = {}
1255 for instrument_channel_full in channel_list:
1256 instrument_channel_split = instrument_channel_full.split(':')
1257 for ii in range(int(instrument_channel_split[0]), int(instrument_channel_split[1])):
1258 outdict[str(ii).zfill(4)] = dict((instrument_channel.split("=")) for instrument_channel in instrument_channel_split[2].split(','))
1259 return outdict
1260
1261
1262@admin.deprecated('replaced by ravel_dict_to_list with join_arg="channel-name" and filter_keys=ifos')
1263def pipeline_channel_list_from_channel_dict(channel_dict, ifos=None, opt="channel-name"):
1264 """
1265 Creates a string of channel names options from a dictionary keyed by ifos.
1266
1267 FIXME: This function exists to work around pipeline.py's inability to
1268 give the same option more than once by producing a string to pass as an argument
1269 that encodes the other instances of the option.
1270
1271 - override --channel-name with a different option by setting opt.
1272 - restrict the ifo keys to a subset of the channel_dict by
1273 setting ifos
1274
1275 Examples:
1276 >>> pipeline_channel_list_from_channel_dict({'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}) # doctest: +SKIP
1277 'H2=SOMETHING-ELSE --channel-name=H1=LSC-STRAIN '
1278
1279 >>> pipeline_channel_list_from_channel_dict({'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}, ifos=["H1"]) # doctest: +SKIP
1280 'H1=LSC-STRAIN '
1281
1282 >>> pipeline_channel_list_from_channel_dict({'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}, opt="test-string") # doctest: +SKIP
1283 'H2=SOMETHING-ELSE --test-string=H1=LSC-STRAIN '
1284 """
1285 outstr = ""
1286 if ifos is None:
1287 ifos = channel_dict.keys()
1288 for i, ifo in enumerate(ifos):
1289 if i == 0:
1290 outstr += "%s=%s " % (ifo, channel_dict[ifo])
1291 else:
1292 outstr += "--%s=%s=%s " % (opt, ifo, channel_dict[ifo])
1293
1294 return outstr
1295
1296
1297@admin.deprecated('replaced by ravel_dict_to_list with key_is_range=True, range_elem=node, join_arg="channel-name", filter_keys=ifos')
1298def pipeline_channel_list_from_channel_dict_with_node_range(channel_dict, node=0, ifos=None, opt="channel-name"):
1299 """
1300 Creates a string of channel names options from a dictionary keyed by ifos.
1301
1302 FIXME: This function exists to work around pipeline.py's inability to
1303 give the same option more than once by producing a string to pass as an argument
1304 that encodes the other instances of the option.
1305
1306 - override --channel-name with a different option by setting opt.
1307 - restrict the ifo keys to a subset of the channel_dict by.
1308 setting ifos
1309
1310 Examples:
1311 >>> pipeline_channel_list_from_channel_dict_with_node_range({'0000': {'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}}, node=0) # doctest: +SKIP
1312 'H2=SOMETHING-ELSE --channel-name=H1=LSC-STRAIN '
1313
1314 >>> pipeline_channel_list_from_channel_dict_with_node_range({'0000': {'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}}, node=0, ifos=["H1"]) # doctest: +SKIP
1315 'H1=LSC-STRAIN '
1316
1317 >>> pipeline_channel_list_from_channel_dict_with_node_range({'0000': {'H2': 'SOMETHING-ELSE', 'H1': 'LSC-STRAIN'}}, node=0, opt="test-string") # doctest: +SKIP
1318 'H2=SOMETHING-ELSE --test-string=H1=LSC-STRAIN '
1319 """
1320 outstr = ""
1321 node = str(node).zfill(4)
1322 if ifos is None:
1323 ifos = channel_dict[node].keys()
1324 for i, ifo in enumerate(ifos):
1325 if i == 0:
1326 outstr += "%s=%s " % (ifo, channel_dict[node][ifo])
1327 else:
1328 outstr += "--%s=%s=%s " % (opt, ifo, channel_dict[node][ifo])
1329
1330 return outstr
1331
1332
1333@admin.deprecated('replaced by parse_list_to_dict with key_is_range=True')
1334def injection_dict_from_channel_list_with_node_range(injection_list):
1335 """
1336 Given a list of injection xml files with a range of mass bins, produce a
1337 dictionary keyed by bin number:
1338
1339 The list here typically comes from an option parser with options that
1340 specify the "append" action.
1341
1342 Examples:
1343 >>> injection_dict_from_channel_list_with_node_range(["0000:0002:Injection_1.xml", "0002:0004:Injection_2.xml"]) # doctest: +SKIP
1344 {'0000': 'Injection_1.xml', '0001': 'Injection_1.xml', '0002': 'Injection_2.xml', '0003': 'Injection_2.xml'}
1345 """
1346 outdict = {}
1347 for injection_name in injection_list:
1348 injection_name_split = injection_name.split(':')
1349 for ii in range(int(injection_name_split[0]), int(injection_name_split[1])):
1350 outdict[str(ii).zfill(4)] = injection_name_split[2]
1351 return outdict
1352
1353
1354@admin.deprecated('replaced by combination of zip_dict_values and parse_list_to_dict with value_transform=parse_int')
1355def state_vector_on_off_dict_from_bit_lists(on_bit_list, off_bit_list, state_vector_on_off_dict=DEFAULT_STATE_VECTOR_ON_OFF):
1356 """
1357 Produce a dictionary (keyed by detector) of on / off bit tuples from a
1358 list provided on the command line.
1359
1360 Takes default values from module level datasource.state_vector_on_off_dict
1361 if state_vector_on_off_dict is not given
1362
1363 Inputs must be given as base 10 or 16 integers
1364
1365 Examples:
1366 >>> on_bit_list = ["V1=7", "H1=7", "L1=7"] # doctest: +SKIP
1367 >>> off_bit_list = ["V1=256", "H1=352", "L1=352"] # doctest: +SKIP
1368 >>> state_vector_on_off_dict_from_bit_lists(on_bit_list, off_bit_list) # doctest: +SKIP
1369 {'H1': [7, 352], 'H2': [7, 352], 'L1': [7, 352], 'V1': [7, 256]}
1370
1371 >>> state_vector_on_off_dict_from_bit_lists(on_bit_list, off_bit_list,{}) # doctest: +SKIP
1372 {'V1': [7, 256], 'H1': [7, 352], 'L1': [7, 352]}
1373
1374 >>> on_bit_list = ["V1=0x7", "H1=0x7", "L1=0x7"] # doctest: +SKIP
1375 >>> off_bit_list = ["V1=0x256", "H1=0x352", "L1=0x352"] # doctest: +SKIP
1376 >>> state_vector_on_off_dict_from_bit_lists(on_bit_list, off_bit_list,{}) # doctest: +SKIP
1377 {'V1': [7, 598], 'H1': [7, 850], 'L1': [7, 850]}
1378 """
1379 for ifo, bits in [line.strip().split("=", 1) for line in on_bit_list]:
1380 bits = int(bits, 16) if bits.startswith("0x") else int(bits)
1381 try:
1382 state_vector_on_off_dict[ifo][0] = bits
1383 except KeyError:
1384 state_vector_on_off_dict[ifo] = [bits, 0]
1385
1386 for ifo, bits in [line.strip().split("=", 1) for line in off_bit_list]:
1387 bits = int(bits, 16) if bits.startswith("0x") else int(bits)
1388 # shouldn't have to worry about key errors at this point
1389 state_vector_on_off_dict[ifo][1] = bits
1390
1391 return state_vector_on_off_dict
1392
1393
1394@admin.deprecated('replaced by combination of ravel_dict_to_list with unzip=True and join_arg=["state-vector-on-bits", "state-vector-off-bits"]')
1395def state_vector_on_off_list_from_bits_dict(bit_dict):
1396 """
1397 Produce a tuple of useful command lines from a dictionary of on / off state
1398 vector bits keyed by detector
1399
1400 FIXME: This function exists to work around pipeline.py's inability to
1401 give the same option more than once by producing a string to pass as an argument
1402 that encodes the other instances of the option.
1403
1404 Examples:
1405 >>> state_vector_on_off_dict = {"H1":[0x7, 0x160], "H2":[0x7, 0x160], "L1":[0x7, 0x160], "V1":[0x67, 0x100]} # doctest: +SKIP
1406 >>> state_vector_on_off_list_from_bits_dict(state_vector_on_off_dict) # doctest: +SKIP
1407 ('H1=7 --state-vector-on-bits=H2=7 --state-vector-on-bits=L1=7 --state-vector-on-bits=V1=103 ', 'H1=352 --state-vector-off-bits=H2=352 --state-vector-off-bits=L1=352 --state-vector-off-bits=V1=256 ')
1408 """
1409
1410 onstr = ""
1411 offstr = ""
1412 for i, ifo in enumerate(bit_dict):
1413 if i == 0:
1414 onstr += "%s=%s " % (ifo, bit_dict[ifo][0])
1415 offstr += "%s=%s " % (ifo, bit_dict[ifo][1])
1416 else:
1417 onstr += "--state-vector-on-bits=%s=%s " % (ifo, bit_dict[ifo][0])
1418 offstr += "--state-vector-off-bits=%s=%s " % (ifo, bit_dict[ifo][1])
1419
1420 return onstr, offstr
1421
1422
1423@admin.deprecated('replaced by parse_list_to_dict with value_transform=parse_host.')
1424def framexmit_dict_from_framexmit_list(framexmit_list):
1425 """
1426 Given a list of framexmit addresses with ports, produce a dictionary keyed by ifo:
1427
1428 The list here typically comes from an option parser with options that
1429 specify the "append" action.
1430
1431 Examples:
1432 >>> framexmit_dict_from_framexmit_list(["H1=224.3.2.1:7096", "L1=224.3.2.2:7097", "V1=224.3.2.3:7098"]) # doctest: +SKIP
1433 {'H1': ('224.3.2.1', 7096), 'L1': ('224.3.2.2', 7097), 'V1': ('224.3.2.3', 7098)}
1434 """
1435 out = []
1436 for instrument_addr in framexmit_list:
1437 ifo, addr_port = instrument_addr.split("=")
1438 addr, port = addr_port.split(':')
1439 out.append((ifo, (addr, int(port))))
1440 return dict(out)
1441
1442
1443@admin.deprecated('replaced by ravel_dict_to_list with value_transform=ravel_host.')
1444def framexmit_list_from_framexmit_dict(framexmit_dict, ifos=None, opt="framexmit-addr"):
1445 """
1446 Creates a string of framexmit address options from a dictionary keyed by ifos.
1447
1448 Examples:
1449 >>> framexmit_list_from_framexmit_dict({'V1': ('224.3.2.3', 7098), 'H1': ('224.3.2.1', 7096), 'L1': ('224.3.2.2', 7097)}) # doctest: +SKIP
1450 'V1=224.3.2.3:7098 --framexmit-addr=H1=224.3.2.1:7096 --framexmit-addr=L1=224.3.2.2:7097 '
1451 """
1452 outstr = ""
1453 if ifos is None:
1454 ifos = framexmit_dict.keys()
1455 for i, ifo in enumerate(ifos):
1456 if i == 0:
1457 outstr += "%s=%s:%s " % (ifo, framexmit_dict[ifo][0], framexmit_dict[ifo][1])
1458 else:
1459 outstr += "--%s=%s=%s:%s " % (opt, ifo, framexmit_dict[ifo][0], framexmit_dict[ifo][1])
1460
1461 return outstr
1462
1463
1464@admin.deprecated('replaced by parse_list_to_dict.')
1465def frame_type_dict_from_frame_type_list(frame_type_list):
1466 """
1467 Given a list of frame types, produce a dictionary keyed by ifo:
1468
1469 The list here typically comes from an option parser with options that
1470 specify the "append" action.
1471
1472 Examples:
1473 >>> frame_type_dict_from_frame_type_list(['H1=H1_GWOSC_O2_16KHZ_R1', 'L1=L1_GWOSC_O2_16KHZ_R1']) # doctest: +SKIP
1474 {'H1': 'H1_GWOSC_O2_16KHZ_R1', 'L1': 'L1_GWOSC_O2_16KHZ_R1'}
1475 """
1476 out = {}
1477 for frame_opt in frame_type_list:
1478 ifo, frame_type = frame_opt.split("=")
1479 out[ifo] = frame_type
1480
1481 return out
data_source
if no frame segments provided, set them to an empty segment list dictionary
__init__(self, Union[str, DataSource] data_source, Dict[Detector, str] channel_name, Union[int, LIGOTimeGPS] gps_start_time=None, Union[int, LIGOTimeGPS] gps_end_time=None, Dict[Detector, str] shared_memory_partition=None, Dict[Detector, str] shared_memory_dir=None, str frame_segments_name=None, Dict[Detector, str] state_vector_on_bits=None, Dict[Detector, str] state_vector_off_bits=None, str dq_vector_on_bits=None, str dq_vector_off_bits=None, Union[str, Path] frame_cache=None, Union[str, Path] injections=None, str nds_host=None, int nds_port=None, str nds_channel_type='online', int shared_memory_assumed_duration=4, int shared_memory_block_size=4096, Union[str, Path] frame_segments_file=None, int block_size=DEFAULT_BLOCK_SIZE, Dict[Detector, str] frame_type=None, Union[str or DataFindServer] data_find_server=None, str framexmit_addr=None, str framexmit_iface=None, Dict[Detector, str] state_channel_name=None, Dict[Detector, str] dq_channel_name=None, Dict[Detector, str] idq_channel_name=None, Dict[Detector, str] idq_state_channel_name=None)
from_optparse(optparse.Values options)
frame_segments
if no frame segments provided, set them to an empty segment list dictionary
_frame_cache_fileobj
create a temporary cache file
USEFUL CONSTANTS #.
Definition datasource.py:80