Nothing
##
## S4 classes for handling seismic traces
##
## Copyright (C) 2012 Mazama Science, Inc.
## by Jonathan Callahan, jonathan@mazamascience.com
##
## This program 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 2 of the License, or
## (at your option) any later version.
##
## This program 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 this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
################################################################################
# R classes for a Trace obj.
#
# This is a port of some of the functionality found in obspy.core.trace.py
#
# http://docs.obspy.org/packages/autogen/obspy.core.trace.Trace.html
################################################################################
################################################################################
# Class TraceHeader
#
# A container for additional header information for each Trace object.
#
# A TraceHeader object contains all header information (aka metadata)
# associated with a Trace object.
#
# Documentation for attributes copied verbatim from obspy.core.trace.py:
#
# ``sampling_rate`` : float, optional
# Sampling rate in hertz (default value is 1.0).
# ``delta`` : float, optional
# Sample distance in seconds (default value is 1.0).
# ``calib`` : float, optional
# Calibration factor (default value is 1.0).
# ``npts`` : int, optional
# Number of sample points (default value is 0, which implies that no data
# is present).
# ``network`` : string, optional
# Network code (default is an empty string).
# ``location`` : string, optional
# Location code (default is an empty string).
# ``station`` : string, optional
# Station code (default is an empty string).
# ``channel`` : string, optional
# Channel code (default is an empty string).
# ``starttime`` : :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
# Date and time of the first data sample given in UTC (default value is
# "1970-01-01T00:00:00.0Z").
# ``endtime`` : :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
# Date and time of the last data sample given in UTC
# (default value is "1970-01-01T00:00:00.0Z").
#
# It can also contain some optional metadata, not included in miniseed headers:
#
# ``latitude`` : numeric, optional
# ``longitude`` : numeric, optional
# ``elevation`` : numeric, optional
# ``depth`` : numeric, optional
# ``azimuth`` : numeric, optional
# ``dip`` : numeric, optional
#
################################################################################
setClass("TraceHeader",
# typed slots (aka attributes) for class TraceHeader
representation(sampling_rate = "numeric",
delta = "numeric",
calib = "numeric",
npts = "integer",
network = "character",
location = "character",
station = "character",
channel = "character",
quality = "character",
starttime = "POSIXct",
endtime = "POSIXct",
latitude = "numeric",
longitude = "numeric",
elevation = "numeric",
depth = "numeric",
azimuth = "numeric",
dip= "numeric",
processing = "character"),
# default values for slots
prototype(sampling_rate = 1.0,
delta = 1.0,
calib = 1.0,
npts = as.integer(0),
network = "",
location = "",
station = "",
channel = "",
quality = "X",
starttime = as.POSIXct("1900-01-01T00:00:00",format="%Y-%m-%dT%H:%M:%OS6", tz="GMT"),
endtime = as.POSIXct("1900-01-01T00:00:00",format="%Y-%m-%dT%H:%M:%OS6", tz="GMT"),
latitude = as.numeric(NA),
longitude = as.numeric(NA),
elevation = as.numeric(NA),
depth = as.numeric(NA),
azimuth = as.numeric(NA),
dip= as.numeric(NA))
)
################################################################################
#
# A TraceHeader can be initialized with a headerLine as returned by the IRIS DMC timeseries webservice:
#
# TIMESERIES LD_POTS__HHZ_M, 351 samples, 100.503 sps, 2012-01-29T00:00:00.006000, SLIST, INTEGER, COUNTS
#
# or with named elements from a headerList. Items in the headerList will override items determined
# by parsing the headerLine.
################################################################################
# TODO: I am currently using "Trace" to mean what libmseed calls a "Trace Segment".
# TODO: Each libmseed "Trace" has both "numsamples" and "samplecnt".
# TODO: Need to figure out what the difference is and when to bail with zero samples in the SNCL.
# TODO: Zero samples in a "Trace Segment" is not necessarily a deal breaker.
setMethod("initialize", "TraceHeader",
function(.Object,
headerList=list(),
headerLine=NULL,
...) {
# Begin by parsing any headerLine that was passed in.
if (!is.null(headerLine)) {
headerValues <- strsplit(headerLine,", ")[[1]]
sncl <- strsplit(headerValues[1]," ")[[1]][2]
snclValues <- strsplit(sncl,"_")[[1]]
.Object@network <- snclValues[1]
.Object@station <- snclValues[2]
.Object@location <- snclValues[3]
.Object@channel <- snclValues[4]
.Object@npts <- as.integer(strsplit(headerValues[2]," ")[[1]][1])
.Object@sampling_rate <- as.numeric(strsplit(headerValues[3]," ")[[1]][1])
.Object@starttime <- as.POSIXct(headerValues[4], format="%Y-%m-%dT%H:%M:%OS6", tz="GMT") # header uses 'T' format
.Object@latitude <- as.numeric(NA)
.Object@longitude <- as.numeric(NA)
.Object@elevation <- as.numeric(NA)
.Object@depth <- as.numeric(NA)
.Object@azimuth <- as.numeric(NA)
.Object@dip <- as.numeric(NA)
}
# override with anything found in the headerList
for (key in names(headerList)) {
slot(.Object,key) = headerList[[key]]
}
# guarantee npts and sampling_rate are non-zero numbers
if (is.na(.Object@npts)) {
err_msg <- paste("initialize.TraceHeader: npts=", .Object@npts, sep="")
if (!is.null(headerLine)) {
err_msg <- paste(err_msg, "\nheaderLine =\"", headerLine, "\"", sep="")
}
stop(err_msg)
}
if (is.na(.Object@sampling_rate) || (.Object@sampling_rate == 0)) {
err_msg <- paste("initialize.TraceHeader: sampling_rate=", .Object@sampling_rate, sep="")
if (!is.null(headerLine)) {
err_msg <- paste(err_msg, "\nheaderLine =\"", headerLine, "\"", sep="")
}
stop(err_msg)
}
# calculate derived values
delta = 1.0 / .Object@sampling_rate
if (!is.finite(delta)) {
delta = 0
}
.Object@delta = delta
if (.Object@npts == 0) {
delta = 0
} else {
delta = (.Object@npts-1) / .Object@sampling_rate
if (!is.finite(delta))
delta = 0
}
if (is.na(.Object@endtime)) {
.Object@endtime = .Object@starttime + delta
}
# return the newly created .Object
return(.Object)
}
)
# show method is called by show() and print() ----------------------------------
show.TraceHeader <- function(object) {
cat ("Seismic Trace TraceHeader \n")
cat (" Network: " , object@network, "\n")
cat (" Station: " , object@station, "\n")
cat (" Location: " , object@location, "\n")
cat (" Channel: " , object@channel, "\n")
cat (" Quality: " , object@quality, "\n")
cat (" calib: " , object@calib, "\n")
cat (" npts: " , object@npts, "\n")
cat (" sampling rate: " , object@sampling_rate, "\n")
cat (" delta: " , object@delta, "\n")
cat (" starttime: " , format(object@starttime), "\n")
cat (" endtime: " , format(object@endtime), "\n")
cat (" latitude: " , format(object@latitude), "\n")
cat (" longitude: " , format(object@longitude), "\n")
cat (" elevation: " , format(object@elevation), "\n")
cat (" depth: " , format(object@depth), "\n")
cat (" azimuth: " , format(object@azimuth), "\n")
cat (" dip: " , format(object@dip), "\n")
cat (" processing: " , paste(object@processing,collapse="; "), "\n")
}
# NOTE: method signature must match generic signature for 'show' with argument: 'object'
setMethod("show", signature(object="TraceHeader"), function(object) show.TraceHeader(object))
# as.headerLine prints TraceHeader out as a single line ------------------------
#
# TIMESERIES LD_POTS__HHZ_M, 351 samples, 100.503 sps, 2012-01-29T00:00:00.006000, SLIST, INTEGER, COUNTS
if (!isGeneric("as.headerLine")) {
setGeneric("as.headerLine", function(obj) {
standardGeneric("as.headerLine")
})
}
as.headerLine.TraceHeader <- function(obj) {
cat("TIMESERIES ",obj@network,"_",obj@station,"_",obj@location,"_",obj@channel,", ", sep="")
cat(obj@npts," samples, ",obj@sampling_rate," sps, ",obj@starttime,", SLIST, INTEGER, COUNTS", sep="")
}
setMethod("as.headerLine", signature(obj="TraceHeader"), function(obj) as.headerLine.TraceHeader(obj))
# TODO: Setter and getter methods could be written as described in:
# TODO: www.stat.auckland.ac.nz/S-Workshop/Gentleman/S4Objects.pdf
################################################################################
# Class Trace
#
# An object containing data of a continuous series, such as a seismic trace.
#
# Documentation for attributes copied verbatim from obspy.core.trace.py
#
# :type data: :class:`~numpy.ndarray` or :class:`~numpy.ma.MaskedArray`
# :param data: Array of data samples
# :type header: dict or :class:`~obspy.core.trace.Stats`
# :param header: Dictionary containing header fields
#
# :var id: A SEED compatible identifier of the trace.
# :var stats: A container :class:`~obspy.core.trace.Stats` for additional
# header information of the trace.
# :var Sensor:
# :var InstrumentSensitivity:
# :var SensitivityFrequency:
# :var InputUnits:
# :var data: Data samples in a :class:`~numpy.ndarray` or
# :class:`~numpy.ma.MaskedArray`
#
################################################################################
setClass("Trace",
# typed slots (aka attributes) for class Trace
representation(id = "character",
stats = "TraceHeader",
Sensor = "character",
InstrumentSensitivity = "numeric",
SensitivityFrequency = "numeric",
InputUnits = "character",
data = "numeric"),
# default values for slots
prototype(id = "",
stats = new("TraceHeader"),
Sensor = "",
#InstrumentSensitivity = 1.0,
InstrumentSensitivity = as.numeric(NA), #if no information is available, the default should not be 1.0
#SensitivityFrequency = 1.0,
SensitivityFrequency = as.numeric(NA), #if no information is available, the default should not be 1.0
InputUnits = "",
data = c(0))
)
# initialize method
setMethod("initialize", "Trace",
function(.Object,
id="",
stats=new("TraceHeader"),
Sensor = "",
#InstrumentSensitivity = 1.0,
InstrumentSensitivity = as.numeric(NA),
#SensitivityFrequency = 1.0,
SensitivityFrequency = as.numeric(NA),
InputUnits = "",
data=c(0),
...) {
.Object@id <- id
.Object@stats <- stats
.Object@Sensor <- Sensor
.Object@InstrumentSensitivity <- InstrumentSensitivity
.Object@SensitivityFrequency <- SensitivityFrequency
.Object@InputUnits <- InputUnits
.Object@data <- data
if (.Object@id == "") {
stats <- .Object@stats
.Object@id <- paste(stats@network,stats@station,stats@location,stats@channel,stats@quality, sep=".")
}
return(.Object)
}
)
################################################################################
# Simple methods working on the data from a Trace object
################################################################################
# NOTE: The method signatures must match those already defined by R core functions
# NOTE: and R standard generics. Use "?function" to see the documentation or just
# NOTE: "function" to see the implementation and signature.
# NOTE:
# NOTE: When creating a new method to extend a method that has "...", always
# NOTE: provide the S4 object as the first argument, then the "..." and then
# NOTE: any final arguments.
# as.vector --------------------------------------------------------------------
as.vector.Trace <- function(x, mode="any") {
return( x@data )
}
setMethod("as.vector", signature(x="Trace"), function(x) as.vector.Trace(x))
# isDC -------------------------------------------------------------------------
if (!isGeneric("isDC")) {
setGeneric("isDC", function(x) {
standardGeneric("isDC")
})
}
isDC.Trace <- function(x) {
x <- DDT(x, demean=TRUE, detrend=FALSE, taper=0)
return( all(x@data==0, na.rm=TRUE) )
}
setMethod("isDC", signature(x="Trace"), function(x) isDC.Trace(x))
# Length -----------------------------------------------------------------------
length.Trace <- function(x) {
return( length(x@data) )
}
setMethod("length", signature(x="Trace"), function(x) length.Trace(x))
# Maximum ----------------------------------------------------------------------
max.Trace <- function(x, ..., na.rm=TRUE) {
return( max(x@data, na.rm=na.rm) )
}
setMethod("max", signature(x="Trace"), function(x, ...) max.Trace(x, ...))
# Mean -------------------------------------------------------------------------
mean.Trace <- function(x, ...) {
return( mean(x@data, ...) )
}
setMethod("mean", signature(x="Trace"), function(x, ...) mean.Trace(x, ...))
# TODO: How to get mean to use na.rm=TRUE as the default?
# Median -----------------------------------------------------------------------
median.Trace <- function(x, na.rm) {
return( median(x@data, na.rm=na.rm) )
}
# NOTE: method signature must match generic signature for function 'median' with arguments: 'x', 'na.rm'
setMethod("median", signature(x="Trace", na.rm="logical"), function(x, na.rm) median.Trace(x, na.rm=na.rm))
setMethod("median", signature(x="Trace", na.rm="missing"), function(x, na.rm) median.Trace(x, na.rm=TRUE))
# Minimum ----------------------------------------------------------------------
min.Trace <- function(x, ..., na.rm=TRUE) {
return( min(x@data, na.rm=na.rm) )
}
setMethod("min", signature(x="Trace"), function(x, ...) min.Trace(x, ...))
# Standard Deviation -----------------------------------------------------------
sd.Trace <- function(x, na.rm) {
return( sd(x@data, na.rm=na.rm) )
}
# NOTE: method signature must match generic signature for for function 'sd' with arguments: 'x', 'na.rm'
setMethod("sd", signature(x="Trace", na.rm="logical"), function(x, na.rm) sd.Trace(x, na.rm=na.rm))
setMethod("sd", signature(x="Trace", na.rm="missing"), function(x, na.rm) sd.Trace(x, na.rm=TRUE))
# Root Mean Square ----------------------------------------------------
if (!isGeneric("rms")) {
setGeneric("rms", function(x, na.rm) {
standardGeneric("rms")
})
}
rms.Trace <- function(x, na.rm) {
return( sqrt( mean((x@data)^2, na.rm=na.rm) ) )
}
setMethod("rms", signature(x="Trace", na.rm="logical"), function(x, na.rm) rms.Trace(x, na.rm=na.rm))
setMethod("rms", signature(x="Trace", na.rm="missing"), function(x, na.rm) rms.Trace(x, na.rm=TRUE))
# Root Mean Square Variance ----------------------------------------------------
if (!isGeneric("rmsVariance")) {
setGeneric("rmsVariance", function(x, na.rm) {
standardGeneric("rmsVariance")
})
}
rmsVariance.Trace <- function(x, na.rm) {
mean <- mean(x, na.rm=na.rm)
if (na.rm) {
n <- length(x@data)-length(which(is.na(x@data)))
} else {
n <- length(x)
}
return( sqrt( sum( (x@data-mean)^2, na.rm=na.rm ) / (n) ) )
}
setMethod("rmsVariance", signature(x="Trace", na.rm="logical"), function(x, na.rm) rmsVariance.Trace(x, na.rm=na.rm))
setMethod("rmsVariance", signature(x="Trace", na.rm="missing"), function(x, na.rm) rmsVariance.Trace(x, na.rm=TRUE))
################################################################################
# Methods that return a new, modified Trace
################################################################################
# Multiply by a constant -------------------------------------------------
if (!isGeneric("multiplyBy")) {
setGeneric("multiplyBy", function(x, y) {
standardGeneric("multiplyBy")
})
}
multiplyBy.Trace <- function(x, y) {
id <- x@id
stats <- x@stats
Sensor <- x@Sensor
InstrumentSensitivity <- x@InstrumentSensitivity
SensitivityFrequency <- x@SensitivityFrequency
InputUnits <- x@InputUnits
data <- x@data * y
stats@processing <- append(stats@processing,paste("multiply by",y))
return( new("Trace", id, stats, Sensor, InstrumentSensitivity, SensitivityFrequency, InputUnits, data) )
}
setMethod("multiplyBy", signature("Trace", y="numeric"), function(x, y) multiplyBy.Trace(x, y=y))
# Apply typical seismic DDT ----------------------------
if (!isGeneric("DDT")) {
setGeneric("DDT", function(x, demean, detrend, taper) {
standardGeneric("DDT")
})
}
DDT.Trace <- function(x, demean, detrend, taper) {
# TODO: When traces have processingInfo we can check the processingInfo
# TODO: and skip any steps that have already been done.
id <- x@id
stats <- x@stats
Sensor <- x@Sensor
InstrumentSensitivity <- x@InstrumentSensitivity
SensitivityFrequency <- x@SensitivityFrequency
InputUnits <- x@InputUnits
# NOTE: Use the pracma::detrend() function.
# NOTE: Linear detrending also removes the mean.
# demean only
if (demean && !detrend) {
data <- x@data - mean(x@data, na.rm=TRUE)
stats@processing <- append(stats@processing,"demean")
} else {
data <- x@data
}
# detrend
if (detrend) {
data <- as.numeric(pracma::detrend(x@data, tt='linear'))
stats@processing <- append(stats@processing,"detrend")
}
# cosine taper
if (taper > 0) {
data <- stats::spec.taper(data, p=taper)
stats@processing <- append(stats@processing,"cosine taper")
}
return( new("Trace", id, stats, Sensor, InstrumentSensitivity, SensitivityFrequency, InputUnits, data) )
}
# All parameters specified
setMethod("DDT", signature("Trace", demean="logical", detrend="logical", taper="numeric"),
function(x, demean, detrend, taper) DDT.Trace(x, demean, detrend, taper))
# No parameters
setMethod("DDT", signature("Trace", demean="missing", detrend="missing", taper="missing"),
function(x, demean, detrend, taper) DDT.Trace(x, TRUE, TRUE, 0.1))
# Apply Butterworth filters ------------
if (!isGeneric("butterworth")) {
setGeneric("butterworth", function(x, n, low, high, type) {
standardGeneric("butterworth")
})
}
butterworth.Trace <- function(x, n, low, high, type) {
# TODO: When traces have processingInfo we can check the processingInfo
# TODO: and skip any steps that have already been done.
data <- x@data
id <- x@id
stats <- x@stats
Sensor <- x@Sensor
InstrumentSensitivity <- x@InstrumentSensitivity
SensitivityFrequency <- x@SensitivityFrequency
InputUnits <- x@InputUnits
# If neither demean nor detrend has ever been applied, demean the data
if ( !any(stringr::str_detect(x@stats@processing,"demean")) &&
!any(stringr::str_detect(x@stats@processing,"detrend")) ) {
data <- data - mean(data, na.rm=TRUE)
stats@processing <- append(stats@processing,"demean")
}
# filt <- signal::butter(2,c(0.02/(trZ@stats@sampling_rate/2),0.04/(trZ@stats@sampling_rate/2)),type="pass")
# tsZ <- stats::ts(trZ@data,frequency=trZ@stats@sampling_rate)
# ts2 <- stats::ts(tr2@data,frequency=tr2@stats@sampling_rate)
# ts1 <- stats::ts(tr1@data,frequency=tr1@stats@sampling_rate)
#
# trZ_f <- trZ; trZ_f@data <- as.numeric(signal::filter(filt,tsZ))
#
# Create normalization factor
norm <- stats@sampling_rate / 2
# Create Butterworth filter
if (type == 'low') {
bf <- signal::butter(n, low/norm, type='low')
} else if (type == 'high') {
bf <- signal::butter(n, high/norm, type='high')
} else {
bf <- signal::butter(n, c(low/norm,high/norm), type=type)
}
# Apply filter
ts <- stats::ts(data, frequency=stats@sampling_rate)
ts_filtered <- signal::filter(bf, ts)
data <- as.numeric(ts_filtered)
stats@processing <- append(stats@processing,paste0("Butterworth filtered with (",n,",",low,",",high,",",type,")"))
return( new("Trace", id, stats, Sensor, InstrumentSensitivity, SensitivityFrequency, InputUnits, data) )
}
# All parameters specified
setMethod("butterworth", signature("Trace", n="numeric", low="numeric", high="numeric", type="character"),
function(x, n, low, high, type) butterworth.Trace(x, n, low, high, type))
# type missing -- band pass
setMethod("butterworth", signature("Trace", n="numeric", low="numeric", high="numeric", type="missing"),
function(x, n, low, high, type) butterworth.Trace(x, n, low, high, "pass"))
# hi and type missing -- low pass
setMethod("butterworth", signature("Trace", n="numeric", low="numeric", high="missing", type="missing"),
function(x, n, low, high, type) butterworth.Trace(x, n, low, NULL, "high"))
# lo and type missing -- high pass
setMethod("butterworth", signature("Trace", n="numeric", low="missing", high="numeric", type="missing"),
function(x, n, low, high, type) butterworth.Trace(x, n, NULL, high, "low"))
# Slice out a portion of a Trace ---------------------------
# NOTE: Unlike the ObsPy method, this method does no padding. The returned
# NOTE: Trace will always be a subset of the original Trace.
# NOTE: Slicing is to the nearest sample as of version 1.4.6
if (!isGeneric("slice")) {
setGeneric("slice", function(x, starttime, endtime) {
standardGeneric("slice")
})
}
slice.Trace <- function(x, starttime, endtime) {
# id and stats will be part of the returned Trace
id <- x@id
stats <- x@stats
Sensor <- x@Sensor
InstrumentSensitivity <- x@InstrumentSensitivity
SensitivityFrequency <- x@SensitivityFrequency
InputUnits <- x@InputUnits
data <- x@data
sliced <- FALSE
start_index <- 1
end_index <- length(data)
# Sanity check
if (starttime >= endtime) {
stop(paste("slice.Trace: requested starttime \"", starttime, "\" >= requested endtime \"", endtime, "\""))
}
if (starttime >= stats@endtime) {
stop(paste("slice.Trace: requested starttime \"", starttime, "\" >= Trace endtime \"", stats@endtime, "\""))
}
if (endtime <= stats@starttime) {
stop(paste("slice.Trace: requested endtime \"", endtime, "\" <= Trace starttime \"", stats@starttime, "\""))
}
# Modify the startime and startIndex if needed
if (starttime > stats@starttime ) {
# NOTE: Use difftime() instead of just subtracting to guarantee that units are "secs"
offset_secs <- as.numeric(difftime(starttime, stats@starttime, units="secs"))
offset_secs <- round(offset_secs, digits=6)
start_index <- start_index + as.integer(floor(offset_secs * stats@sampling_rate))
stats@starttime <- stats@starttime + offset_secs
sliced <- TRUE
} # else leave the start as is
# Modify the endtime and end_index if needed
if (endtime < stats@endtime) {
# NOTE: Use difftime() instead of just subtracting to guarantee that units are "secs"
offset_secs <- as.numeric(difftime(stats@endtime, endtime, units="secs"))
offset_secs <- round(offset_secs, digits=6)
end_index <- end_index - as.integer(floor(offset_secs * stats@sampling_rate))
stats@endtime <- stats@endtime - offset_secs
sliced <- TRUE
} # else leave the end as is
if (sliced) {
stats@npts <- as.integer(end_index - start_index + 1)
stats@processing <- append(stats@processing,"slice")
return( new("Trace", id, stats, Sensor, InstrumentSensitivity, SensitivityFrequency, InputUnits, data=data[start_index:end_index]) )
} else {
return(x)
}
}
setMethod("slice", signature(x="Trace", starttime="POSIXct", endtime="POSIXct"),
function(x, starttime, endtime) slice.Trace(x, starttime=starttime, endtime=endtime))
################################################################################
# Spectral Methods
################################################################################
# Hilbert transform -------------------------
if (!isGeneric("hilbert")) {
setGeneric("hilbert", function(x) {
standardGeneric("hilbert")
})
}
# Code copied from seewave package hilbert() method
# http://cran.r-project.org/web/packages/seewave/index.html
hilbert.Trace <- function(x) {
# Always detrend, demean, cosine taper the data
x <- DDT(x)
hfft <- hilbertFFT(x@data)
# The Hilbert tansform is the Imaginary part of the transformed data
x@data <- Im(hfft)
x@stats@processing <- append(x@stats@processing,"Hilbert transform")
return(x)
}
setMethod("hilbert", signature(x="Trace"),
function(x) hilbert.Trace(x))
# Envelope using Hilbert transform -------------------------
if (!isGeneric("envelope")) {
setGeneric("envelope", function(x) {
standardGeneric("envelope")
})
}
# Code copied from seewave package env() and hilbert() methods
# http://cran.r-project.org/web/packages/seewave/index.html
envelope.Trace <- function(x) {
# Always detrend, demean, cosine taper the data
x <- DDT(x)
hfft <- hilbertFFT(x@data)
# The envelope is the Modulus of the transformed data
x@data <- Mod(hfft)
x@stats@processing <- append(x@stats@processing,"envelope")
return(x)
}
setMethod("envelope", signature(x="Trace"),
function(x) envelope.Trace(x))
################################################################################
# Methods for event picking
################################################################################
# Multi-purpose SLA/TLA trigger algorithm --------------------------------------
# NOTE: http://en.wikipedia.org/wiki/First_break_picking
# NOTE:
# NOTE: "A Comparison of Select Trigger Algorithms for Automated Global Seismic Phase and Event Detection"
# NOTE: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.116.245&rep=rep1&type=pdf
# NOTE:
# NOTE: Wong:
# NOTE: "Automatic time-picking of first arrivals on noisy microseismic data"
# NOTE: http://www.crewes.org/ForOurSponsors/ConferenceAbstracts/2009/CSEG/Wong_CSEG_2009.pdf
# NOTE:
# NOTE: GEOPHYSICS, VOL. 75, NO. 4 (JULY-AUGUST 2010); P. V67-V76
# NOTE: Automatic first-breaks picking: New strategies and algorithms"
# NOTE: http://www.fcaglp.unlp.edu.ar/~velis/papers/PickingGeop10.pdf
# NOTE:
# NOTE: "Adaptive microseismic event detection and automatic time picking"
# NOTE: http://www.cspg.org/documents/Conventions/Archives/Annual/2012/279_GC2012_Adaptive_Microseismic_Event_Detection.pdf
# NOTE:
# NOTE: Earle and Shearer:
# NOTE: "Characterization of Global Seismograms Using an Automatic-Picking Algorithm"
# NOTE: Bulletin of the Seismological Society of America, Vol. 84, No. 2, pp. 366-376, April 1994
if (!isGeneric("STALTA")) {
setGeneric("STALTA", function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment) {
standardGeneric("STALTA")
})
}
STALTA.Trace <- function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment) {
# Calculate constants
n_sta <- staSecs * x@stats@sampling_rate
n_lta <- ltaSecs * x@stats@sampling_rate
if (n_sta < 1) {
stop(paste("STALTA.Trace: STA window of",staSecs,"secs is too short for trace sampling rate of", x@stats@sampling_rate,"Hz."))
}
if (demean | detrend) {
x <- DDT(x, demean, detrend, taper)
}
len <- length(x)
# Stop if there is insufficient data
if ( len < n_lta ) {
stop(paste("STALTA.Trace: insufficient data."))
}
# DC signals will have all zeroes after demeaning.
# Return all zeroes in this case.
if ( all(x@data == 0, na.rm=TRUE) ) {
return(x@data)
}
# Apply the algorithm over the entire data series
if (algorithm == "classic_RR") {
# "Automatic time-picking of first arrivals on noisy microseismic data"
# NOTE: In this reference, the LTA and STA windows use the same right edge (index is in bth windows)
if (increment != 1) {
stop(paste("STALTA.Trace: algorithm EarleAndShearer_envelope requires increment=1.",sep=""))
}
sta <- seismicRoll::roll_mean(x@data^2, n_sta, increment, align="right")
lta <- seismicRoll::roll_mean(x@data^2, n_lta, increment, align="right")
picker <- sta/lta
} else if (algorithm == "classic_LR") {
# NOTE: This is the same algorithm but with left-right alignment (index is in both windows)
picker <- seismicRoll::roll_stalta(x@data^2, n_sta, n_lta, increment)
} else if (algorithm == "EarleAndShearer_envelope") {
if (increment != 1) {
stop(paste("STALTA.Trace: algorithm EarleAndShearer_envelope requires increment=1.",sep=""))
}
data <- envelope(x)@data
sta <- seismicRoll::roll_mean(data, n_sta, increment, align="left")
lta <- seismicRoll::roll_mean(data, n_lta, increment, align="right")
picker <- sta/lta
} else {
stop(paste("STALTA.Trace: algorithm=\"",algorithm,"\" not recognized.",sep=""))
}
return( picker )
}
# All parameters specified
setMethod("STALTA", signature(x="Trace", staSecs="numeric", ltaSecs="numeric",
algorithm="character", demean="logical", detrend="logical", taper="numeric", increment="numeric"),
function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment)
STALTA.Trace(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment))
# demean, detrend, taper and increment missing
setMethod("STALTA", signature(x="Trace", staSecs="numeric", ltaSecs="numeric",
algorithm="character", demean="missing", detrend="missing", taper="missing", increment="missing"),
function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment)
STALTA.Trace(x, staSecs, ltaSecs, algorithm, TRUE, TRUE, 0.0, 1))
# Only lstaSecs and ltaSecs
setMethod("STALTA", signature(x="Trace", staSecs="numeric", ltaSecs="numeric",
algorithm="missing", demean="missing", detrend="missing", taper="missing", increment="missing"),
function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment)
STALTA.Trace(x, staSecs, ltaSecs, "classic_LR", TRUE, TRUE, 0.0, 1))
# All parameters missing
setMethod("STALTA", signature(x="Trace", staSecs="missing", ltaSecs="missing",
algorithm="missing", demean="missing", detrend="missing", taper="missing", increment="missing"),
function(x, staSecs, ltaSecs, algorithm, demean, detrend, taper, increment)
STALTA.Trace(x, 3, 30, "classic_LR", TRUE, TRUE, 0.0, 1))
# Simple triggering of p-wave onset --------------------------------------------
# NOTE: Method to return the first timepoint at which STALTA is above a threshold
#
# NOTE: This method only returns the first timepoint or NA. It DOES NOT implement
# NOTE: the full functionality found in the ObsPy method of the same name.
#
# NOTE: http://docs.obspy.org/packages/autogen/obspy.signal.trigger.triggerOnset.html
if (!isGeneric("triggerOnset")) {
setGeneric("triggerOnset", function(x, picker, threshold, index) {
standardGeneric("triggerOnset")
})
}
triggerOnset.Trace <- function(x, picker, threshold, index) {
if (missing(threshold)) {
threshold <- stats::quantile(picker,0.99999,na.rm=TRUE)
}
if (missing(index)) {
index <- FALSE
}
# At which index is the picker above the threshold?
eventIndex <- which(picker > threshold)[1]
# Return just the index if desired
if (index) {
return(eventIndex)
}
# Otherwise calculate the time at which that index occurs
if (is.na(eventIndex)) {
eventTime <- NA
} else {
eventTime <- x@stats@starttime + eventIndex / x@stats@sampling_rate
}
return(eventTime)
}
setMethod("triggerOnset", signature(x="Trace", picker="numeric", threshold="ANY", index="ANY"),
function(x, picker, threshold, index)
triggerOnset.Trace(x, picker, threshold, index))
# Select the event window around p-wave onset ----------------------------------
if (!isGeneric("eventWindow")) {
setGeneric("eventWindow", function(x, picker, threshold, windowSecs) {
standardGeneric("eventWindow")
})
}
eventWindow.Trace <- function(x, picker, threshold, windowSecs) {
eventOnset <- triggerOnset(x, picker, threshold)
windowStarttime <- eventOnset - windowSecs/2
windowEndtime <- eventOnset + windowSecs/2
windowTrace <- slice(x, windowStarttime, windowEndtime)
return(windowTrace)
}
setMethod("eventWindow", signature(x="Trace", picker="numeric", threshold="numeric", windowSecs="numeric"),
function(x, picker, threshold, windowSecs)
eventWindow.Trace(x, picker, threshold, windowSecs))
setMethod("eventWindow", signature(x="Trace", picker="numeric", threshold="numeric", windowSecs="missing"),
function(x, picker, threshold, windowSecs)
eventWindow.Trace(x, picker, threshold, windowSecs=3600))
setMethod("eventWindow", signature(x="Trace", picker="numeric", threshold="missing", windowSecs="numeric"),
function(x, picker, threshold, windowSecs)
eventWindow.Trace(x, picker, threshold=stats::quantile(picker,0.999,na.rm=TRUE), windowSecs))
setMethod("eventWindow", signature(x="Trace", picker="numeric", threshold="missing", windowSecs="missing"),
function(x, picker, threshold, windowSecs)
eventWindow.Trace(x, picker, threshold=stats::quantile(picker,0.999,na.rm=TRUE), windowSecs=3600))
################################################################################
# Basic plotting
################################################################################
# NOTE: I don't seem to be able to use optional arguments of type "ANY" in the
# NOTE: function declaration. Probably has to do with plot.~() being older
# NOTE: style functions rather than S4 methods.
plot.Trace <- function(x, starttime=x@stats@starttime, endtime=x@stats@endtime,
subsampling=NULL, add=FALSE, ylab="raw", xlab="", ...) {
# Create subsampled indices if necessary
max_size <- 10000
if (is.null(subsampling)) {
if (length(x) <= max_size) {
subsampling <- 1
} else {
subsampling <- as.integer(length(x)/(max_size/2))
}
}
indices <- seq(1,length(x),subsampling)
# Create labels
# remove ".M" or other quality factor designation as people don't expect it
id <- stringr::str_sub(x@id, 1, stringr::str_length(x@id)-2)
main <- paste("Seismic Trace for ",id,sep="")
sensorText <- paste("(", x@Sensor, ")")
# Create array of times
times <- seq(from=x@stats@starttime, to=x@stats@endtime, length.out=length(x@data))
if (length(x) == length(indices)) {
xlab <- paste(xlab,"\n"," (",length(x), "points )")
} else {
xlab <- paste(xlab, "\n"," ( ",length(x), " points, subsampling=", subsampling, " for this plot. )", sep="")
}
# Plot
if (difftime(x@stats@endtime,x@stats@starttime,units="days") > "1 day" & difftime(x@stats@endtime,x@stats@starttime,units="days") < "7 days") {
plot(x@data[indices] ~ times[indices], type='l',main=main, xaxt="n", xlim=c(starttime, endtime), xlab=xlab,ylab=ylab,...)
graphics::axis.POSIXct(1, at=seq(from=x@stats@starttime, to=x@stats@endtime, by="day"), format="%b %d")
} else {
plot(x@data[indices] ~ times[indices], type='l', xlim=c(starttime, endtime),main=main,xlab=xlab,ylab=ylab,...)
}
# title
graphics::mtext(sensorText, line=0.2, adj=0.05)
}
# NOTE: I don't seem to be able to use optional arguments of type "ANY" in the
# NOTE: function declaration. Probably has to do with plot.~() being older
# NOTE: style functions rather than S4 methods.
setMethod("plot", signature(x="Trace"), function(x, ...) plot.Trace(x, ...))
################################################################################
# END
################################################################################
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.