def run_alarm(config, T0): time.sleep(config.latency) SCNL = DataFrame.from_dict(config.SCNL) lvlv = np.array(SCNL['value']) scnl = SCNL['scnl'].tolist() stas = [sta.split('.')[0] for sta in scnl] t1 = T0 - config.duration t2 = T0 st = utils.grab_data(scnl, t1, t2, fill_value=0) #### preprocess data #### st.detrend('demean') st.taper(max_percentage=None, max_length=config.taper_val) st.filter('bandpass', freqmin=config.f1, freqmax=config.f2) #### calculate rsam #### rms = np.array([np.sqrt(np.mean(np.square(tr.data))) for tr in st]) ############################# Icinga message ############################# state_message = ''.join('{}: {:.0f}/{}, '.format(sta, rms[i], lvlv[i]) for i, sta in enumerate(stas[:-1])) state_message = ''.join([ state_message, 'Arrestor ({}): {:.0f}/{}'.format(stas[-1], rms[-1], lvlv[-1]) ]) ########################################################################### if (rms[-1] < lvlv[-1]) & (sum(rms[:-1] > lvlv[:-1]) >= config.min_sta): #### RSAM Detection!! #### ########################## print('********** DETECTION **********') state_message = '{} (UTC) RSAM detection! {}'.format( T0.strftime('%Y-%m-%d %H:%M'), state_message) state = 'CRITICAL' #### Generate Figure #### start = time.time() try: filename = make_figure(scnl, T0, config.alarm_name) except: filename = None ### Send Email Notification #### craft_and_send_email(t1, t2, stas, rms, lvlv, config.alarm_name, filename) end = time.time() print('{:.2f} seconds to make figure & send email.'.format(end - start)) # elif (rms[-1] < lvlv[-1]) & (sum(rms[:-1] > lvlv[:-1] / 2) >= config.min_sta): #### elevated RSAM #### ####################### state_message = '{} (UTC) RSAM elevated! {}'.format( T0.strftime('%Y-%m-%d %H:%M'), state_message) state = 'WARNING' # elif sum(rms[:-1] != 0) < config.min_sta: #### not enough data #### ######################### state_message = '{} (UTC) data missing! {}'.format( T0.strftime('%Y-%m-%d %H:%M'), state_message) state = 'WARNING' # elif (rms[-1] >= lvlv[-1]) & (sum(rms[:-1] > lvlv[:-1]) >= config.min_sta): ### RSAM arrested ### ##################### state_message = '{} (UTC) RSAM normal (arrested). {}'.format( T0.strftime('%Y-%m-%d %H:%M'), state_message) state = 'WARNING' # else: #### RSAM normal #### ##################### state_message = '{} (UTC) RSAM normal. {}'.format( T0.strftime('%Y-%m-%d %H:%M'), state_message) state = 'OK' # send heartbeat status message to icinga utils.icinga_state(config.alarm_name, state, state_message)
def make_figure(scnl, T0, alarm_name): import matplotlib as m m.use('Agg') import matplotlib.pyplot as plt import matplotlib.cm as cm from matplotlib.colors import LinearSegmentedColormap from PIL import Image #### grab data #### start = time.time() st = utils.grab_data(scnl, T0 - 3600, T0, fill_value='interpolate') end = time.time() print('{:.2f} seconds to grab figure data.'.format(end - start)) #### preprocess data #### st.detrend('demean') [ tr.decimate(2, no_filter=True) for tr in st if tr.stats.sampling_rate == 100 ] [ tr.decimate(2, no_filter=True) for tr in st if tr.stats.sampling_rate == 50 ] [tr.resample(25) for tr in st if tr.stats.sampling_rate != 25] colors = cm.jet(np.linspace(-1, 1.2, 256)) color_map = LinearSegmentedColormap.from_list('Upper Half', colors) plt.figure(figsize=(4.5, 4.5)) for i, tr in enumerate(st): ax = plt.subplot(len(st), 1, i + 1) tr.spectrogram(title='', log=False, samp_rate=25, dbscale=True, per_lap=0.5, mult=25.0, wlen=6, cmap=color_map, axes=ax) ax.set_yticks([3, 6, 9, 12]) ax.set_ylabel(tr.stats.station + '\n' + tr.stats.channel, fontsize=5, rotation='horizontal', multialignment='center', horizontalalignment='right', verticalalignment='center') ax.yaxis.set_ticks_position('right') ax.tick_params('y', labelsize=4) if i == 0: ax.set_title(alarm_name + ' Alarm') if i < len(st) - 1: ax.set_xticks([]) else: d_sec = np.linspace(0, 3600, 7) ax.set_xticks(d_sec) T = [tr.stats.starttime + dt for dt in d_sec] ax.set_xticklabels([t.strftime('%H:%M') for t in T]) ax.tick_params('x', labelsize=5) ax.set_xlabel(tr.stats.starttime.strftime('%Y-%m-%d') + ' UTC') plt.subplots_adjust(left=0.08, right=.94, top=0.92, bottom=0.1, hspace=0.1) filename = utils.tmp_figure_dir + '/' + UTCDateTime.utcnow().strftime( '%Y%m%d_%H%M%S_%f') plt.savefig(filename, dpi=250, format='png') im = Image.open(filename) remove(filename) filename = filename + '.jpg' im.save(filename) return filename
'0') # round down to the nearest 10-minute except: warnings.warn( 'Needs end-time argument. eg: array_processing.py 201701020205') sys.exit() t1 = T0 - config.duration t2 = T0 print('{} - {}'.format(t1.strftime('%Y.%m.%d %H:%M'), t2.strftime('%Y.%m.%d %H:%M'))) for array in config.arrays: print('--- ' + array['Name'] + ' ---') #### download data #### SCNL = DataFrame.from_dict(array['SCNL']) st = utils.grab_data(SCNL['scnl'].tolist(), t1 - config.latency, t2 + config.latency + config.window_length, fill_value=0) st = utils.add_coordinate_info(st, SCNL) array = utils.get_volcano_backazimuth(st, array) ######################## #### check for enough data #### for tr in st: if np.sum(np.abs(tr.data)) == 0: st.remove(tr) if len(st) < config.min_chan: print('Too many blank traces. Skipping.') continue ######################## #### check for gappy data ####
STALTA_SEC = [3, 8] # Timeseries plot variables TSPLOTW = 900 TSPLOTH = 200 TSTOOLS = 'pan,reset' # Trigger settings ntriggersta = 2 # number of required channels w a coincident detection for a trigger ##################### # Initialize data download and cft calculation st = utils.grab_data(settings['server'], settings['port'], settings['scnl'], UTCDateTime(settings['startstop'][0]), UTCDateTime(settings['startstop'][1])) from obspy.signal.trigger import coincidence_trigger from obspy.signal.trigger import classic_sta_lta cft = classic_sta_lta(st[0].data, int(3 * st[0].stats.sampling_rate), int(8 * st[0].stats.sampling_rate)) # set up widgets ticker_alg = Select(value=list(STALTA_ALGORITHMS.keys())[0], options=list(STALTA_ALGORITHMS.keys())) stalta_slider = RangeSlider(start=1, end=15, value=(3, 8),
def make_figure(st, volcano, T0, config, mx_pressure): import matplotlib as m m.use('Agg') import matplotlib.pyplot as plt import matplotlib.cm as cm from matplotlib.colors import LinearSegmentedColormap from PIL import Image import matplotlib.dates as mdates start = time.time() ##### get seismic data ##### seis = utils.grab_data(volcano['seismic_scnl'], T0 - 3600, T0, fill_value='interpolate') ##### get infrasound data ##### infra_scnl = [ '{}.{}.{}.{}'.format(tr.stats.station, tr.stats.channel, tr.stats.network, tr.stats.location) for tr in st ] infra = utils.grab_data(infra_scnl, T0 - 600, T0, fill_value='interpolate') end = time.time() print('{:.2f} seconds to grab figure data.'.format(end - start)) ################################################### ################# plot infrasound ################# #### preprocess data #### infra.detrend('demean') infra.taper(max_percentage=None, max_length=config.taper_val) infra.filter('bandpass', freqmin=config.f1, freqmax=config.f2) [ tr.decimate(2, no_filter=True) for tr in infra if tr.stats.sampling_rate == 100 ] [ tr.decimate(2, no_filter=True) for tr in infra if tr.stats.sampling_rate == 50 ] [tr.resample(25) for tr in infra if tr.stats.sampling_rate != 25] ##### stack infrasound data ##### print('stacking infrasound data') stack = xcorr_align_stream(infra, config) ##### plot stack spectrogram ##### plt.figure(figsize=(4.5, 4.5)) colors = cm.jet(np.linspace(-1, 1.2, 256)) color_map = LinearSegmentedColormap.from_list('Upper Half', colors) ax = plt.subplot(len(seis) + 3, 1, 1) ax.set_title(config.alarm_name + ' Alarm: ' + volcano['volcano'] + ' detection!') print(np.max(stack.data)) stack.spectrogram(title='', log=False, samp_rate=25, dbscale=True, per_lap=0.7, mult=25.0, wlen=3, cmap=color_map, axes=ax) ax.set_yticks([3, 6, 9, 12]) ax.set_ylim(0, 12.5) ax.set_ylabel(stack.stats.station + '\nstack', fontsize=5, rotation='horizontal', multialignment='center', horizontalalignment='right', verticalalignment='center') ax.yaxis.set_ticks_position('right') ax.tick_params('y', labelsize=4) ax.set_xticks([]) ##### plot stack trace ##### ax = plt.subplot(len(seis) + 3, 1, 2) t1 = mdates.date2num(infra[0].stats.starttime.datetime) t1 = round(t1 * 24 * 60) / (24 * 60) # round to nearest minute t2 = mdates.date2num(infra[0].stats.endtime.datetime) t2 = round(t2 * 24 * 60) / (24 * 60) # round to nearest minute t_vector = np.linspace(t1, t2, stack.stats.npts) plt.plot(t_vector, stack.data, color='k', LineWidth=0.2) ax.set_ylabel(stack.stats.station + '\nstack', fontsize=5, rotation='horizontal', multialignment='center', horizontalalignment='right', verticalalignment='center') ax.yaxis.set_ticks_position('right') ax.tick_params('y', labelsize=4) ax.set_xlim(t1, t2) t_ticks = np.linspace(t1, t2, 6) ax.set_xticks(t_ticks) ax.set_xticklabels([mdates.num2date(t).strftime('%H:%M') for t in t_ticks]) ax.tick_params('x', labelsize=5) ax.set_xlabel( '{:.0f} Minute Infrasound Stack\n{} UTC, Peak Pressure: {:.1f} Pa'. format(round((t2 - t1) * 24 * 60), tr.stats.starttime.strftime('%Y-%b-%d'), mx_pressure)) ################################################### ################################################### ################################################### ################## plot seismic ################### #### preprocess data #### seis.detrend('demean') [ tr.decimate(2, no_filter=True) for tr in seis if tr.stats.sampling_rate == 100 ] [ tr.decimate(2, no_filter=True) for tr in seis if tr.stats.sampling_rate == 50 ] [tr.resample(25) for tr in seis if tr.stats.sampling_rate != 25] for i, tr in enumerate(seis): ax = plt.subplot(len(seis) + 3, 1, i + 1 + 3) tr.spectrogram(title='', log=False, samp_rate=25, dbscale=True, per_lap=0.5, mult=25.0, wlen=6, cmap=color_map, axes=ax) ax.set_yticks([3, 6, 9, 12]) ax.set_ylabel(tr.stats.station + '\n' + tr.stats.channel, fontsize=5, rotation='horizontal', multialignment='center', horizontalalignment='right', verticalalignment='center') ax.yaxis.set_ticks_position('right') ax.tick_params('y', labelsize=4) if i != len(seis) - 1: ax.set_xticks([]) else: d_sec = np.linspace(0, 3600, 7) ax.set_xticks(d_sec) T = [tr.stats.starttime + dt for dt in d_sec] ax.set_xticklabels([t.strftime('%H:%M') for t in T]) ax.tick_params('x', labelsize=5) ax.set_xlabel('{:.0f} Minute Local Seismic Data'.format( round(tr.stats.endtime - tr.stats.starttime) / 60)) ################################################### ################################################### plt.subplots_adjust(left=0.08, right=.94, top=0.92, bottom=0.1, hspace=0.1) filename = utils.tmp_figure_dir + '/' + UTCDateTime.utcnow().strftime( '%Y%m%d_%H%M%S_%f') plt.savefig(filename, dpi=250, format='png') im = Image.open(filename) remove(filename) filename = filename + '.jpg' im.save(filename) return filename
def run_alarm(config, T0): time.sleep(config.latency) state_message = '{} (UTC) {}'.format(T0.strftime('%Y-%m-%d %H:%M'), config.alarm_name) #### download data #### SCNL = DataFrame.from_dict(config.SCNL) t1 = T0 - config.duration t2 = T0 st = utils.grab_data(SCNL['scnl'].tolist(), t1, t2, fill_value=0) st = add_coordinate_info(st, SCNL) ######################## #### check for enough data #### for tr in st: if np.sum(np.abs(np.abs(tr.data))) == 0: st.remove(tr) if len(st) < config.min_chan: state_message = '{} - Not enough channels!'.format(state_message) state = 'WARNING' utils.icinga_state(config.alarm_name, state, state_message) return ######################## #### check for gappy data #### for tr in st: num_zeros = len(np.where(tr.data == 0)[0]) if num_zeros / float(tr.stats.npts) > 0.01: st.remove(tr) if len(st) < config.min_chan: state_message = '{} - Gappy data!'.format(state_message) state = 'WARNING' utils.icinga_state(config.alarm_name, state, state_message) return ######################## #### preprocess data #### st.detrend('demean') st.taper(max_percentage=None, max_length=config.taper_val) st.filter('bandpass', freqmin=config.f1, freqmax=config.f2) for tr in st: if tr.stats['sampling_rate'] == 100: tr.decimate(2) if tr.stats['sampling_rate'] != 50: tr.resample(50.0) ######################## #### check amplitude threshold #### min_pa = np.array([v['min_pa'] for v in config.VOLCANO]).min() st = Stream( [tr for tr in st if np.any(np.abs(tr.data * config.digouti) > min_pa)]) if len(st) < config.min_chan: state_message = '{} - not enough channels exceeding amplitude threshold!'.format( state_message) state = 'OK' utils.icinga_state(config.alarm_name, state, state_message) return ######################## #### Set up grid #### config = get_volcano_backazimuth(st, config) yx, intsd, ints_az = setup_coordinate_system(st) #### Cross correlate #### lags, lags_inds1, lags_inds2 = calc_triggers(st, config, intsd) cmbm2, cmbm2n, counter, mpk = associator(lags_inds1, lags_inds2, st, config) if counter == 0: state_message = '{} - alarm normal.'.format(state_message) state = 'OK' else: #### some event detected...determine velocity and azimuth #### velocity, azimuth, rms = inversion(cmbm2n, cmbm2, intsd, ints_az, lags_inds1, lags_inds2, lags, mpk) d_Azimuth = azimuth - np.array( [t['back_azimuth'] for t in config.VOLCANO]) az_tolerance = np.array( [t['Azimuth_tolerance'] for t in config.VOLCANO]) #### check if this is airwave velocity from a volcano in config file list #### if np.any(np.abs(d_Azimuth) < az_tolerance): v_ind = np.argmax(np.abs(d_Azimuth) < az_tolerance) mx_pressure = np.max(np.array([tr.data for tr in st])) * config.digouti if config.VOLCANO[v_ind]['vmin'] < velocity < config.VOLCANO[v_ind][ 'vmax'] and mx_pressure > config.VOLCANO[v_ind]['min_pa']: #### DETECTION #### volcano = config.VOLCANO[v_ind] d_Azimuth = d_Azimuth[v_ind] print('Airwave Detection!!!') state_message = '{} - {} detection! {:.1f} Pa peak pressure'.format( state_message, volcano['volcano'], mx_pressure) state = 'CRITICAL' try: filename = make_figure(st, volcano, T0, config, mx_pressure) except: filename = None craft_and_send_email(t1, t2, config, volcano, d_Azimuth, velocity, mx_pressure, filename) else: print('Non-volcano detect!!!') state_message = '{} - Detection with wrong velocity ({:.1f} km/s) or maximum pressure ({:.1f} Pa)'.format( state_message, velocity, mx_pressure) state = 'WARNING' else: #### trigger, but not from volcano #### print('Non-volcano detect!!!') state_message = '{} - Detection with wrong backazimuth ({:.0f} from N)'.format( state_message, azimuth) state = 'WARNING' # send heartbeat status message to icinga utils.icinga_state(config.alarm_name, state, state_message)