mirror of
https://github.com/PAMGuard/PAMGuard.git
synced 2024-11-21 14:42:27 +00:00
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:
parent
2d207a15c3
commit
cee21dbc3d
Binary file not shown.
After Width: | Height: | Size: 160 KiB |
Binary file not shown.
@ -129,7 +129,7 @@ as classification variables.</p>
|
||||
<br>
|
||||
<h3><a name="rocca_ParamsClassifier">Contour/Classifier
|
||||
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"> <br>
|
||||
<img alt="" src="images/rocca_params_contour-classifier6.png" border="0"> <br>
|
||||
<ol>
|
||||
<li>
|
||||
<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 ‘ambiguous’.</p>
|
||||
</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
|
||||
parameter to use when <a href="rocca_ContourExtractionManipulation.html">extracting a
|
||||
whistle
|
||||
|
@ -341,8 +341,8 @@ public class RoccaClassifyThis {
|
||||
contourStats.put(RoccaContourStats.ParamIndx.INFLMEANDELTA, Double.parseDouble(dataArray[56]));
|
||||
contourStats.put(RoccaContourStats.ParamIndx.INFLSTDDEVDELTA, Double.parseDouble(dataArray[57]));
|
||||
contourStats.put(RoccaContourStats.ParamIndx.INFLMEDIANDELTA, Double.parseDouble(dataArray[58]));
|
||||
//contourStats.put(RoccaContourStats.ParamIndx.INFLDUR, Double.parseDouble(dataArray[59]));
|
||||
//contourStats.put(RoccaContourStats.ParamIndx.STEPDUR, Double.parseDouble(dataArray[60]));
|
||||
contourStats.put(RoccaContourStats.ParamIndx.INFLDUR, Double.parseDouble(dataArray[59]));
|
||||
contourStats.put(RoccaContourStats.ParamIndx.STEPDUR, Double.parseDouble(dataArray[60]));
|
||||
|
||||
// Run the classifier
|
||||
roccaProcess.roccaClassifier.classifyContour2(rcdb);
|
||||
|
@ -306,6 +306,15 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
|
||||
*/
|
||||
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 */
|
||||
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;
|
||||
|
||||
|
||||
|
||||
public RoccaParameters() {
|
||||
}
|
||||
|
||||
@ -855,6 +865,17 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
|
||||
this.trimWav = trimWav;
|
||||
}
|
||||
|
||||
|
||||
public boolean isStrongWhistleDiff() {
|
||||
return strongWhistleDiff;
|
||||
}
|
||||
|
||||
|
||||
public void setStrongWhistleDiff(boolean strongWhistleDiff) {
|
||||
this.strongWhistleDiff = strongWhistleDiff;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PamParameterSet getParameterSet() {
|
||||
PamParameterSet ps = PamParameterSet.autoGenerate(this, ParameterSetType.DETECTOR);
|
||||
@ -927,4 +948,5 @@ public class RoccaParameters implements Serializable, Cloneable, ManagedParamete
|
||||
return ps;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import java.util.Vector;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ButtonGroup;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.GroupLayout;
|
||||
import javax.swing.JButton;
|
||||
@ -137,6 +138,9 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
JCheckBox ancCalcs4Clicks; // serialVersionUID=22 2015/06/13 added
|
||||
JCheckBox ancCalcs4Whistles; // serialVersionUID=22 2015/06/13 added
|
||||
JCheckBox trimWav;
|
||||
JRadioButton absStrongThreshold;
|
||||
JRadioButton diffStrongThreshold;
|
||||
ButtonGroup strongThreshold;
|
||||
JLabel outputDirLbl;
|
||||
JTextField outputDirTxt;
|
||||
JButton outputDirectoryButton;
|
||||
@ -557,6 +561,11 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
sightingThreshold = new JTextField(3);
|
||||
sightingThreshold.setMaximumSize(new Dimension(40, sightingThreshold.getHeight()));
|
||||
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);
|
||||
thresholdSubPanel.setLayout(thresholdLayout);
|
||||
thresholdLayout.setAutoCreateGaps(true);
|
||||
@ -571,6 +580,10 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
.addComponent(schoolLbl)
|
||||
.addComponent(sightingThreshold)
|
||||
.addComponent(schoolUnits))
|
||||
.addGroup(thresholdLayout.createSequentialGroup()
|
||||
.addComponent(absStrongThreshold))
|
||||
.addGroup(thresholdLayout.createSequentialGroup()
|
||||
.addComponent(diffStrongThreshold))
|
||||
);
|
||||
thresholdLayout.setVerticalGroup(
|
||||
thresholdLayout.createSequentialGroup()
|
||||
@ -582,6 +595,10 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
.addComponent(schoolLbl)
|
||||
.addComponent(sightingThreshold)
|
||||
.addComponent(schoolUnits))
|
||||
.addGroup(thresholdLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(absStrongThreshold))
|
||||
.addGroup(thresholdLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(diffStrongThreshold))
|
||||
);
|
||||
thresholdLayout.linkSize(SwingConstants.HORIZONTAL, whistleLbl, schoolLbl);
|
||||
classifierPanel.add(thresholdSubPanel);
|
||||
@ -1311,6 +1328,13 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
ancCalcs4Clicks.setSelected(roccaParameters.runAncCalcs4Clicks); // serialVersionUID=22 2015/06/13 added
|
||||
ancCalcs4Whistles.setSelected(roccaParameters.runAncCalcs4Whistles); // serialVersionUID=22 2015/06/13 added
|
||||
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",
|
||||
roccaParameters.getClassificationThreshold()));
|
||||
sightingThreshold.setText(String.format("%d",
|
||||
@ -1637,6 +1661,12 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
||||
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,
|
||||
// so catch the exception and return false to prevent exit from the dialog.
|
||||
|
@ -501,7 +501,7 @@ public class RoccaProcess extends PamProcess {
|
||||
// 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
|
||||
// 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.DURATION) < 0.005 ||
|
||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||
@ -509,7 +509,7 @@ public class RoccaProcess extends PamProcess {
|
||||
rcdb = null;
|
||||
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.DURATION) < 0.01 ||
|
||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||
@ -517,7 +517,7 @@ public class RoccaProcess extends PamProcess {
|
||||
rcdb = null;
|
||||
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.DURATION) < 0.005 ||
|
||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||
|
@ -24,6 +24,8 @@
|
||||
package rocca;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import weka.classifiers.AbstractClassifier;
|
||||
import weka.core.DenseInstance;
|
||||
@ -98,6 +100,9 @@ public class RoccaRFModel implements java.io.Serializable {
|
||||
double[] theseVotes =
|
||||
roccaClassifierModel.distributionForInstance(rcdbInst);
|
||||
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
|
||||
// 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 (treeConfClassified <
|
||||
boolean threshIsDiff = roccaClassifier.roccaControl.roccaParameters.isStrongWhistleDiff();
|
||||
if (
|
||||
(!threshIsDiff && treeConfClassified <
|
||||
((float) roccaClassifier.roccaControl.roccaParameters.getClassificationThreshold())
|
||||
/100) {
|
||||
/100)
|
||||
||
|
||||
(threshIsDiff && bigDiff <
|
||||
((float) roccaClassifier.roccaControl.roccaParameters.getClassificationThreshold())
|
||||
/100)
|
||||
){
|
||||
rcdb.setClassifiedAs(classifiedAs);
|
||||
|
||||
// otherwise, check if there is a next stage for the classified species
|
||||
|
@ -26,6 +26,7 @@ package rocca;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
@ -34,6 +35,7 @@ import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import weka.classifiers.trees.RandomForest;
|
||||
import weka.core.Instances;
|
||||
import weka.core.SerializationHelper;
|
||||
import weka.core.TechnicalInformation;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
RoccaTrainClassifier rtc = new RoccaTrainClassifier();
|
||||
|
||||
// Get a single file
|
||||
File arffFile = rtc.getArff();
|
||||
if (arffFile!=null) {
|
||||
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);
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
@ -90,6 +106,29 @@ public class RoccaTrainClassifier {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -125,12 +164,12 @@ public class RoccaTrainClassifier {
|
||||
System.out.println("Setting Options...");
|
||||
String[] options = new String[6];
|
||||
options[0] = "-I"; // number of iterations/trees
|
||||
options[1] = "10000"; // = 750
|
||||
options[1] = "750"; // = 750
|
||||
options[2] = "-K"; // number of attributes (aka mtry)
|
||||
options[3] = "5"; // = 3
|
||||
options[4] = "-S"; // seed for random number generator
|
||||
options[5] = "1"; // = 1
|
||||
|
||||
|
||||
try {
|
||||
model.setOptions(options);
|
||||
} catch (Exception ex) {
|
||||
@ -153,13 +192,33 @@ public class RoccaTrainClassifier {
|
||||
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
|
||||
// String[] curOptions = model.getOptions();
|
||||
// Enumeration test = model.listOptions();
|
||||
Instances header = new Instances(trainData,0);
|
||||
int index = arffFile.getAbsolutePath().lastIndexOf(".");
|
||||
String modelName = arffFile.getAbsolutePath().substring(0,index) + ".model";
|
||||
System.out.println("Saving Classifier..." + modelName);
|
||||
System.out.println("\nSaving Classifier..." + modelName);
|
||||
try {
|
||||
SerializationHelper.writeAll
|
||||
// ("C:\\Users\\Mike\\Documents\\Work\\Java\\WEKA\\weka vs R\\RF_8sp_54att_110whistle-subset.model",
|
||||
|
Loading…
Reference in New Issue
Block a user