diff --git a/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.png b/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.png new file mode 100644 index 00000000..aec77506 Binary files /dev/null and b/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.png differ diff --git a/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.pptx b/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.pptx new file mode 100644 index 00000000..7f15d568 Binary files /dev/null and b/src/help/classifiers/roccaHelp/docs/images/rocca_params_contour-classifier6.pptx differ diff --git a/src/help/classifiers/roccaHelp/docs/rocca_Configure.html b/src/help/classifiers/roccaHelp/docs/rocca_Configure.html index 0ef4b2de..7ee3a4b2 100644 --- a/src/help/classifiers/roccaHelp/docs/rocca_Configure.html +++ b/src/help/classifiers/roccaHelp/docs/rocca_Configure.html @@ -129,7 +129,7 @@ as classification variables.


Contour/Classifier tab

- 
+ 
  1. 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’.

  2. +

    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.

    +
  3. +
  4. Noise Sensitivity: specify the global noise sensitivity parameter to use when extracting a whistle diff --git a/src/rocca/RoccaClassifyThis.java b/src/rocca/RoccaClassifyThis.java index 87987247..694c8301 100644 --- a/src/rocca/RoccaClassifyThis.java +++ b/src/rocca/RoccaClassifyThis.java @@ -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); diff --git a/src/rocca/RoccaParameters.java b/src/rocca/RoccaParameters.java index 88478740..f092b8fa 100644 --- a/src/rocca/RoccaParameters.java +++ b/src/rocca/RoccaParameters.java @@ -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; } + } diff --git a/src/rocca/RoccaParametersDialog.java b/src/rocca/RoccaParametersDialog.java index bb3797d6..54fba28a 100644 --- a/src/rocca/RoccaParametersDialog.java +++ b/src/rocca/RoccaParametersDialog.java @@ -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. diff --git a/src/rocca/RoccaProcess.java b/src/rocca/RoccaProcess.java index 3da20d49..86e7c4c6 100644 --- a/src/rocca/RoccaProcess.java +++ b/src/rocca/RoccaProcess.java @@ -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 )) { diff --git a/src/rocca/RoccaRFModel.java b/src/rocca/RoccaRFModel.java index e7c9b208..9afb2a34 100644 --- a/src/rocca/RoccaRFModel.java +++ b/src/rocca/RoccaRFModel.java @@ -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 diff --git a/src/rocca/RoccaTrainClassifier.java b/src/rocca/RoccaTrainClassifier.java index cf75a5c6..970288a0 100644 --- a/src/rocca/RoccaTrainClassifier.java +++ b/src/rocca/RoccaTrainClassifier.java @@ -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",