updates to Rocca (#161)

* allow Rocca to run without classifiers

Fixed bug that threw an error if no classifier files were specified in Rocca Params dialog

* add rocca switch to enable dev mode

currently only shows/hides extra buttons in the Params dialog, but will
extend to more options in the future

* Fix memory issue with RoccaContourDataBlocks not being released for
garbage collection

Set RoccaContourDataBlock objects to null and stop PamObserver Timer to
force release

* Fix problem tracing whistles in Rocca spectrogram pop-up

Whistle and raw data were being cleared before the user had time to trace out the whistle, causing PAMGuard to throw an exception.  Both were already being cleared when the pop-up window is closed, so no need to do it here.

* updated for training/testing classifiers

* added option to apply whistle classification threshold as the difference between the highest and second highest vote, instead of as an absolute value

* update Rocca help files

update help files to include description of new strong whistle options

* fix typo

Fix typo in ONR Temperate Pacific classifier reference
This commit is contained in:
m2oswald 2024-09-19 15:52:14 +01:00 committed by GitHub
parent 2d207a15c3
commit cee21dbc3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 141 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -129,7 +129,7 @@ as classification variables.</p>
<br> <br>
<h3><a name="rocca_ParamsClassifier">Contour/Classifier <h3><a name="rocca_ParamsClassifier">Contour/Classifier
tab</a></h3> tab</a></h3>
<img alt="" src="file:///C:/Users/SCANS/workspace/Pamguard_BWbranch/src/help/detectors/roccaHelp/docs/images/rocca_params_contour-classifier5.png" border="0">&nbsp;<br> <img alt="" src="images/rocca_params_contour-classifier6.png" border="0">&nbsp;<br>
<ol> <ol>
<li> <li>
<p><a name="rocca_ClassifierModel">Whistle <p><a name="rocca_ClassifierModel">Whistle
@ -180,6 +180,13 @@ species in the random forest falls below this threshold, the encounter
is considered non-classifiable and is labelled as &#8216;ambiguous&#8217;.</p> is considered non-classifiable and is labelled as &#8216;ambiguous&#8217;.</p>
</li> </li>
<li> <li>
<p>The whistle/click threshold can be used in one of two ways: as an absolute value, or as the
minimum difference between the highest vote and the second-highest vote. If used as an absolute, whistles will only be classified
if the percent of trees in the random forest voting for the predicted species is higher than the selected threshold.
If used as a difference, whistles will only be classified if the highest percentage of trees in the random forest voting for the
predicted species is greater than the second-highest percentage by the threshold value.</p>
</li>
<li>
<p>Noise Sensitivity: specify the global noise sensitivity <p>Noise Sensitivity: specify the global noise sensitivity
parameter to use when <a href="rocca_ContourExtractionManipulation.html">extracting a parameter to use when <a href="rocca_ContourExtractionManipulation.html">extracting a
whistle whistle

View File

@ -341,8 +341,8 @@ public class RoccaClassifyThis {
contourStats.put(RoccaContourStats.ParamIndx.INFLMEANDELTA, Double.parseDouble(dataArray[56])); contourStats.put(RoccaContourStats.ParamIndx.INFLMEANDELTA, Double.parseDouble(dataArray[56]));
contourStats.put(RoccaContourStats.ParamIndx.INFLSTDDEVDELTA, Double.parseDouble(dataArray[57])); contourStats.put(RoccaContourStats.ParamIndx.INFLSTDDEVDELTA, Double.parseDouble(dataArray[57]));
contourStats.put(RoccaContourStats.ParamIndx.INFLMEDIANDELTA, Double.parseDouble(dataArray[58])); contourStats.put(RoccaContourStats.ParamIndx.INFLMEDIANDELTA, Double.parseDouble(dataArray[58]));
//contourStats.put(RoccaContourStats.ParamIndx.INFLDUR, Double.parseDouble(dataArray[59])); contourStats.put(RoccaContourStats.ParamIndx.INFLDUR, Double.parseDouble(dataArray[59]));
//contourStats.put(RoccaContourStats.ParamIndx.STEPDUR, Double.parseDouble(dataArray[60])); contourStats.put(RoccaContourStats.ParamIndx.STEPDUR, Double.parseDouble(dataArray[60]));
// Run the classifier // Run the classifier
roccaProcess.roccaClassifier.classifyContour2(rcdb); roccaProcess.roccaClassifier.classifyContour2(rcdb);

View File

@ -306,6 +306,15 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
*/ */
int sightingThreshold = 40; int sightingThreshold = 40;
/**
* boolean indicating whether the Strong Whistle threshold represents the difference
* between the highest vote and the second highest vote (=true) or whether it
* is used as an absolute value (=false). The original method was to use as an
* absolute value, but new testing shows that using it as a difference seems to
* work better
*/
private boolean strongWhistleDiff = false;
/** the filename template, modelled after the Ishmael template */ /** the filename template, modelled after the Ishmael template */
String filenameTemplate = "Encounter%X-%f-Channel%t-%Y%M%D_%H%m%s"; String filenameTemplate = "Encounter%X-%f-Channel%t-%Y%M%D_%H%m%s";
@ -422,6 +431,7 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
private boolean trimWav = false; private boolean trimWav = false;
public RoccaParameters() { public RoccaParameters() {
} }
@ -855,6 +865,17 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
this.trimWav = trimWav; this.trimWav = trimWav;
} }
public boolean isStrongWhistleDiff() {
return strongWhistleDiff;
}
public void setStrongWhistleDiff(boolean strongWhistleDiff) {
this.strongWhistleDiff = strongWhistleDiff;
}
@Override @Override
public PamParameterSet getParameterSet() { public PamParameterSet getParameterSet() {
PamParameterSet ps = PamParameterSet.autoGenerate(this, ParameterSetType.DETECTOR); PamParameterSet ps = PamParameterSet.autoGenerate(this, ParameterSetType.DETECTOR);
@ -927,4 +948,5 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
return ps; return ps;
} }
} }

View File

@ -43,6 +43,7 @@ import java.util.Vector;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultComboBoxModel;
import javax.swing.GroupLayout; import javax.swing.GroupLayout;
import javax.swing.JButton; import javax.swing.JButton;
@ -137,6 +138,9 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
JCheckBox ancCalcs4Clicks; // serialVersionUID=22 2015/06/13 added JCheckBox ancCalcs4Clicks; // serialVersionUID=22 2015/06/13 added
JCheckBox ancCalcs4Whistles; // serialVersionUID=22 2015/06/13 added JCheckBox ancCalcs4Whistles; // serialVersionUID=22 2015/06/13 added
JCheckBox trimWav; JCheckBox trimWav;
JRadioButton absStrongThreshold;
JRadioButton diffStrongThreshold;
ButtonGroup strongThreshold;
JLabel outputDirLbl; JLabel outputDirLbl;
JTextField outputDirTxt; JTextField outputDirTxt;
JButton outputDirectoryButton; JButton outputDirectoryButton;
@ -557,6 +561,11 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
sightingThreshold = new JTextField(3); sightingThreshold = new JTextField(3);
sightingThreshold.setMaximumSize(new Dimension(40, sightingThreshold.getHeight())); sightingThreshold.setMaximumSize(new Dimension(40, sightingThreshold.getHeight()));
JLabel schoolUnits = new JLabel("%"); JLabel schoolUnits = new JLabel("%");
absStrongThreshold = new JRadioButton("Threshold is absolute value",true);
diffStrongThreshold = new JRadioButton("Threshold is difference between highest and 2nd highest votes");
strongThreshold = new ButtonGroup();
strongThreshold.add(absStrongThreshold);
strongThreshold.add(diffStrongThreshold);
GroupLayout thresholdLayout = new GroupLayout(thresholdSubPanel); GroupLayout thresholdLayout = new GroupLayout(thresholdSubPanel);
thresholdSubPanel.setLayout(thresholdLayout); thresholdSubPanel.setLayout(thresholdLayout);
thresholdLayout.setAutoCreateGaps(true); thresholdLayout.setAutoCreateGaps(true);
@ -571,6 +580,10 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
.addComponent(schoolLbl) .addComponent(schoolLbl)
.addComponent(sightingThreshold) .addComponent(sightingThreshold)
.addComponent(schoolUnits)) .addComponent(schoolUnits))
.addGroup(thresholdLayout.createSequentialGroup()
.addComponent(absStrongThreshold))
.addGroup(thresholdLayout.createSequentialGroup()
.addComponent(diffStrongThreshold))
); );
thresholdLayout.setVerticalGroup( thresholdLayout.setVerticalGroup(
thresholdLayout.createSequentialGroup() thresholdLayout.createSequentialGroup()
@ -582,6 +595,10 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
.addComponent(schoolLbl) .addComponent(schoolLbl)
.addComponent(sightingThreshold) .addComponent(sightingThreshold)
.addComponent(schoolUnits)) .addComponent(schoolUnits))
.addGroup(thresholdLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(absStrongThreshold))
.addGroup(thresholdLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(diffStrongThreshold))
); );
thresholdLayout.linkSize(SwingConstants.HORIZONTAL, whistleLbl, schoolLbl); thresholdLayout.linkSize(SwingConstants.HORIZONTAL, whistleLbl, schoolLbl);
classifierPanel.add(thresholdSubPanel); classifierPanel.add(thresholdSubPanel);
@ -1311,6 +1328,13 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
ancCalcs4Clicks.setSelected(roccaParameters.runAncCalcs4Clicks); // serialVersionUID=22 2015/06/13 added ancCalcs4Clicks.setSelected(roccaParameters.runAncCalcs4Clicks); // serialVersionUID=22 2015/06/13 added
ancCalcs4Whistles.setSelected(roccaParameters.runAncCalcs4Whistles); // serialVersionUID=22 2015/06/13 added ancCalcs4Whistles.setSelected(roccaParameters.runAncCalcs4Whistles); // serialVersionUID=22 2015/06/13 added
trimWav.setSelected(roccaParameters.isTrimWav()); trimWav.setSelected(roccaParameters.isTrimWav());
if (roccaParameters.isStrongWhistleDiff()) {
diffStrongThreshold.setSelected(true);
absStrongThreshold.setSelected(false);
} else {
diffStrongThreshold.setSelected(false);
absStrongThreshold.setSelected(true);
}
classificationThreshold.setText(String.format("%d", classificationThreshold.setText(String.format("%d",
roccaParameters.getClassificationThreshold())); roccaParameters.getClassificationThreshold()));
sightingThreshold.setText(String.format("%d", sightingThreshold.setText(String.format("%d",
@ -1637,6 +1661,12 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
roccaParameters.setGpsSource(gpsSourcePanel.getSourceName()); roccaParameters.setGpsSource(gpsSourcePanel.getSourceName());
} }
if(diffStrongThreshold.isSelected()) {
roccaParameters.setStrongWhistleDiff(true);
} else {
roccaParameters.setStrongWhistleDiff(false);
}
// will throw an exception if the number format of any of the parameters is invalid, // will throw an exception if the number format of any of the parameters is invalid,
// so catch the exception and return false to prevent exit from the dialog. // so catch the exception and return false to prevent exit from the dialog.

View File

@ -501,7 +501,7 @@ public class RoccaProcess extends PamProcess {
// classifiers. Check if the loaded classifier model filename matches one of the classifier // classifiers. Check if the loaded classifier model filename matches one of the classifier
// names created for the project. If so, compare the click to the parameters used to prune // names created for the project. If so, compare the click to the parameters used to prune
// the datasets and exit if the click falls outside of the thresholds // the datasets and exit if the click falls outside of the thresholds
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("TemPacClick.model") && if (roccaControl.roccaParameters.roccaClickClassifierModelFilename.getName().equals("TempPacClick.model") &&
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. || (rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 || rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) { rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
@ -509,7 +509,7 @@ public class RoccaProcess extends PamProcess {
rcdb = null; rcdb = null;
return; return;
} }
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("HIClick.model") && if (roccaControl.roccaParameters.roccaClickClassifierModelFilename.getName().equals("HIClick.model") &&
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 40. || (rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 40. ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.01 || rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.01 ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) { rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
@ -517,7 +517,7 @@ public class RoccaProcess extends PamProcess {
rcdb = null; rcdb = null;
return; return;
} }
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("NWAtlClick.model") && if (roccaControl.roccaParameters.roccaClickClassifierModelFilename.getName().equals("NWAtlClick.model") &&
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. || (rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 || rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) { rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {

View File

@ -24,6 +24,8 @@
package rocca; package rocca;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import weka.classifiers.AbstractClassifier; import weka.classifiers.AbstractClassifier;
import weka.core.DenseInstance; import weka.core.DenseInstance;
@ -98,6 +100,9 @@ public class RoccaRFModel implements java.io.Serializable {
double[] theseVotes = double[] theseVotes =
roccaClassifierModel.distributionForInstance(rcdbInst); roccaClassifierModel.distributionForInstance(rcdbInst);
double treeConfClassified = theseVotes[(int) speciesNum]; double treeConfClassified = theseVotes[(int) speciesNum];
double[] dupVotes = theseVotes.clone();
Arrays.sort(dupVotes);
double bigDiff = dupVotes[dupVotes.length-1]-dupVotes[dupVotes.length-2];
// save the tree votes to rcdb. Step through the species list one at a time and // save the tree votes to rcdb. Step through the species list one at a time and
// compare to the species in the current model. When we find a match, save the // compare to the species in the current model. When we find a match, save the
@ -124,9 +129,16 @@ public class RoccaRFModel implements java.io.Serializable {
// if the vote is less than the threshold, set the class to AMBIG and exit // if the vote is less than the threshold, set the class to AMBIG and exit
if (treeConfClassified < boolean threshIsDiff = roccaClassifier.roccaControl.roccaParameters.isStrongWhistleDiff();
if (
(!threshIsDiff && treeConfClassified <
((float) roccaClassifier.roccaControl.roccaParameters.getClassificationThreshold()) ((float) roccaClassifier.roccaControl.roccaParameters.getClassificationThreshold())
/100) { /100)
||
(threshIsDiff && bigDiff <
((float) roccaClassifier.roccaControl.roccaParameters.getClassificationThreshold())
/100)
){
rcdb.setClassifiedAs(classifiedAs); rcdb.setClassifiedAs(classifiedAs);
// otherwise, check if there is a next stage for the classified species // otherwise, check if there is a next stage for the classified species

View File

@ -26,6 +26,7 @@ package rocca;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FilenameFilter;
import java.util.Date; import java.util.Date;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
@ -34,6 +35,7 @@ import javax.swing.filechooser.FileNameExtensionFilter;
import weka.classifiers.trees.RandomForest; import weka.classifiers.trees.RandomForest;
import weka.core.Instances; import weka.core.Instances;
import weka.core.SerializationHelper; import weka.core.SerializationHelper;
import weka.core.TechnicalInformation;
/** /**
* Single-stage classifier creation. Dataset must be a WEKA-formatted arff file, with the correct * Single-stage classifier creation. Dataset must be a WEKA-formatted arff file, with the correct
@ -54,10 +56,24 @@ public class RoccaTrainClassifier {
public static void main(String[] args) { public static void main(String[] args) {
RoccaTrainClassifier rtc = new RoccaTrainClassifier(); RoccaTrainClassifier rtc = new RoccaTrainClassifier();
// Get a single file
File arffFile = rtc.getArff(); File arffFile = rtc.getArff();
if (arffFile!=null) { if (arffFile!=null) {
String modelName = rtc.trainClassifier(arffFile); String modelName = rtc.trainClassifier(arffFile);
} }
// Get a folder full of files
// File arffFolder = rtc.getAllArff();
// File[] arffFiles = arffFolder.listFiles(new FilenameFilter() {
// public boolean accept(File dirFiles, String filename) {
// return filename.endsWith(".txt");
// }
// });
// for (File aFile : arffFiles) {
// String modelName = rtc.trainClassifier(aFile);
// }
//
} }
@ -91,6 +107,29 @@ public class RoccaTrainClassifier {
} }
} }
public File getAllArff() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Select directory containing training data");
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setFileHidingEnabled(true);
fileChooser.setApproveButtonText("Select");
//FileNameExtensionFilter restrict = new FileNameExtensionFilter("Only .arff files", "arff");
//FileNameExtensionFilter restrict = new FileNameExtensionFilter("Only .txt files", "txt");
//fileChooser.addChoosableFileFilter(restrict);
File arffFolder;
int state = fileChooser.showOpenDialog(null);
if (state == JFileChooser.APPROVE_OPTION) {
// load the folder
arffFolder = fileChooser.getSelectedFile();
return arffFolder;
} else {
return null;
}
}
/** /**
* Actual code to train the classifier * Actual code to train the classifier
@ -125,7 +164,7 @@ public class RoccaTrainClassifier {
System.out.println("Setting Options..."); System.out.println("Setting Options...");
String[] options = new String[6]; String[] options = new String[6];
options[0] = "-I"; // number of iterations/trees options[0] = "-I"; // number of iterations/trees
options[1] = "10000"; // = 750 options[1] = "750"; // = 750
options[2] = "-K"; // number of attributes (aka mtry) options[2] = "-K"; // number of attributes (aka mtry)
options[3] = "5"; // = 3 options[3] = "5"; // = 3
options[4] = "-S"; // seed for random number generator options[4] = "-S"; // seed for random number generator
@ -153,13 +192,33 @@ public class RoccaTrainClassifier {
return null; return null;
} }
Enumeration<?> e = model.enumerateMeasures();
System.out.print("Enumeration of classifier:");
while (e.hasMoreElements())
System.out.println("\nValue is: " + e.nextElement());
String globalInfo = model.globalInfo();
System.out.print("\n\nGlobal Info of classifier:\n");
System.out.print(globalInfo);
TechnicalInformation techRef = model.getTechnicalInformation();
System.out.print("\n\nGlobal Info of classifier:\n");
System.out.print(techRef);
String[] modeloptions = model.getOptions();
System.out.print("Options");
System.out.print(modeloptions);
// save the classifier // save the classifier
// String[] curOptions = model.getOptions(); // String[] curOptions = model.getOptions();
// Enumeration test = model.listOptions(); // Enumeration test = model.listOptions();
Instances header = new Instances(trainData,0); Instances header = new Instances(trainData,0);
int index = arffFile.getAbsolutePath().lastIndexOf("."); int index = arffFile.getAbsolutePath().lastIndexOf(".");
String modelName = arffFile.getAbsolutePath().substring(0,index) + ".model"; String modelName = arffFile.getAbsolutePath().substring(0,index) + ".model";
System.out.println("Saving Classifier..." + modelName); System.out.println("\nSaving Classifier..." + modelName);
try { try {
SerializationHelper.writeAll SerializationHelper.writeAll
// ("C:\\Users\\Mike\\Documents\\Work\\Java\\WEKA\\weka vs R\\RF_8sp_54att_110whistle-subset.model", // ("C:\\Users\\Mike\\Documents\\Work\\Java\\WEKA\\weka vs R\\RF_8sp_54att_110whistle-subset.model",