Source code for gwsumm.tabs.sei

# coding=utf-8
# Copyright (C) Duncan Macleod (2013)
#
# This file is part of GWSumm
#
# GWSumm is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GWSumm is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GWSumm.  If not, see <http://www.gnu.org/licenses/>

"""`SummaryTab` for seismic watchdog monitoring
"""

import os
import re
from collections import OrderedDict
from configparser import NoOptionError

from dateutil import tz

import numpy

from MarkupPy import markup

from gwpy.time import Time

from gwdetchar.io import html

from .registry import (get_tab, register_tab)
from .. import globalv
from ..config import GWSummConfigParser
from ..channels import get_channel
from ..data import get_timeseries_dict
from ..plot.registry import get_plot
from ..utils import vprint

__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>'
__all__ = ['SEIWatchDogTab']

base = get_tab('default')
HAMs = ['HAM%d' % i for i in range(1, 7)]
BSCs = ['BS', 'ITMX', 'ITMY', 'ETMX', 'ETMY']

HEPI_GPS_CHANNEL = '%s:HPI-%s_WD_MON_GPS_TIME'
HEPI_LATCH_CHANNEL = '%s:HPI-%s_WD_MON_FIRSTTRIG_LATCH'

HAM_ISI_GPS_CHANNEL = '%s:ISI-%s_WD_MON_GPS_TIME'
HAM_ISI_LATCH_CHANNEL = '%s:ISI-%s_WD_MON_FIRSTTRIG_LATCH'
BSC_ST1_GPS_CHANNEL = '%s:ISI-%s_ST1_WD_MON_GPS_TIME'
BSC_ST1_LATCH_CHANNEL = '%s:ISI-%s_ST1_WD_MON_FIRSTTRIG_LATCH'
BSC_ST2_GPS_CHANNEL = '%s:ISI-%s_ST2_WD_MON_GPS_TIME'
BSC_ST2_LATCH_CHANNEL = '%s:ISI-%s_ST2_WD_MON_FIRSTTRIG_LATCH'

re_no_count = re.compile(r'(ISI (.*)IOP|(.*) Reset|(.*)from stage \d+)')


[docs] class SEIWatchDogTab(base): """Summarise the WatchDog trips recorded from the SEI system. """ type = 'seismic-watchdog' window = 5
[docs] @classmethod def from_ini(cls, config, section, plotdir='plots', **kwargs): """Define a new `SEIWatchDogTab`. """ chambers = config.get(section, 'chamber-type') new = super(SEIWatchDogTab, cls).from_ini(config, section, plotdir=plotdir, **kwargs) new.ifo = config.get('DEFAULT', 'IFO') new.plotdir = plotdir chamber = str(chambers).upper() if chamber == 'HAM': new.chambers = HAMs elif chamber == 'BSC': new.chambers = BSCs else: raise ValueError("Cannot process chamber-type = '%s'" % chamber) try: new.plot_duration = config.getint(section, 'plot-duration') except NoOptionError: new.plot_duration = 30 return new
[docs] def process(self, nds=None, nproc=1, config=GWSummConfigParser(), datacache=None, trigcache=None, datafind_error='raise', **kwargs): """Process data for the given state. """ ifo = self.ifo for p in self.plots: if p.outputfile in globalv.WRITTEN_PLOTS: p.new = False # -------------------------------------------------------------------- # work out which channels are needed tschannels = [HEPI_GPS_CHANNEL % (ifo, chamber) for chamber in self.chambers] svchannels = [HEPI_LATCH_CHANNEL % (ifo, chamber) for chamber in self.chambers] if self.chambers == HAMs: tschannels.extend([HAM_ISI_GPS_CHANNEL % (ifo, chamber) for chamber in self.chambers[1:]]) svchannels.extend([HAM_ISI_LATCH_CHANNEL % (ifo, chamber) for chamber in self.chambers[1:]]) else: tschannels.extend([BSC_ST1_GPS_CHANNEL % (ifo, chamber) for chamber in self.chambers]) svchannels.extend([BSC_ST1_LATCH_CHANNEL % (ifo, chamber) for chamber in self.chambers]) tschannels.extend([BSC_ST2_GPS_CHANNEL % (ifo, chamber) for chamber in self.chambers]) svchannels.extend([BSC_ST2_LATCH_CHANNEL % (ifo, chamber) for chamber in self.chambers]) state = sorted(self.states, key=lambda s: abs(s.active))[0] vprint(" %d channels identified for TimeSeries\n" % len(tschannels)) tripdata = get_timeseries_dict(tschannels, state, config=config, nds=nds, nproc=nproc, datafind_error=datafind_error, cache=datacache) vprint(" All time-series data loaded\n") vprint(" %d channels identified as StateVectors\n" % len(svchannels)) latchdata = get_timeseries_dict(svchannels, state, config=config, nds=nds, nproc=nproc, datafind_error=datafind_error, statevector=True, cache=datacache) vprint(" All state-vector data loaded\n") # -------------------------------------------------------------------- # find the trips self.trips = [] for (gpschannel, latch) in zip(tschannels, svchannels): # get channel data gpschannel = get_channel(gpschannel) latch = get_channel(latch) chamber = gpschannel.subsystem system = gpschannel.system vprint(" Locating WD trips for %s %s %s...\n" % (ifo, chamber, system)) # find times of a trip trips = [] for i, ts in enumerate(tripdata[gpschannel.name]): alltrips = ts.times[((numpy.diff(ts.value) > 0) & (ts.value[1:] > 1e8)).nonzero()[0] + 1] for j, gpstime in enumerate(alltrips.value): trips.append((i, gpstime)) vprint(" Found %d WD trips.\n" % len(trips)) # associate cause for i, trip in enumerate(trips): tsid, t = trip gpstime = int(t) # extract 1 second of LATCH data ldata = latchdata[latch.name][tsid].crop(gpstime, gpstime + 1).value # find transition value try: bits = ldata[sorted(numpy.unique(ldata, return_index=True)[1])][1] except IndexError: bits = ldata[0] except (IndexError, ValueError): bits = None # associate cause if not bits: if re.match(r'ST\d', latch.signal): stage = 'ISI %s' % latch.signal.split('_')[0] else: stage = system causes = ['%s Unknown' % stage] else: allbits = numpy.nonzero( map(int, bin(int(bits))[2:][::-1]), )[0] causes = [latch.bits[b] for b in allbits] t2 = Time(t, format='gps', scale='utc') vprint(" Trip GPS %s (%s), triggers:\n" % (t, t2.iso)) for cause in causes: vprint(" %s\n" % cause) # configure plot mapsec = 'sei-wd-map-%s' % cause if (not config.has_section(mapsec) and re.match(r'ISI ST\d ', cause)): mapsec = ('sei-wd-map-%s' % (' '.join(cause.split(' ', 2)[::2]))) if config.has_section(mapsec): pstart = gpstime - self.plot_duration / 2. if self.chambers == HAMs or system == 'HPI': platform = chamber else: platform = '%s_%s' % ( chamber, gpschannel.signal.split('_')[0]) p = os.path.join(self.plotdir, '%s-%s_%s_WATCHDOG_TRIP-%d-%d.png' % (ifo, system, platform, pstart, self.plot_duration)) self.plots.append(get_plot('watchdog')( t, chamber, cause, config, p, ifo=ifo, nds=nds is True or False)) plot = self.plots[-1] else: plot = None self.trips.append((t, chamber, cause, plot)) super(SEIWatchDogTab, self).process( config=config, nds=nds, nproc=nproc, datacache=datacache, trigcache=trigcache, **kwargs)
[docs] def write_state_html(self, state): """Build HTML summary of watchdog trips """ # find one example of each channel, and get the bits hepichannel = get_channel(HEPI_LATCH_CHANNEL % (self.ifo, self.chambers[1])) hepimask = hepichannel.bits + ['HEPI Unknown'] if self.chambers == HAMs: isichannels = [get_channel(HAM_ISI_LATCH_CHANNEL % (self.ifo, self.chambers[0]))] isimask = isichannels[0].bits + ['ISI Unknown'] else: isichannels = [get_channel(BSC_ST1_LATCH_CHANNEL % (self.ifo, self.chambers[0])), get_channel(BSC_ST2_LATCH_CHANNEL % (self.ifo, self.chambers[0]))] isimask = (isichannels[0].bits + ['ISI ST1 Unknown'] + isichannels[1].bits + ['ISI ST2 Unknown']) mask = hepimask + isimask # count trips count = {} for _, chamber, trigger, _ in self.trips: key = (chamber, trigger) try: count[key] += 1 except KeyError: count[key] = 1 page = markup.page() # build summary table page.div(class_='card card-body border-light shadow-sm') chambertype = self.chambers[0][:-1] id_ = '{}-{}'.format(self.ifo.lower(), chambertype.lower()) page.table( class_='table table-sm table-hover watchdog', id_=id_) page.caption("Number of watch-dog trips per %s chamber (column) and " "trigger (row)" % (chambertype)) page.thead() # add headers page.tr(class_='header') for th in ['WD'] + self.chambers + ['Sub-total']: page.th(th) page.tr.close() page.thead.close() # add rows page.tbody() totals = numpy.zeros((len(mask), len(self.chambers) + 1), dtype=int) rows = [] for i, bit in enumerate(mask): class_ = [] if (i == len(hepimask) or i == len(hepimask + isichannels[0].bits)): class_.append('tdiv') if re_no_count.match(bit): class_.append('ignore') if class_: page.tr(class_=' '.join(class_)) else: page.tr() page.th(bit) for j, chamber in enumerate(self.chambers): try: c = count[(chamber, bit)] except KeyError: c = 0 pass page.td(c or '-') # exclude IOP from total if not re_no_count.match(bit): totals[i][j] = c # add row total totals[i][-1] = totals[i].sum() page.th(str(totals[i][-1])) page.tr.close() page.tbody.close() # add totals page.thead() page.tr(class_='header') page.th('Totals') for i in range(totals.shape[1]): page.th(str(totals[:, i].sum())) page.tr.close() page.thead.close() page.table.close() page.button( 'Export to CSV', class_='btn btn-outline-secondary btn-table mt-2', **{'data-table-id': id_, 'data-filename': '%s.csv' % id_}) page.div.close() # build trip groups self.trips.sort( key=lambda x: (x[0], x[2] in mask and (mask).index(x[2]) or 1000, x[3] is None)) groups = OrderedDict() j = 0 for i in range(len(self.trips)): if i == 0: j = i groups[j] = [] continue t = self.trips[i][0] t2 = self.trips[i-1][0] if (t - t2) < self.window: groups[j].append(i) else: j = i groups[j] = [] # build trip table page.h1('Trip list', class_='mt-4') page.div(class_='card card-body border-light shadow-sm') utc = tz.gettz('UTC') if self.ifo in ['H1', 'C1', 'P1']: localzone = tz.gettz('America/Los_Angeles') elif self.ifo in ['L1']: localzone = tz.gettz('America/Chicago') elif self.ifo in ['K1']: localzone = tz.gettz('Asia/Tokyo') else: localzone = tz.gettz('Europe/Berlin') headers = ['GPS time', 'UTC time', 'Local time', 'Chamber', 'Trigger', 'Plot', 'Associated'] rows = [] for id in groups: t, chamber, trigger, plot = self.trips[id] t2 = Time(t, format='gps', scale='utc') tlocal = Time( t2.datetime.replace(tzinfo=utc).astimezone(localzone), format='datetime', scale='utc') rows.append([t, t2.iso, tlocal.iso, chamber, trigger]) if plot: rows[-1].append(markup.oneliner.a( '[Click here]', href=plot.href, class_='fancybox plot', **{'data-fancybox-group': '1'})) else: rows[-1].append('-') assoc = [] for id2 in groups[id]: t2, chamber2, trigger2, plot2 = self.trips[id2] dt = t2 - t tag = '%s %s (+%.2fs)' % (chamber2, trigger2, dt) if plot2: assoc.append(markup.oneliner.a( tag, href=plot2.href, class_='fancybox plot', **{'data-fancybox-group': '1'} )) else: assoc.append(tag) if assoc: rows[-1].append('<br>'.join(assoc)) else: rows[-1].append('-') page.add(str(html.table( headers, rows, caption=('List of %s watch-dog trips in interval [%d .. %d) - ' 'trips are considered \'associated\' if they fall within ' '%s seconds of each other.' % (chambertype, self.start, self.end, self.window))))) wdp = [] for i, p in enumerate(self.plots): if 'WATCHDOG_TRIP' in p.href: wdp.append(i) for idx in wdp[::-1]: self.plots.pop(idx) # write trips to data file tripfile = os.path.join(self.path, 'trips.dat') with open(tripfile, 'w') as f: for id in groups: t, chamber, cause, _ = self.trips[id] if cause in hepimask: stage = 'HEPI' elif self.chambers == HAMs: stage = 'ISI' elif cause in isichannels[0].bits: stage = 'ISI1' else: stage = 'ISI2' cause = cause.replace(' ', '_') print(t, chamber, stage, cause, file=f) page.p() page.add('The list of trips can be downloaded ') page.a('here.', href=tripfile, alt=os.path.basename(tripfile), title='Trip data') page.p.close() page.div.close() # write to file idx = self.states.index(state) with open(self.frames[idx], 'w') as fobj: fobj.write(str(page)) return self.frames[idx]
register_tab(SEIWatchDogTab)