Merge pull request #13 from PAMGuard/main
Merge synchronization fix from main
2
pom.xml
@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.pamguard</groupId>
|
||||
<artifactId>Pamguard</artifactId>
|
||||
<version>2.02.04f</version>
|
||||
<version>2.02.04aa</version>
|
||||
<name>Pamguard Java12+</name>
|
||||
<description>Pamguard for Java 12+, using Maven to control dependcies</description>
|
||||
<url>www.pamguard.org</url>
|
||||
|
@ -31,12 +31,12 @@ public class PamguardVersionInfo {
|
||||
* Version number, major version.minorversion.sub-release.
|
||||
* Note: can't go higher than sub-release 'f'
|
||||
*/
|
||||
static public final String version = "2.02.04a";
|
||||
static public final String version = "2.02.04aa";
|
||||
|
||||
/**
|
||||
* Release date
|
||||
*/
|
||||
static public final String date = "23 June 2022";
|
||||
static public final String date = "13 September 2022";
|
||||
|
||||
// /**
|
||||
// * Release type - Beta or Core
|
||||
|
@ -188,29 +188,31 @@ public class RawDataTransforms {
|
||||
* @param fftLength
|
||||
* @return Power spectrum
|
||||
*/
|
||||
public synchronized double[] getPowerSpectrum(int channel, int fftLength) {
|
||||
if (powerSpectra == null) {
|
||||
powerSpectra = new double[PamUtils.getNumChannels(dataUnit.getChannelBitmap())][];
|
||||
}
|
||||
if (fftLength == 0) {
|
||||
fftLength = getCurrentSpectrumLength();
|
||||
}
|
||||
|
||||
if (powerSpectra[channel] == null
|
||||
|| powerSpectra[channel].length != fftLength / 2) {
|
||||
ComplexArray cData = getComplexSpectrumHann(channel, fftLength);
|
||||
currentSpecLen = fftLength;
|
||||
powerSpectra[channel] = cData.magsq();
|
||||
if (powerSpectra==null){
|
||||
System.err.println("DLDetection: could not calculate power spectra");
|
||||
return null;
|
||||
|
||||
public double[] getPowerSpectrum(int channel, int fftLength) {
|
||||
synchronized (synchObject) {
|
||||
if (powerSpectra == null) {
|
||||
powerSpectra = new double[PamUtils.getNumChannels(dataUnit.getChannelBitmap())][];
|
||||
}
|
||||
if (powerSpectra[channel].length != fftLength/2) {
|
||||
powerSpectra[channel] = Arrays.copyOf(powerSpectra[channel], fftLength/2);
|
||||
if (fftLength == 0) {
|
||||
fftLength = getCurrentSpectrumLength();
|
||||
}
|
||||
|
||||
if (powerSpectra[channel] == null
|
||||
|| powerSpectra[channel].length != fftLength / 2) {
|
||||
ComplexArray cData = getComplexSpectrumHann(channel, fftLength);
|
||||
currentSpecLen = fftLength;
|
||||
powerSpectra[channel] = cData.magsq();
|
||||
if (powerSpectra==null){
|
||||
System.err.println("DLDetection: could not calculate power spectra");
|
||||
return null;
|
||||
|
||||
}
|
||||
if (powerSpectra[channel].length != fftLength/2) {
|
||||
powerSpectra[channel] = Arrays.copyOf(powerSpectra[channel], fftLength/2);
|
||||
}
|
||||
}
|
||||
return powerSpectra[channel];
|
||||
}
|
||||
return powerSpectra[channel];
|
||||
}
|
||||
|
||||
|
||||
@ -220,25 +222,27 @@ public class RawDataTransforms {
|
||||
* @param fftLength
|
||||
* @return Sum of power spectra
|
||||
*/
|
||||
public synchronized double[] getTotalPowerSpectrum(int fftLength) {
|
||||
if (fftLength == 0) {
|
||||
fftLength = getCurrentSpectrumLength();
|
||||
}
|
||||
if (fftLength == 0) {
|
||||
fftLength = PamUtils.getMinFftLength(getSampleDuration());
|
||||
}
|
||||
double[] ps;
|
||||
if (totalPowerSpectrum == null
|
||||
|| totalPowerSpectrum.length != fftLength / 2) {
|
||||
totalPowerSpectrum = new double[fftLength / 2];
|
||||
for (int c = 0; c < PamUtils.getNumChannels(this.dataUnit.getChannelBitmap()); c++) {
|
||||
ps = getPowerSpectrum(c, fftLength);
|
||||
for (int i = 0; i < fftLength / 2; i++) {
|
||||
totalPowerSpectrum[i] += ps[i];
|
||||
public double[] getTotalPowerSpectrum(int fftLength) {
|
||||
synchronized (synchObject) {
|
||||
if (fftLength == 0) {
|
||||
fftLength = getCurrentSpectrumLength();
|
||||
}
|
||||
if (fftLength == 0) {
|
||||
fftLength = PamUtils.getMinFftLength(getSampleDuration());
|
||||
}
|
||||
double[] ps;
|
||||
if (totalPowerSpectrum == null
|
||||
|| totalPowerSpectrum.length != fftLength / 2) {
|
||||
totalPowerSpectrum = new double[fftLength / 2];
|
||||
for (int c = 0; c < PamUtils.getNumChannels(this.dataUnit.getChannelBitmap()); c++) {
|
||||
ps = getPowerSpectrum(c, fftLength);
|
||||
for (int i = 0; i < fftLength / 2; i++) {
|
||||
totalPowerSpectrum[i] += ps[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalPowerSpectrum;
|
||||
}
|
||||
return totalPowerSpectrum;
|
||||
}
|
||||
|
||||
|
||||
@ -253,15 +257,17 @@ public class RawDataTransforms {
|
||||
* @param fftLength - the FFT length to use.
|
||||
* @return the complex spectrum - the comnplex spectrum of the wave data from the specified channel.
|
||||
*/
|
||||
public synchronized ComplexArray getComplexSpectrumHann(int channel, int fftLength) {
|
||||
complexSpectrum = new ComplexArray[PamUtils.getNumChannels(dataUnit.getChannelBitmap())];
|
||||
if (complexSpectrum[channel] == null
|
||||
|| complexSpectrum.length != fftLength / 2) {
|
||||
public ComplexArray getComplexSpectrumHann(int channel, int fftLength) {
|
||||
synchronized (synchObject) {
|
||||
complexSpectrum = new ComplexArray[PamUtils.getNumChannels(dataUnit.getChannelBitmap())];
|
||||
if (complexSpectrum[channel] == null
|
||||
|| complexSpectrum.length != fftLength / 2) {
|
||||
|
||||
complexSpectrum[channel] = getComplexSpectrumHann(rawData.getWaveData()[channel], fftLength);
|
||||
currentSpecLen = fftLength;
|
||||
complexSpectrum[channel] = getComplexSpectrumHann(rawData.getWaveData()[channel], fftLength);
|
||||
currentSpecLen = fftLength;
|
||||
}
|
||||
return complexSpectrum[channel];
|
||||
}
|
||||
return complexSpectrum[channel];
|
||||
}
|
||||
|
||||
|
||||
@ -389,29 +395,31 @@ public class RawDataTransforms {
|
||||
* @param fftLength
|
||||
* @return the complex spectrum
|
||||
*/
|
||||
public synchronized ComplexArray getComplexSpectrum(int channel, int fftLength) {
|
||||
double[] paddedRawData;
|
||||
double[] rawData;
|
||||
int i, mn;
|
||||
public ComplexArray getComplexSpectrum(int channel, int fftLength) {
|
||||
synchronized (synchObject) {
|
||||
double[] paddedRawData;
|
||||
double[] rawData;
|
||||
int i, mn;
|
||||
|
||||
if (complexSpectrum == null) {
|
||||
complexSpectrum = new ComplexArray[getNChan()];
|
||||
}
|
||||
if (complexSpectrum[channel] == null
|
||||
|| complexSpectrum.length != fftLength / 2) {
|
||||
paddedRawData = new double[fftLength];
|
||||
rawData = getWaveData(channel);
|
||||
//double[] rotData = getRotationCorrection(channel);
|
||||
mn = Math.min(fftLength, getSampleDuration().intValue());
|
||||
for (i = 0; i < mn; i++) {
|
||||
paddedRawData[i] = rawData[i];//-rotData[i];
|
||||
if (complexSpectrum == null) {
|
||||
complexSpectrum = new ComplexArray[getNChan()];
|
||||
}
|
||||
for (i = mn; i < fftLength; i++) {
|
||||
paddedRawData[i] = 0;
|
||||
if (complexSpectrum[channel] == null
|
||||
|| complexSpectrum.length != fftLength / 2) {
|
||||
paddedRawData = new double[fftLength];
|
||||
rawData = getWaveData(channel);
|
||||
//double[] rotData = getRotationCorrection(channel);
|
||||
mn = Math.min(fftLength, getSampleDuration().intValue());
|
||||
for (i = 0; i < mn; i++) {
|
||||
paddedRawData[i] = rawData[i];//-rotData[i];
|
||||
}
|
||||
for (i = mn; i < fftLength; i++) {
|
||||
paddedRawData[i] = 0;
|
||||
}
|
||||
complexSpectrum[channel] = fastFFT.rfft(paddedRawData, fftLength);
|
||||
}
|
||||
complexSpectrum[channel] = fastFFT.rfft(paddedRawData, fftLength);
|
||||
return complexSpectrum[channel];
|
||||
}
|
||||
return complexSpectrum[channel];
|
||||
}
|
||||
|
||||
|
||||
@ -420,14 +428,16 @@ public class RawDataTransforms {
|
||||
* @param iChan channel index
|
||||
* @return analytic waveform
|
||||
*/
|
||||
public synchronized double[] getAnalyticWaveform(int iChan) {
|
||||
if (analyticWaveform == null) {
|
||||
analyticWaveform = new double[getNChan()][];
|
||||
public double[] getAnalyticWaveform(int iChan) {
|
||||
synchronized (synchObject) {
|
||||
if (analyticWaveform == null) {
|
||||
analyticWaveform = new double[getNChan()][];
|
||||
}
|
||||
// if (analyticWaveform[iChan] == null) {
|
||||
analyticWaveform[iChan] = hilbert.getHilbert(getWaveData(iChan));
|
||||
// }
|
||||
return analyticWaveform[iChan];
|
||||
}
|
||||
// if (analyticWaveform[iChan] == null) {
|
||||
analyticWaveform[iChan] = hilbert.getHilbert(getWaveData(iChan));
|
||||
// }
|
||||
return analyticWaveform[iChan];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,12 +449,14 @@ public class RawDataTransforms {
|
||||
* @param fftFilterParams fft filter parameters.
|
||||
* @return analystic waveform.
|
||||
*/
|
||||
public synchronized double[] getAnalyticWaveform(int iChan, boolean filtered, FFTFilterParams fftFilterParams) {
|
||||
if (filtered == false || fftFilterParams == null) {
|
||||
return getAnalyticWaveform(iChan);
|
||||
}
|
||||
else {
|
||||
return getFilteredAnalyticWaveform(fftFilterParams, iChan);
|
||||
public double[] getAnalyticWaveform(int iChan, boolean filtered, FFTFilterParams fftFilterParams) {
|
||||
synchronized (synchObject) {
|
||||
if (filtered == false || fftFilterParams == null) {
|
||||
return getAnalyticWaveform(iChan);
|
||||
}
|
||||
else {
|
||||
return getFilteredAnalyticWaveform(fftFilterParams, iChan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
BIN
src/Resources/backArrow.png
Normal file
After Width: | Height: | Size: 173 B |
@ -107,6 +107,7 @@ public class ClickAlarmManager extends PamProcess {
|
||||
ClickDetection cd = (ClickDetection) arg;
|
||||
// System.out.println("ClickAlarm hashcode, time = " + System.identityHashCode(arg) + "," + cd.getTimeMilliseconds() + "," + cd.getClickType() );
|
||||
if (cd.getClickType()!=0) {
|
||||
|
||||
ClickTypeCommonParams commonParams = clickControl.getClickIdentifier().getCommonParams(cd.getClickType());
|
||||
|
||||
if (commonParams==null) return;
|
||||
|
@ -142,6 +142,7 @@ public class SweepClassifier implements ClickIdentifier , PamSettings {
|
||||
@Override
|
||||
public ClickTypeCommonParams getCommonParams(int code) {
|
||||
int codeIdx = codeToListIndex(code);
|
||||
if (codeIdx<0 || codeIdx>=sweepClassifierParameters.getNumSets()) return null;
|
||||
return sweepClassifierParameters.getSet(codeIdx);
|
||||
}
|
||||
|
||||
|
@ -82,16 +82,16 @@ public class ExampleClickTrains {
|
||||
String path = null;
|
||||
switch (type) {
|
||||
case SIMCLICKS_1:
|
||||
path = masterPath + "\\" + "simtrains_1.mat";
|
||||
path = masterPath + File.separator + "simtrains_1.mat";
|
||||
break;
|
||||
case SIMCLICKS_2:
|
||||
path = masterPath + "\\" + "simtrains_2.mat";
|
||||
path = masterPath + File.separator + "simtrains_2.mat";
|
||||
break;
|
||||
case DOLPHINS_ST:
|
||||
path = masterPath + "\\" + "20190801_212731_clicks_dolphins_ST.mat";
|
||||
path = masterPath + File.separator + "20190801_212731_clicks_dolphins_ST.mat";
|
||||
break;
|
||||
case SIMCLICKS_3:
|
||||
path = masterPath + "\\" + "simtrains_3_corr.mat";
|
||||
path = masterPath + File.separator + "simtrains_3_corr.mat";
|
||||
break;
|
||||
}
|
||||
File file = new File(path);
|
||||
|
@ -217,14 +217,16 @@ public class TemplateSpectrumPane extends PamBorderPane {
|
||||
* @param file - the file to open.
|
||||
*/
|
||||
private MatchTemplate openFile(File file) {
|
||||
try {
|
||||
MatchTemplate template = null;
|
||||
String extension = getFileExtension(file);
|
||||
for (int i=0; i< this.templateImporters.size(); i++) {
|
||||
for (int j=0; j<templateImporters.get(i).getExtension().length; j++) {
|
||||
//System.out.println(templateImporters.get(i).getExtension()[j] + " : " + extension);
|
||||
// System.out.println(templateImporters.get(i).getExtension()[j] + " : " + extension);
|
||||
if (templateImporters.get(i).getExtension()[j].equals(extension)) {
|
||||
//System.out.println("Import using the extensions: " + extension);
|
||||
// System.out.println("Import using the extensions: " + extension);
|
||||
template=templateImporters.get(i).importTemplate(file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,6 +235,11 @@ public class TemplateSpectrumPane extends PamBorderPane {
|
||||
return null;
|
||||
}
|
||||
else return template;
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jmatio.types.MLArray;
|
||||
import com.jmatio.types.MLDouble;
|
||||
import com.jmatio.types.MLStructure;
|
||||
|
||||
import PamguardMVC.PamDataUnit;
|
||||
@ -63,6 +64,11 @@ public class MLDetectionsManager {
|
||||
//so, need to sort compatible data units.
|
||||
|
||||
ArrayList<MLArray> list = new ArrayList<MLArray>();
|
||||
|
||||
//keep a track of the data units that have been transcribed. This means data units that are multiple types
|
||||
//(e.g. a raw data holder and click) are not added to two different list of structures.
|
||||
boolean[] alreadyStruct = new boolean[dataUnits.size()];
|
||||
|
||||
//iterate through possible export functions.
|
||||
for (int i=0; i<mlDataUnitsExport.size(); i++){
|
||||
|
||||
@ -78,21 +84,34 @@ public class MLDetectionsManager {
|
||||
|
||||
if (n==0) continue; //no need to do anything else. There are no data units of this type.
|
||||
|
||||
//create a structure for each type of data unit.
|
||||
MLStructure mlStructure= new MLStructure(mlDataUnitsExport.get(i).getName(), new int[]{n, 1});
|
||||
float sampleRate = -1;
|
||||
|
||||
n=0;
|
||||
//allocate the class now.
|
||||
for (int j=0; j<dataUnits.size(); j++){
|
||||
//check whether the same.
|
||||
if (mlDataUnitsExport.get(i).getUnitClass().isAssignableFrom(dataUnits.get(j).getClass())) {
|
||||
if (mlDataUnitsExport.get(i).getUnitClass().isAssignableFrom(dataUnits.get(j).getClass()) && !alreadyStruct[j]) {
|
||||
mlStructure=mlDataUnitsExport.get(i).detectionToStruct(mlStructure, dataUnits.get(j), n);
|
||||
sampleRate = dataUnits.get(j).getParentDataBlock().getSampleRate();
|
||||
n++;
|
||||
alreadyStruct[j] = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (n>=1) list.add(mlStructure);
|
||||
|
||||
if (n>=1) {
|
||||
list.add(mlStructure);
|
||||
list.add(new MLDouble((mlDataUnitsExport.get(i).getName()+"_sR"), new double[] {sampleRate}, 1));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//now ready to save.
|
||||
return list;
|
||||
|
||||
|
@ -139,13 +139,13 @@ public class MLExportOverlayMenu extends ExportOverlayMenu {
|
||||
//create the filename;
|
||||
long millisStart=foundDataUnits.getFirstTimeMillis();
|
||||
String currentPath = PamCalendar.formatFileDateTime(millisStart, false);
|
||||
//add data types to the filen,ae
|
||||
for (int i=0 ;i<mlData.size(); i++ ){
|
||||
//add data types to the filename
|
||||
for (int i=0 ;i<mlData.size(); i=i+2 ){//bit of a hack but every second name is the samplerate so leave that out of filename.
|
||||
currentPath=currentPath + "_" + mlData.get(i).getName();
|
||||
}
|
||||
//add correct file type.
|
||||
currentPath = currentPath + ".mat";
|
||||
currentPath = currentFolder+"\\"+currentPath;
|
||||
currentPath = currentFolder+ File.separator +currentPath;
|
||||
|
||||
//now write the file
|
||||
try {
|
||||
|
@ -64,7 +64,7 @@ public class MLRawExport extends MLDataUnitExport<PamDataUnit>{
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pam_data_units";
|
||||
return "raw_data_units";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -351,13 +351,15 @@ public class SQLTypes {
|
||||
* goes to the default, so it needs to be on UTC at the moment data are written to the database, not
|
||||
* just in this function.
|
||||
*/
|
||||
// return timeMillis;
|
||||
if (timeMillis == null) {
|
||||
return null;
|
||||
}
|
||||
// return PamCalendar.formatDBDateTime(timeMillis, false);
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
Timestamp ts = new UTCTimestamp(timeMillis - tz.getOffset(timeMillis));
|
||||
// TimeZone tz = TimeZone.getDefault();
|
||||
// TimeZone.setDefault(PamCalendar.defaultTimeZone);
|
||||
// Timestamp ts = new Timestamp(timeMillis - tz.getOffset(timeMillis));
|
||||
Timestamp ts = new Timestamp(timeMillis);
|
||||
// TimeZone.setDefault(tz);
|
||||
// Timestamp newTS = ts.toLocalDateTime();
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,9 @@ public class Group3DOfflineTask extends OfflineTask<PamDataUnit>{
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (group3DControl == null) {
|
||||
return null;
|
||||
}
|
||||
return group3DControl.getUnitName();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,522 @@
|
||||
<html>
|
||||
<head>
|
||||
<LINK href="../../../pamHelpStylesheet.css" type="text/css"
|
||||
rel="STYLESHEET">
|
||||
<title>Click Detector</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1 id="click-train-detector">Click Train Detector</h1>
|
||||
<h2 id="overview">Overview</h2>
|
||||
<p>When a toothed whale, bat or other echolocator uses echolocation
|
||||
for hunting or sensing their surroundings they usually produce regular
|
||||
clicks/calls which vary slowly in inter-click/call-interval,
|
||||
amplitude, bearing etc. Individual click detections can be difficult
|
||||
to classify from other random transients because recieved waveforms
|
||||
and spectra are distorted by number of factors, such as narrow beam
|
||||
profiles, frequency dependent absorption, propogation effects and
|
||||
animal behaviour. The broadband clicks of many dolphins psecies are
|
||||
especially difficult to distinguish because they are very similar to
|
||||
many other sources of transient noise, such as cavitations from ship
|
||||
propellors. However, the echolocation clicks used by toothed whales
|
||||
(and bats) are not produced in isolation - animals tend to rapidly
|
||||
produce clicks with a slowly varying inter-click-interval (ICI); there
|
||||
are very few non-biological sources which produce regular repetitive
|
||||
sound and so this provides an additional contextual dimension for
|
||||
click classification. An automated algorithm which is based on
|
||||
identifying repeating patterns of sounds therefore has the potential
|
||||
to be significantly more accurate than an algorithm based on
|
||||
identifying individual calls.</p>
|
||||
<p>The PAMGuard click train detector module is used to detect and
|
||||
then classify repeating patterns of clicks. It is designed to work
|
||||
with multiple types of acoustic data, from CPOD detections to single
|
||||
channel and multi-channel hydrophone recordings.</p>
|
||||
<h2 id="how-it-works">How it works</h2>
|
||||
<p>PAMGuard's click train detector utilises both a detection and
|
||||
classification stage to extract click trains from recordings.</p>
|
||||
<p>The detection stage is currently based on a multi hypothesis
|
||||
tracking (MHT) algorithm. This algorithm considers all possible
|
||||
combinations of transient detections creating a large hypothesis
|
||||
matrix which holds potential click trains. As more clicks are added to
|
||||
the hypothesis matrix it grows exponentially and so, to prevent a
|
||||
computer running out memory, it is regularly <em>pruned</em> to keep only
|
||||
the most likely click trains over time. The assigned likelihood of a
|
||||
click train is based on number of properties which can be defined in
|
||||
by the user. For example, a user might select, ICI, Amplitude and
|
||||
Correlation as variables to score click trains; this would mean that
|
||||
combinations of clicks with slowly changing ICI, amplitude and
|
||||
waveforms would be favoured by the algorithm and stay in the
|
||||
hypothesis matrix. Other properties such as bearing, click length and
|
||||
peak frequency can also be selected. A graphical explanation of the
|
||||
click train detection algorithm is shown in Figure 1 and a more
|
||||
detailed explanation of the be found in Macaulay (2019).</p>
|
||||
<p align="center">
|
||||
<img width="930" height="900" src="resources/mht_diagram.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>Diagram demonstrating how the click train algorithm works.
|
||||
Black dots are a set of 14 detected clicks at times t1 to t14. The
|
||||
click train algorithm begins at click 1 and creates two possible
|
||||
clicks trains, one that includes the first click (filled circle) and
|
||||
the other in which the click is not part of the click train
|
||||
(non-filled circle). The algorithm then moves to the next click and
|
||||
adds it to the hypothesis matrix. As the number of clicks increases,
|
||||
the hypothesis matrix exponentially expands in size and must be
|
||||
pruned. After a minimum of Npmin clicks (in this case 4) each track
|
||||
hypothesis (possible click train) is assigned a Χ<sup>2</sup> score.
|
||||
The track hypothesis with lowest score (defined by larger coloured
|
||||
circles) has it's branch traced back Np (in this case 3) clicks.
|
||||
Any track hypothesis which do not include the click Np steps back are
|
||||
pruned (defined by the double lines). Clicks which share no click
|
||||
associations with the first track hypothesis are then pruned and the
|
||||
process repeats until all clicks are part of a track or a maximum
|
||||
number of tracks have been considered (in this example there are two
|
||||
tracks). The algorithm then moves to the next click, adds it to the
|
||||
hypothesis matrix, assigns Χ<sup>2</sup> scores and traces the
|
||||
lowest Χ<sup>2</sup> branch Np steps back, pruning the hypothesis
|
||||
matrix again; the process repeats until the last click. Note that
|
||||
there is always a track hypothesis with no associated clicks (i.e.
|
||||
the bottom-most branch where no clicks belong to a click train). If a
|
||||
track hypothesis is confirmed and thus removed from the hypothesis
|
||||
matrix, then this track can be used to start another click train
|
||||
</em>
|
||||
</p>
|
||||
<p>The advantage of this MHT approach is that the click train
|
||||
detection module is quite general and can cope with a large variety of
|
||||
complex situations and multiple overlapping click trains. The
|
||||
disadvantage is that there are a large number of potential variables
|
||||
which can be set that affect the performance of the detector which can
|
||||
make it complex to initially set up.</p>
|
||||
<p>The subsequent classification stage attempts to classify
|
||||
detected click trains to species. Classification is currently based on
|
||||
a series of relatively simple binary classification steps but there is
|
||||
scope for machine learning approaches in future versions. The binary
|
||||
classification is based on parameters such as number of detected
|
||||
clicks, the mean and standard deviation in ICI and bearing and the
|
||||
correlation of the average spectrum of the click train with a
|
||||
predefined spectral template.</p>
|
||||
<p>A click train which has been both detected and classified is
|
||||
saved to PAMGuard's database and can be reclassified in PAMGuard's
|
||||
viewer mode.</p>
|
||||
<h2 id="configuring-the-click-train-detector">Configuring the
|
||||
click train detector</h2>
|
||||
<p>The primary settings to configure can be split into MHT Kernel
|
||||
and Χ<sup>2</sup> settings, these are all set in the primary click train
|
||||
detector dialog as shown in Figure 2.</p>
|
||||
<p align="center">
|
||||
<img width="850" height="700" src="resources/detection_pane.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The settings pane of the click train detector.</em>
|
||||
</p>
|
||||
<h3 id="mht-kernel-settings">MHT Kernel Settings</h3>
|
||||
<p>The MHT Kernel is the part of the detection algorithm which
|
||||
creates and then prunes the large hypothesis matrix which keeps a copy
|
||||
of all possible click trains. MHT Kernel settings are therefore
|
||||
important because they influence speed (a larger number of possible
|
||||
click trains in memory is more processor intensive) and the quality of
|
||||
the detections (the larger the number of possibilities the more likely
|
||||
that <em>true</em> click trains are contained in the hypothesis matrix).
|
||||
The specific settings are;</p>
|
||||
<p>
|
||||
<strong><em>Prune-back</em></strong>: The hypothesis matrix needs
|
||||
pruned so that it does not grow exponentially and cause memory issues.
|
||||
The matrix is pruned at Np (see Figure 1) previous detections i.e. if
|
||||
Np is 5 then then then the algorithm selects the most likely click
|
||||
train, moves back five detections back and discards other hypothesis
|
||||
that do not contain the combination of clicks in that branch. Thus,
|
||||
increasing the prune-back means that more hypothesis are kept at any
|
||||
one time but decreasing will lead to faster processing times as less
|
||||
combination are kept in memory.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Prune-start</em></strong>: The initial number of
|
||||
detections before the pruning process starts. This cannot be less than
|
||||
Prune-back and should generally should be set no more than 15 for 8GB
|
||||
of memory.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Max no. coasts</em></strong>: A click train is saved and
|
||||
removed the hypothesis mix once it has passed a number of tests. It
|
||||
must be over three clicks long, survive the pruning process and have
|
||||
missed the max no. coasts. A coast is when a click has been missed
|
||||
from a click train based on ICI. i.e. if the ICI is 2s and a click
|
||||
train goes for 6s without a detected click then there have been 3
|
||||
coasts. Increasing the maximum number of coasts means that click
|
||||
trains are less fragmented but can come at the cost of keeping click
|
||||
trains in the hypothesis matrix for longer which have ended.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Max no. trains</em></strong>: This is a maximum allowed
|
||||
number of trains in the hypothesis mix. Note this refers to the number
|
||||
of trains which can survive pruning - the actual number of potential
|
||||
click trains in the hypothesis mix will be much larger. Generally,
|
||||
just via pruning, the hypothesis matrix will keep itself below the max
|
||||
no. trains, however, in certain situations it can grow too large and
|
||||
requires a limit. The max no. trains therefore usually have little
|
||||
effect on results but should generally be set to less than 50 to
|
||||
ensure smooth processing
|
||||
</p>
|
||||
<h3 id="-sup-2-sup-settings">
|
||||
Χ<sup>2</sup> Settings
|
||||
</h3>
|
||||
<p>
|
||||
Χ<sup>2</sup> is a measure of the likelihood that a click train is
|
||||
from a (usually) biological source. The higher the Χ<sup>2</sup>
|
||||
value the lower the quality of a click train.
|
||||
</p>
|
||||
<p>
|
||||
The Χ<sup>2</sup> model used in the click train detector considers
|
||||
both the slowly varying properties of click trains, as well as bonus
|
||||
and penalty factors to discourage fragmentation and aliasing
|
||||
(selecting a multiple of the true ICI) of detected click trains.
|
||||
</p>
|
||||
<p>The initial basis of the model is:</p>
|
||||
<p align="center">
|
||||
<img width="550" height="100" src="resources/mht_equation.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
where <em>m</em> is the number of selected descriptors, e.g. ICI,
|
||||
amplitude, bearing etc., and <em>y(i,k)</em> is the measurement of
|
||||
descriptor <em>i</em> for click <em>k</em> in a click train with n
|
||||
associated clicks. <em>t(k+1)</em> is the measured time of a click <em>k</em>.
|
||||
Each descriptor is divided by q<sub>i</sub> which is a user tuneable
|
||||
parameter that alters the importance each descriptor has on the total
|
||||
Χ<sup>2</sup>. Ideally it should correspond to a prediction of the
|
||||
likely variance of the descriptor.
|
||||
</p>
|
||||
<p>
|
||||
The descriptors can be enabled and the variance set in the Χ<sup>2</sup>
|
||||
Settings pane. The toggle button next to each descriptor sets whether
|
||||
a descriptor is used to score a click train and the variance is then
|
||||
set using the slider or by inputting manually by clicking the settings
|
||||
cog. Increasing the variance means that the descriptor has less of an
|
||||
influence on the calculation of Χ<sup>2</sup> and decreasing means
|
||||
that the descriptor has a larger influence on Χ<sup>2</sup>. In some
|
||||
cases, clicks can be so close together that the variance is tiny and
|
||||
thus Χ<sup>2</sup> becomes huge e.g. during buzzes. A minimum
|
||||
variance value (<em>qt<sub>i</sub></em>) prevents the variance <em>(max¡(q<sub>i</sub>
|
||||
(t<sub>(k+1)</sub>-t<sub>k</sub> ),qt<sub>i</sub> )<sup>2</sup>)
|
||||
</em> from falling below very low values.
|
||||
</p>
|
||||
<p>Ideally the variance for each parameter would be calculated from
|
||||
a test dataset of manually annotated click trains e.g. by calculating
|
||||
the variance of ICI of all marked click trains.</p>
|
||||
<p align="center">
|
||||
<img width="900" height="120" src="resources/varience_pane.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>Each descriptor has a variance setting which can be changed
|
||||
by moving the slider or manually inputting data by clicking the
|
||||
settings button. Variance is multiplied by the ICI for each click
|
||||
detection because clicks closer together in time the descriptor
|
||||
values will change less. In some cases, clicks can be so close
|
||||
together that the variance is tiny and thus Χ<sup>2</sup> in Eq. 1
|
||||
becomes huge e.g. during buzzes. A Min. Error prevents the variance
|
||||
from falling below very low values.
|
||||
</em>
|
||||
</p>
|
||||
<p>The available descriptors parameters can be set in the click
|
||||
detector settings pane (Figure 3) and works as follows;</p>
|
||||
<p>
|
||||
<strong><em>IDI:</em></strong> the inter-detection-interval in
|
||||
milliseconds. The algorithm looks for slowly changes in the interval
|
||||
between detections.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Amplitude:</em></strong> the amplitude in dB re 1μPa pp.
|
||||
The algorithm looks for slowly changing amplitude values. Note that
|
||||
the algorithm is comparing the change in change in amplitude so that
|
||||
the click train algorithm is not biased against large but consistent
|
||||
changes in amplitude (e.g. due to a narrow beam profile sweeping
|
||||
across a hydrophone).
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Bearing:</em></strong> the bearing of multi-channel clicks
|
||||
in degrees. Slowly changing bearings will increase the likelihood that
|
||||
click trains are detected. Note that in a similar way to Amplitude,
|
||||
the change in change in bearing is considered so that the algorithm is
|
||||
not biased against large but consistent changes in bearings. The
|
||||
bearing parameter has some additional settings which apply a large
|
||||
penalty to clicks trains if there is a large (user-defined) jump in
|
||||
bearing.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Correlation:</em></strong> the algorithm calculates the
|
||||
peak of the cross-correlation value of subsequent clicks and looks for
|
||||
slowly changing values in the cross-correlation value. This tells the
|
||||
click train algorithm to search for clicks with consistent/slowly
|
||||
changing spectra. The correlation descriptor also has some additional
|
||||
settings which allow the user to pre-filter waveforms before
|
||||
cross-correlation. This is especially useful in removing noise from
|
||||
higher frequency detections.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Time Delays:</em></strong> the time delay between
|
||||
multi-channel clicks in milliseconds. The algorithm looks for slowly
|
||||
changing values in the time delays between multichannel clicks. This
|
||||
is useful for arrays with more than two hydrophones where an error in
|
||||
a single time delay measurement may cause a substantial error in
|
||||
bearing. Like amplitude and bearing, the time delay values are the
|
||||
change in change in time delays between subsequent clicks to ensure
|
||||
that click trains are not biased against faster changes in bearing.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Click Length:</em></strong> the length of the saved
|
||||
waveform of a click in milliseconds. This is a crude measure of the
|
||||
length of a click; however, it can be useful in helping the algorithm
|
||||
distinguish between species with long multi-modal clicks such as sperm
|
||||
whales, and much shorter broadband clicks such as dolphins.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Peak Frequency:</em></strong> the peak frequency in Hz.
|
||||
The peak frequency between subsequent clicks is used score click
|
||||
trains. This is useful for click trains with very stable peak
|
||||
frequencies such as echosounders, narrow band high frequency species
|
||||
and perhaps some beaked whale species.
|
||||
</p>
|
||||
<h3 id="advanced-sup-2-sup-settings">
|
||||
Advanced Χ<sup>2</sup> Settings
|
||||
</h3>
|
||||
<p>The descriptors used in Eq. 1 on their own do not provide a good
|
||||
score for click train detections. This is because Eq.1 can achieve the
|
||||
same score by either skipping clicks e.g. every second click in a
|
||||
click train, or by splitting click trains into smaller fragments.</p>
|
||||
<p align="center">
|
||||
<img width="500" height="350" src="resources/advanced_pane.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The advanced settings for calculating Χ^2. These parameters
|
||||
are primarily used to prevent click train aliasing and fragmentation.
|
||||
The advanced settings (see Figure 4) are a series of additional
|
||||
factors that prevent aliasing and fragmentation and work as flows.</em>
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Low ICI Bonus:</em></strong> if the median ICI of the
|
||||
possible click train is above a specified maximum value, a large
|
||||
penalty is added which effectively makes it one of the least likely
|
||||
click trains in the hypothesis matrix. If the median ICI is below the
|
||||
maximum value then Χ<sup>2</sup> = (Χ<sup>2</sup> (I/(max<sub>k</sub><em>I<sub>k</sub>))<sup>LI</sup>
|
||||
where I is the median ICI, max<sub>k</sub>
|
||||
</em>I<sub>k</sub> is the maximum ICI in the possible click train and LI is
|
||||
the low ICI Bonus constant term. This bonus term favours lower ICI
|
||||
values, preventing aliased click trains.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Long track bonus:</em></strong> add a bonus factor for
|
||||
longer click trains to prevent fragmentation. This is the total length
|
||||
of the click train in seconds divided by the total hypothesis matrix
|
||||
time in seconds L which is then multiplied so that Χ<sup>2</sup> =
|
||||
(Χ<sup>2</sup>*L)<sup>LT</sup> where LT is the long track bonus.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Coast penalty:</em></strong> add a penalty for
|
||||
'coasting' i.e. when an expected click, based on ICI, is not
|
||||
present in the click train. This penalty is multiplied by the number
|
||||
of coasts i.e. the likely number of missed clicks based on ICI
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>New Track Penalty:</em></strong> if a track hypothesis is
|
||||
newly added in the hypothesis matrix, then add a minor penalty factor.
|
||||
This is added until the number of click trains exceeds No. New Track
|
||||
Clicks
|
||||
</p>
|
||||
<h2 id="classification">Classification</h2>
|
||||
<p>The classification process attempts to assign a species
|
||||
identification to each detected click trains. Currently there is only
|
||||
one implemented classifier, a simple binary classifier which tests
|
||||
user defined parameters (e.g. IDI, bearing, spectral correlation and
|
||||
classifies each click). Classification parameters are accessed via the
|
||||
classification tab in the settings dialog.</p>
|
||||
<p>There is currently a basic spectral correlation/IDI/bearing
|
||||
classifier; more complex classifiers can be implemented in the future.
|
||||
</p>
|
||||
<p align="center">
|
||||
<img width="510" height="800" src="resources/classifier_pane.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The classifier settings. Users can add multiple classifiers
|
||||
using the + button next to the classifier tabs. Each classifier
|
||||
allows the user to choose a number of different approaches to
|
||||
classification based on the goodness of fit, inter-click interval,
|
||||
average spectra and bearings of the click trains. Users can use just
|
||||
one or all of these options and set specific parameters for each.</em>
|
||||
</p>
|
||||
<p>Users can add multiple classifiers by selecting the + button
|
||||
next to the classification tabs. Each classifier allows the user to
|
||||
choose a number of different methods for click train classification
|
||||
based on the goodness of fit, inter-click interval, average spectra
|
||||
and/or bearings of the click trains; for a click train to be
|
||||
classified it must pass all enabled methods (use toggle switches to
|
||||
enable and disable different types of classification). The different
|
||||
classification methods.</p>
|
||||
<h3 id="-sup-2-sup-threshold-classifier">
|
||||
Χ<sup>2</sup> threshold classifier
|
||||
</h3>
|
||||
<p>
|
||||
The click train is classified if it's overall Χ<sup>2</sup> value
|
||||
is lower than the set Χ<sup>2</sup> Threshold and it has more than
|
||||
Min. Clicks and the time between the first and last click is greater
|
||||
than Min. Time
|
||||
</p>
|
||||
<h3 id="idi-classifier">IDI Classifier</h3>
|
||||
<p>The click train is classified if the median/mean and standard
|
||||
deviation in the inter detection interval (IDI) between subsequent
|
||||
clicks are within user defined limits.</p>
|
||||
<h3 id="spectrum-template-classifier">Spectrum Template Classifier</h3>
|
||||
<p>The click train is classified if the average spectra of the
|
||||
click train has a correlation value above Spectrum Correlation
|
||||
Threshold with a user defined spectral template. The template can be
|
||||
set using the button on the top right of the spectrum plot – a
|
||||
default spectrum can be loaded or a spectrum can be loaded from a .mat
|
||||
or .csv file. A csv file should have the first row as the spectrum and
|
||||
first column of the second row the sample rate. A .mat file should be
|
||||
a single saved structure with sR (sample rate) and spectrum (array of
|
||||
spectrum values) fields.</p>
|
||||
<h3 id="bearing-classifier">Bearing Classifier</h3>
|
||||
<p>The click train is classified if minimum and maximum bearing
|
||||
(Bearing Limits) the average change in bearing (° Bearing Mean), the
|
||||
median change in bearing (° Bearing Median) and/or the average
|
||||
standard deviation in bearing change (° Bearing Std) are within user
|
||||
defined limits.</p>
|
||||
<h2 id="parametrising-the-classifier">Parametrising the classifier</h2>
|
||||
<p>Each classifier has a set of metadata that are added to click
|
||||
trains. This can be accessed through the tooltip or right click menus
|
||||
in various displays. For example, in the Time Base Display FX hover
|
||||
the mouse over a click train or bring the pop menu with a right click.
|
||||
Parameters such as the spectral correlation value, IDI and bearing
|
||||
information etc are displayed which allows users to get an idea of
|
||||
which values to set for the classifier. Currently this requires (like
|
||||
most PAMGuard classifiers) a trial and error approach. It is hoped
|
||||
that future update will allow manually validated data to be used to
|
||||
parametrise both the detection and classification stage of the click
|
||||
train detector.</p>
|
||||
<p align="center">
|
||||
<img width="700" height="500" src="resources/rightclickmenu.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The metadata associated with each classifier is stored with
|
||||
every click train and be accessed through right clicking on or
|
||||
hovering the mouse over a click train detection.</em>
|
||||
</p>
|
||||
<h2 id="localisation">Localisation</h2>
|
||||
<p>The click train detector can be used to localise the position of
|
||||
animals detected by the click train detector using target motion
|
||||
analysis. This generally means that the localisation capabilities are
|
||||
generally restricted to data which has been collected using towed
|
||||
hydrophone arrays.</p>
|
||||
<p align="center">
|
||||
<img width="242" height="430" src="resources/localisation1.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>Screenshot of the click train localisation settings.
|
||||
Currently, only target motion is supported.</em>
|
||||
</p>
|
||||
<p>Localisation is enabled by ticking Localise click trains. The
|
||||
type of localisation algorithm which is used is selected in the
|
||||
Localisation algorithms (See the localisation section in PAMGuard help
|
||||
for more info on localisation algorithms). Localisation using 3D
|
||||
simplex and MCMC can be processor intensive, especially when there are
|
||||
a large number of clicks in a train and so the Algorithm Limits pane
|
||||
can be used to set a maximum number of input clicks for a
|
||||
localisation. If the maximum is exceeded then clicks are sub sampled
|
||||
from the click train evenly in time.</p>
|
||||
<p>Generally, target motion localisation only works well when there
|
||||
are a large number of clicks over a long time period. The Filters tab
|
||||
allows users to select which click trains are localised and also to
|
||||
remove spurious results from unsuccessful localisations. The Pre
|
||||
Localisation Filter allows users to select a minimum number of
|
||||
detections before localisations are attempted and a minimum bearing
|
||||
change in the click train (Min Angle range). Click trains with larger
|
||||
angle ranges will generally result in higher quality localisations.</p>
|
||||
<p align="center">
|
||||
<img width="242" height="430" src="resources/localisation2.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The filter tab allows users to pre-filter which click train
|
||||
are localised.</em>
|
||||
</p>
|
||||
<p>
|
||||
The Results Filter allows for spurious localisation results to be
|
||||
deleted: any results from target motion localisation (which can have
|
||||
more than one possible localisation) which are further away than
|
||||
Maximum Range, shallower than Minimum Depth or deeper than Maximum
|
||||
Depth are discarded.<br>Running The click train detector can be
|
||||
run in real time or post processing. In real time add the module and
|
||||
it will automatically detected click trains once PAMGuard started.
|
||||
</p>
|
||||
<p align="center">
|
||||
<img width="200" height="300" src="resources/offlineprocessing.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The filter tab allows users to pre-filter which click train
|
||||
are localised.</em>
|
||||
</p>
|
||||
<p>In viewer mode, add the module and then go to Settings>Click
|
||||
Train Detector > Reanalyse click trains.This will bring up
|
||||
PAMGuard's generic data reprocessing dialog with two settings, Click
|
||||
Train Detector or Click Train Classifier. The Click Train Detector
|
||||
option will run the detection and classification algorithm again. The
|
||||
Click Train Classifier will only run the classification algorithm on
|
||||
existing detected click trains (much faster). Note that users can
|
||||
select how much data to reprocess in the Data dropdown menu. All
|
||||
Data means the entire dataset will be reprocessed, Loaded Data means
|
||||
just the current data loaded in the display (all scrollable data),
|
||||
Select Data allows the user to define two time limits between which
|
||||
all data is reprocessed.</p>
|
||||
<h2 id="visualising-results">Visualising Results</h2>
|
||||
<p>The results from the click train detector can be visualised in a
|
||||
variety of displays in PAMGuard.</p>
|
||||
<h3 id="click-bearing-time-display">Click bearing time display</h3>
|
||||
<p>By default, clicks trains will be shown in the Click Detector
|
||||
Module's in built bearing time display. Different click trains are
|
||||
represented as different colours. Note that you must right click on
|
||||
the display and select Colour by Click Trains</p>
|
||||
<p align="center">
|
||||
<img width="940" height="500" src="resources/clicktrain_BT.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>The results of the click train detector displayed on the
|
||||
bearing time display. Different colours correspond to different click
|
||||
trains.</em>
|
||||
</p>
|
||||
<h3 id="time-display-fx">Time Display FX</h3>
|
||||
<p>The Time Display FX is a more modern display which allows any
|
||||
time-based data to be plotted together on a large variety of y-axis
|
||||
(e.g., frequency, bearing, amplitude etc.). Click trains will be
|
||||
plotted on the time-based display by adding Click detections to the
|
||||
display and then using the right</p>
|
||||
<p align="center">
|
||||
<img width="940" height="500" src="resources/clicktrain_TDFX.png">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<em>Click train data displayed in the time display FX. Users can
|
||||
right click on click trains to view average spectra and waterfall
|
||||
spectrograms (shown here in top right).</em>
|
||||
</p>
|
||||
|
||||
|
||||
<p CLASS="previousLink">
|
||||
<a href=""></a>
|
||||
</p>
|
||||
<p CLASS="nextLink">
|
||||
<a href="ClickDetector_clickDetectorDisplays.html">Next: Click
|
||||
Detector Displays </a>
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 254 KiB |
After Width: | Height: | Size: 596 KiB |
After Width: | Height: | Size: 891 KiB |
After Width: | Height: | Size: 580 KiB |
After Width: | Height: | Size: 690 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 504 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 556 KiB |
After Width: | Height: | Size: 101 KiB |
@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
|
||||
import com.jmatio.io.MatFileReader;
|
||||
import com.jmatio.types.MLDouble;
|
||||
import com.jmatio.types.MLStructure;
|
||||
|
||||
|
||||
/**
|
||||
@ -25,23 +26,114 @@ public class ImportTemplateMAT implements TemplateImport {
|
||||
|
||||
@Override
|
||||
public MatchTemplate importTemplate(File filePath){
|
||||
|
||||
errorCode = 0; //reset error code
|
||||
try {
|
||||
//System.out.println("Import MAT file waveform");
|
||||
|
||||
//the matlab file reader.
|
||||
//the MATLAB file reader.
|
||||
mfr = new MatFileReader(filePath);
|
||||
MLDouble sampleRateML = (MLDouble) mfr.getMLArray("sR");
|
||||
|
||||
MatchTemplate matchTemplate;
|
||||
|
||||
|
||||
MLDouble waveformML = (MLDouble) mfr.getMLArray("waveform");
|
||||
if (waveformML==null) waveformML = (MLDouble) mfr.getMLArray("spectrum"); //might be a spectrum
|
||||
//try standard template first
|
||||
matchTemplate = getTemplateStandard(mfr);
|
||||
|
||||
if (sampleRateML==null || waveformML==null) {
|
||||
//use the first click from a structure of clicks.
|
||||
if (matchTemplate == null && errorCode==0) {
|
||||
matchTemplate = getTemplateStruct(mfr);
|
||||
}
|
||||
|
||||
//if the match template is still null then the file is the wrong format.
|
||||
if (matchTemplate == null && errorCode==0) {
|
||||
errorCode=TemplateImport.INCORRECT_FILE_FORMAT;
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
matchTemplate.name = filePath.getName();
|
||||
|
||||
return matchTemplate;
|
||||
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a template which has a simple sR and waveform field.
|
||||
* @param mfr - .mat file reader.
|
||||
* @return the match template.
|
||||
*/
|
||||
private MatchTemplate getTemplateStruct(MatFileReader mfr2) {
|
||||
|
||||
//the MATLAB file reader.
|
||||
MLStructure clicksStruct = (MLStructure) mfr.getMLArray("clicks");
|
||||
MLDouble sampleRateML = (MLDouble) mfr.getMLArray("clicks_sR");
|
||||
|
||||
|
||||
|
||||
if (clicksStruct==null) {
|
||||
clicksStruct = (MLStructure) mfr.getMLArray("raw_data_units");
|
||||
sampleRateML = (MLDouble) mfr.getMLArray("raw_data_units_sR");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (sampleRateML==null || clicksStruct==null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MLDouble waveML = (MLDouble) clicksStruct.getField("wave", 0);
|
||||
double[][] waveform = waveML.getArray();
|
||||
|
||||
|
||||
MatchTemplate matchedTemplate = new MatchTemplate(null, waveform[0], (float) sampleRateML.get(0).doubleValue());
|
||||
|
||||
|
||||
return matchedTemplate;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a template which has a simple sR and waveform field.
|
||||
* @param mfr - .mat file reader.
|
||||
* @return the match template.
|
||||
*/
|
||||
private MatchTemplate getTemplateStandard(MatFileReader mfr) {
|
||||
|
||||
//System.out.println("Import MAT file waveform");
|
||||
|
||||
|
||||
|
||||
//the MATLAB file reader.
|
||||
MLDouble sampleRateML = (MLDouble) mfr.getMLArray("sR");
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sr"); //try a different name for the sample rate
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("fs"); //try a different name for the sample rate
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("samplerate"); //try a different name for the sample rate
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sample_rate"); //try a different name for the sample rate
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sampleRate"); //try a different name for the sample rate
|
||||
if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("clicks_sR"); //try a different name for the sample rate
|
||||
|
||||
|
||||
//get the waveform or spectrum
|
||||
MLDouble waveformML = (MLDouble) mfr.getMLArray("waveform");
|
||||
|
||||
if (waveformML==null) waveformML = (MLDouble) mfr.getMLArray("wave"); //try a different name for the waveform
|
||||
if (waveformML==null) waveformML = (MLDouble) mfr.getMLArray("spectrum"); //might be a spectrum
|
||||
|
||||
|
||||
if (sampleRateML==null || waveformML==null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//import a wave in column or row dimension
|
||||
int size = Math.max(waveformML.getM(), waveformML.getN());
|
||||
@ -55,23 +147,15 @@ public class ImportTemplateMAT implements TemplateImport {
|
||||
for (int i = 0 ; i<size; i++) {
|
||||
waveform[i]= waveformML.getM()>waveformML.getN() ? waveformML.get(i, 0) : waveformML.get(0, i);
|
||||
}
|
||||
float sR= new Double(sampleRateML.getArray()[0][0]).floatValue();
|
||||
float sR= Float.valueOf((float) sampleRateML.getArray()[0][0]);
|
||||
|
||||
|
||||
//now create waveform
|
||||
// System.out.println("Create a waveform with " + waveform.length + " samples with a sample rate of "
|
||||
// + sR + " Hz");
|
||||
MatchTemplate matchedTemplate = new MatchTemplate(filePath.getName(), waveform, sR);
|
||||
MatchTemplate matchedTemplate = new MatchTemplate(null, waveform, sR);
|
||||
return matchedTemplate;
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class MatchTemplate implements RawDataHolder, Serializable, Cloneable, Ma
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " sR: " + sR + " waveform length: " + waveform + " " + name;
|
||||
return super.toString() + " sR: " + sR + " waveform length: " + waveform == null ? "null waveform" : waveform.length + " " + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -114,7 +114,7 @@ public class MatchedTemplateParams implements Serializable, Cloneable, ManagedPa
|
||||
/**
|
||||
* The number of bins to sample around.
|
||||
*/
|
||||
public int restrictedBins=128;
|
||||
public int restrictedBins=2048;
|
||||
|
||||
/**
|
||||
* True to search for for wave peak and sample around it.
|
||||
|
@ -367,10 +367,11 @@ public class MTClassifierPane extends SettingsPane<MTClassifier> {
|
||||
String extension = getFileExtension(file);
|
||||
for (int i=0; i< this.templateImporters.size(); i++) {
|
||||
for (int j=0; j<templateImporters.get(i).getExtension().length; j++) {
|
||||
//System.out.println(templateImporters.get(i).getExtension()[j] + " : " + extension);
|
||||
System.out.println(templateImporters.get(i).getExtension()[j] + " : " + extension);
|
||||
if (templateImporters.get(i).getExtension()[j].equals(extension)) {
|
||||
//System.out.println("Import using the extensions: " + extension);
|
||||
// System.out.println("Import using the extensions: " + extension);
|
||||
template=templateImporters.get(i).importTemplate(file);
|
||||
// System.out.println("Match template 3: " + template) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
110
src/matchedTemplateClassifer/matched_click_classifer_help.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Matched click classifier
|
||||
|
||||
## Overview
|
||||
|
||||
The matched click classifier is an alternative to the in built click classifier in the click detection module which uses two click templates (target and reject) to classify individual clicks detections from the click detection module. The idea behind this classifier is to more accurately classify rarer clicks when there
|
||||
|
||||
## How it works
|
||||
|
||||
The classifier is based on a matched filter i.e. a candidate click detection is compared to a template and it's maximum correlation value with the template is then used for classification. Each click is compared to both a _match_ and a _reject_ template. If the difference between the correlation match to the reject template exceeds a certain threshold, then the click is classified. There can be multiple combinations of match to reject templates allowing the matched classifier to deal with different types of clicks or even clicks from multiple species.
|
||||
|
||||
|
||||
## Configuring the matched click classifier
|
||||
|
||||
The matched click classifier settings are accessed via **Settings-> Matched click classifier**_. The settings are split into the three sections, general settings, click waveform and click templates.
|
||||
|
||||
<p align="center">
|
||||
<img width="950" height="520" src = "resources/matched_click_dialog_summary.png">
|
||||
</p>
|
||||
|
||||
_The settings pane of the matched click classifier_
|
||||
|
||||
### General Settings
|
||||
|
||||
The general settings allow for channel options, species ID and the default colours for classified clicks to be to be set.
|
||||
_
|
||||
Channel Options _allows users to define whether a single click from one channel, all clicks or an averaged click should be used to be used for classification in multi-channel situations. if there is only one channel then this makes no difference.
|
||||
|
||||
_Click Type_ sets the number that defines the species ID. Make sure this is not the same as any of the species IDs in the default click classifier (this is why the default is set so high).
|
||||
|
||||
_Symbol_ and _Fill_ define the default colours clicks which have been classified by the matched click classifier should be plotted on displays.
|
||||
|
||||
### Click Waveform Settings
|
||||
|
||||
Before a click is classified it undergoes some pre-conditioning by the matched click classifier.
|
||||
|
||||
_Restrict parameter extraction to XX samples_ sets the maximum length of the waveform to the classifier. If this is selected, then center of the click is located and samples trimmed around the center. In some use cases, for example SoundTrap detections, which may be 10,000 samples long, setting a lower number of maximum samples can greatly increase processing speed and improve the accuracy of results. Note that if the number of samples is set too low (e.g. well below the click template length) then the correaltion values will not make much sense.
|
||||
|
||||
_Peak threshold_ and _Smoothing_ are both parameters used to find the click center so that a click can be trimmed to the maximum number of samples. The click length is measured by calculating the waveform envelope using a Hilbert Transform. The envelope is smoothed using a moving average filter (the _Smoothing_ parameter defines the size of the averaging window). The click is trimmed as follows. First the peak of the waveform envelope is found. The length of the click is defined as the point at which the click falls _Peak threshold_ dB below the peak. The center of the click is then the middle of this snippet. The click is then trimmed from the center of the click.
|
||||
|
||||
_Amplitude normalisation_ If there is a very loud click compared to a template it's correlation score will be different to that of a very quiet click of with exactly the same waveform. It is therefore a good idea to normalise the waveform before it is compared the match click classifier. The types of normalisation are
|
||||
* _norm_ - this is the default - the click is divided by it's RMS amplitude.
|
||||
* _peak to peak_ this can be useful for some types of shorter click e.g. dolphins - the click is divided by it's peak to peak amplitude.
|
||||
* _none_ no normalisation (not recommended).
|
||||
|
||||
### Template settings
|
||||
|
||||
The matched click classifier has two templates, a match and a reject. The match template should match a stereotypical click of the target species and the reject template can either be a click from a confounding species (e.g. dolphin) or blank. Selecting the drop-down menu from the _Import_ button allows for a selection of some default templates. Custom templates can be imported by selecting the _Import_ button. Custom templates can either be a .csv. or .mat file using the following format.
|
||||
|
||||
**.csv** - the first _row_ are the waveform measurements from -1 to 1 (make sure you save with a sufficient number of decimal points). The second row and first column is the sample rate in sample per second.
|
||||
|
||||
**.mat** - a .mat file that contains two variables named _sR_ and _waveform_. _sR_ is the sample rate in samples per second and _waveform_ is a 1D array containing the waveform for the template form -1 to 1. Arrays of click structures imported using the PAMGuard to MATLAB library can also be used. The first channel of the first click in a list of clicks structures will be imported as the template. The click structure should be names _clicks_ and the sample rate should be saved as a seperate variable named _sR_ in sampes per second.
|
||||
|
||||
The match and reject templates are plotted to provide a user with some visualisation of the classifier settings - the drop down menu allows the user to select different ways to plot the templates and is purely for visualisation purposes so makes no difference to classifier settings.
|
||||
|
||||
_threshold_ is the threshold at which a click is classified. If the difference between the match and reject templates is above the _threshold_ value then the
|
||||
click is classified.
|
||||
|
||||
The + button can be used to add more tabs. Each tab contains a click/reject template pair and unique threshold setting. A click is classified if at least one of the match/reject templates passes it's set threshold.
|
||||
|
||||
## Visualising Results
|
||||
|
||||
The matched click classifier changes the species type flag of a click if at least one of the classifiers passes threshold. This means clicks can be visualised in the click bearing time display or the time base display. The classifier also saves the correlation values for each match/reject template pair which can be accessed in MATLAB/R or through the PAMGuard GUI.
|
||||
|
||||
### Click bearing time display
|
||||
|
||||
Matched clicks can be viewed in the bearing time display. If a click passes the threshold of one match/reject pair then the click symbol (defined in general settings) is shown in the bearing time display if _Colour by Matched Template Classifier_ is selected in the right click menu. The correlation values are shown by hovering the mouse over a click to bring up the info tool tip.
|
||||
|
||||
<p align="center">
|
||||
<img width="950" height="550" src = "resources/matched_click_bt_display.png">
|
||||
</p>
|
||||
|
||||
_Screenshot of clicks classified from the matched click classifier showing matched clicks (coloured pink)_
|
||||
|
||||
### Time base display
|
||||
|
||||
The time base display FX can show clicks classified by the matched clicks classifier. _Colour by Matched Template Classifier_ must be selecting in the right settings window.
|
||||
|
||||
<p align="center">
|
||||
<img width="950" height="520" src = "resources/matched_click_tdisplay_example.png">
|
||||
</p>
|
||||
|
||||
_Screenshot of Time Base display FX showing clicks classified by the match click classifier (coloured pink). The correlation values can be found in the meta data section of the detection pop up menu (highlighted)_
|
||||
|
||||
Note that the time base display allows users to export clicks to be used as templates. Using the advanced pop up menu right click on a click detection and select the MATLAB icon. A .mat file of the selected click or clicks will be saved to your root user folder in a folder called _PAMGuard Manual Export_. This .mat file can be opened by the matched click classifier - the first channel of the first click in the list will be imported as a template.
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img width="510" height="300" src = "resources/exporting_mat_clicks.png">
|
||||
</p>
|
||||
|
||||
|
||||
### Extracting correlation values using MATLAB/R
|
||||
|
||||
Clicks are loaded from binary files using the MATLAB/R function
|
||||
|
||||
```
|
||||
clicks = loadPamguardBinaryFile("/path/to/pamguardfile.pgdf");
|
||||
```
|
||||
|
||||
where clicks is a list of MATLAB/R structures containing the data for each click in the file.
|
||||
|
||||
The matched click classifier template threshold, match value and reject value are then accessed from each click using
|
||||
|
||||
```
|
||||
matchedtemplatevals = clicks(1).annotations. mclassification
|
||||
```
|
||||
|
||||
```matachedtemplatevals``` is a list of where each row is the threshold value, match value and reject value for each match/reject template pair.
|
||||
|
||||
|
BIN
src/matchedTemplateClassifer/resources/exporting_mat_clicks.png
Normal file
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 702 KiB |
After Width: | Height: | Size: 629 KiB |
@ -68,8 +68,12 @@ public abstract class DLModelWorker<T> {
|
||||
double[][] transformedData2; //spec data
|
||||
double[] transformedData1; //waveform data
|
||||
for (int j=0; j<numChunks; j++) {
|
||||
|
||||
|
||||
|
||||
soundData = new AudioData(rawDataUnits.get(j).getRawData()[iChan], sampleRate);
|
||||
|
||||
|
||||
|
||||
// for (int i=0; i<modelTransforms.size(); i++) {
|
||||
// System.out.println("Transfrom type: " + modelTransforms.get(i).getDLTransformType());
|
||||
// }
|
||||
|