PAMpal makes it easy to calculate the average spectrum for a set of clicks, since this is often a useful image to create for assisting in species classification. You simply need to provide PAMpal an AcousticStudy object, and tell it which event (or events) to create the image for. It will then take all the clicks from all channels in that event, calculate their spectra, and average them (converting to/from log space where appropriate). This function creates two plots - first a concatenated spectrogram of all the clicks, and second the average spectrum. Here we calculate and plot the average spectrum of the first event in our study.
<- calculateAverageSpectra(myStudy, evNum = 1) avSpec
The evNum
argument can be a vector to calculate and plot the average of multiple events. In this case the average will be calculated over all events, and the concatenated spectrogram will have black bars marking the start and end of each event. Here we see that our second event is much shorter and our third event is much longer.
calculateAverageSpectra(myStudy, evNum=1:3)
You can also specify the evNum
argument as the name of the event you want to plot rather than the index number of that event. This is usually easier to do when you are trying to plot a specific event. Here we’ll also turn off the plotting option since the plot we are creating is identical to the first event, we are just specifying it by name this time. Vectors of names are also accepted.
<- calculateAverageSpectra(myStudy, evNum="HB1603_MF_Master_Leg3_Skala.OE115", plot=FALSE)
avSpecName # Double check that this is the same whether evNum = 1 or the name of event #1
identical(avSpec, avSpecName)
## [1] TRUE
Note that calculateAverageSpectra
also creates an output in addition to the plot (which is why we were able to compare those two examples to make sure they were the same). It stores all the data used to create the plots in the output.
str(avSpec)
## List of 6
## $ freq : num [1:256] 375 750 1125 1500 1875 ...
## $ UID : chr [1:144] "1186" "1195" "1221" "1235" ...
## $ avgSpec : num [1:256] -43.1 -42.4 -42.1 -42.1 -41.9 ...
## $ allSpec : num [1:256, 1:144] -43.3 -46.6 -53.4 -48 -53.9 ...
## $ avgNoise: num [1:256] -43.1 -42.5 -42.1 -42.1 -41.9 ...
## $ allNoise: num [1:256, 1:144] -43.4 -46.6 -53.3 -48.1 -54.1 ...
freq
is the frequency value (Hz) on the axes of the plots (here the default window length is 512, so we have 256 frequency values), UID
is the UIDs of the detections included in the plot, avgSpec
is the computed average spectrum of those detections, and allSpec
is a matrix of each individual spectrum used to calculate the average (each column corresponds to each UID). You’ll also note that there are similar outputs avgNoise
and allNoise
. calculateAverageSpectra
internally attempts to compute a noise level for each clip, lets turn that option on and see what the plot shows. We’ll also make use of the ability to only plot one of the two default plots at a time since this will only show up on the average spectrum.
<- calculateAverageSpectra(myStudy, evNum=1, noise=TRUE, plot=c(FALSE, TRUE)) avNoise
The estimated noise level shows up as a dotted line, but there’s a problem! The dotted line is exactly the same as the average spectrum. To figure out why, we need to talk about how PAMpal is calculating each individual spectra. For each detection, PAMpal only has access to the short click clip stored in the PAMGuard binary files. For the click spectra, it will take a window (default length is 512, can be specified with parameter wl
) centered on the maximum value of the waveform. For the estimated noise spectra, it will take a window of the same length starting at the beginning of the clip. If the window length specified is longer than the duration of the clip stored in the binaries, the clip will be zero-padded until it is of appropriate length. Let’s take a look at one of the click clips to see what is going on here.
# Grab the binary data for the first click in this study
<- getBinaryData(myStudy, UID = getClickData(myStudy)$UID[1])
binData # plot the waveform
plot(binData[[1]]$wave[, 1], type='l')
The problem here is that a window of 512 centered around the peak of the waveform (~200) ends up being the exact same as the noise clip that starts at the beginning of the clip. This will also happen if your window length is too large and it results in zero-padding. The solution here is to shorten our wl
argument so that our noise window will be independent from our click window. Looking at the plot, it seems like a wl
of 128 should ensure this.
<- calculateAverageSpectra(myStudy, evNum=1, noise=TRUE, wl=128, plot=c(FALSE, TRUE)) avNoise
Much better! That’s what we would expect the noise to be like. You might be wondering why we can’t just use the back half of that click clip to get a better estimate of noise with longer windows. The problem with this is that in some circumstances you might have an echo or reflection after the initial click, so taking the noise before the click is much more stable. The number of samples before the click can be changed within PAMGuard’s Click Detector settings.
There is an option to try and filter out clicks with a low signal-to-noise ratio (SNR). For each click, we estimate the SNR as the difference between the click spectrum and the noise spectrum at the frequency with the highest amplitude in the click spectrum. Since this depends on a relatively accurate noise spectrum, you must be sure that you have a reasonable noise line before trying to filter by SNR (ie. it looks like the one just above, and not the first plot we made). Once you are satisfied with that, then you can set the snr
parameter, and all clicks with an SNR below that value will be removed from the results. Let’s see if we can remove some of the quieter clicks from one of our events.
par(mfrow=c(2,2))
calculateAverageSpectra(myStudy, evNum=3, wl=128, noise=TRUE, snr=0)
calculateAverageSpectra(myStudy, evNum=3, wl=128, noise=TRUE, snr=20)
Success! We removed about half of the clicks, and the noise level has dropped from ~-25dB to ~-30dB. Note that it is possible to set the snr
parameter to too high of a value which might remove all clicks from an event, in which case you’ll get a warning and nothing will happen.
Sometimes it can be easier to see patterns in the concatenated spectrogram if the clicks are sorted by peak frequency rather than the order in which they occur. Setting sort=TRUE
changes the first plot to display in this manner, but changes none of the calculations. Note that if multiple events are being run at once, the black bars that would normally separate events are no longer present - all clicks are sorted as one group, rather than sorted within events.
calculateAverageSpectra(myStudy, evNum=1, sort=TRUE, plot=c(TRUE, FALSE))
calculateAverageSpectra(myStudy, evNum=1:3, sort=TRUE, plot=c(TRUE, FALSE))
In some circumstances it can be tricky to create a concatenated spectrogram image with a satisfactory appearance. Since it is displaying a large number of clicks, one particularly loud or quiet click can throw off the scaling of the whole image. To help deal with this issue, there are two visual parameters contrast
and brightness
that adjust this image. They both range from -255 to 255, with positive values increasing contrast/brightness and negative values decreasing. There is usually a combination of these parameters that can overcome any image scaling issues with some trial and error. Note that these do not affect any of the numerical results, only the plots. The following are meant only as illustrative examples, the original plot is pretty decent.
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), contrast=100)
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), brightness=-40)
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), contrast=100, brightness=50)
Apart from the brightness
and contrast
settings, there is also another graphical parameter you can tweak for the concatenated spectrogram, but its use is a little less obvious. This parameter is q
, and it sets a quantile threshold for the plotted colors. The default value of q=.01
means that the bottom 1% and top 1% of values will be displayed with the same color value. Without this setting, a few extremely loud or extremely quiet clicks could throw off the scaling of the entire image, resulting in an image with apparently lower contrast. To illustrate this, here are plots with q
values of 0%, 1%, and 5%.
par(mfrow=c(1, 3))
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), q=0)
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), q=.01) #default
calculateAverageSpectra(myStudy, evNum=1, plot=c(TRUE, FALSE), q=.05)
You can see that this has the effect of increasing the contrast in the image, but with higher q
values you lose detail in the brightest and darkest points of the image.
There are also two options to adjust the displayed axis limits, flim
and ylim
. flim
will adjust the frequency limits of both plots (units of Hz). ylim
will adjust y-axis of the average spectra plot only.
calculateAverageSpectra(myStudy, evNum=1, plot=TRUE, flim=c(15e3, 80e3))
calculateAverageSpectra(myStudy, evNum=1, plot=c(FALSE, TRUE), ylim=c(-20, 0))
There are a couple of signal processing options built in to the function. First, you can specify decimate
to reduce the sample rate by integer factors (only integers are allowed). This uses the decimate
function from the signal
package and applies a lowpass filter to avoid aliasing.
calculateAverageSpectra(myStudy, evNum=1, decimate=2)
You can also specify high- or band-pass filters by settings filterfrom_khz
and filterto_khz
parameters (low-pass currently not supported). It is usually necessary to combine these with the ylim
parameter in order to create a useful average spectrum plot since they will greatly reduce the magnitude of the signal.
calculateAverageSpectra(myStudy, evNum=1, filterfrom_khz = 10, plot=c(TRUE, FALSE))
# average spectrum scale will be way off
calculateAverageSpectra(myStudy, evNum=1, filterfrom_khz = 10, filterto_khz = 60)
calculateAverageSpectra(myStudy, evNum=1, filterfrom_khz = 10, filterto_khz = 60, ylim=c(-30, 0), plot=c(FALSE, TRUE))
One more obscure feature of the calculateAverageSpectra
function is that it can calculate the cepstrum of each click instead of the spectrum. In most cases (this example included) this is not particularly useful, but it can be used to find things like echoes of clicks or estimate the IPI in sperm whale clicks. Due to the way that the cepstrum works when finding these features, it is often necessary to apply a decimation factor, increase the window length, or both. This example is purely illustrative of how you would do that, but results are not likely to be meaningful.
calculateAverageSpectra(myStudy, evNum=1, mode='ceps', decimate=2, wl=1024)
channel
can limit the calculations to only considering the first or second channel by setting channel=1
or channel=2
, the default is to use both channels for each detection and average them.
sr
can be used to manually set the samplerate for detections. This is sometimes necessary if data was decimated in Pamguard, but is not needed if you had supplied an XML settings file with addSettings
before processing your data in PAMpal
calibration
can be used to provide a frequency response curve calibration after calcuating the spectrum. This does not need to be specified if one was added before processing your data with addCalibration
(recommended to do this if a calibration is needed/available), in this case it will detect that calibration and use it.
norm
is a logical flag (default TRUE
) to normalize the magnitude of the average spectra to 0. This is only shifting the displayed curve, not affecting any of the calculations.