More updates to get DelphinID working

This commit is contained in:
Jamie Mac 2024-05-02 16:00:21 +01:00
parent fa6991eb80
commit ee694146c3
9 changed files with 266 additions and 30 deletions

View File

@ -32,6 +32,7 @@ import pamViewFX.fxNodes.PamSpinner;
import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.PamVBox;
import pamViewFX.validator.PamValidator; import pamViewFX.validator.PamValidator;
import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.DLClassiferModel;
import rawDeepLearningClassifier.dlClassification.StandardClassifierModel;
/** /**
* Settings pane for SoundSpot * Settings pane for SoundSpot
@ -163,7 +164,7 @@ public abstract class StandardModelPane extends SettingsPane<StandardModelParams
defaultSegmentLenChanged(); defaultSegmentLenChanged();
//only set the hop if the user physically changes the toggle switch. This is not included in defaultSegmentLenChanged //only set the hop if the user physically changes the toggle switch. This is not included in defaultSegmentLenChanged
//becuase defaultSegmentLenChanged can be called from elsewhere //becuase defaultSegmentLenChanged can be called from elsewhere
int defaultsamples = getDefaultSamples(); int defaultsamples = getDefaultSamples(dlClassifierModel, paramsClone);
dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2); dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2);
}); });
usedefaultSeg.setPadding(new Insets(0,0,0,0)); usedefaultSeg.setPadding(new Insets(0,0,0,0));
@ -269,7 +270,7 @@ public abstract class StandardModelPane extends SettingsPane<StandardModelParams
// float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); // float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate();
int defaultsamples = getDefaultSamples(); int defaultsamples = getDefaultSamples(dlClassifierModel, paramsClone);
//work out the window length in samples //work out the window length in samples
dlClassifierModel.getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue(defaultsamples); dlClassifierModel.getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue(defaultsamples);
@ -282,7 +283,7 @@ public abstract class StandardModelPane extends SettingsPane<StandardModelParams
} }
} }
private int getDefaultSamples() { public static int getDefaultSamples(DLClassiferModel dlClassifierModel, StandardModelParams paramsClone) {
float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate();
int defaultsamples = (int) (paramsClone.defaultSegmentLen.doubleValue()*sR/1000.0); int defaultsamples = (int) (paramsClone.defaultSegmentLen.doubleValue()*sR/1000.0);
return defaultsamples; return defaultsamples;

View File

@ -58,10 +58,11 @@ public class ArchiveModelWorker extends GenericModelWorker {
} }
/** /**
* Prepare the model * Prepare the model.
* Note it is important to put a synchonized here or the model loading can fail.
*/ */
@Override @Override
public void prepModel(StandardModelParams dlParams, DLControl dlControl) { public synchronized void prepModel(StandardModelParams dlParams, DLControl dlControl) {
//ClassLoader origCL = Thread.currentThread().getContextClassLoader(); //ClassLoader origCL = Thread.currentThread().getContextClassLoader();
try { try {
@ -198,6 +199,8 @@ public class ArchiveModelWorker extends GenericModelWorker {
* @throws IOException * @throws IOException
*/ */
public ArchiveModel loadModel(String currentPath2) throws MalformedModelException, IOException { public ArchiveModel loadModel(String currentPath2) throws MalformedModelException, IOException {
System.out.println("HELLO MODEL: " +currentPath2 );
return new SimpleArchiveModel(new File(currentPath2)); return new SimpleArchiveModel(new File(currentPath2));
} }

View File

@ -1,13 +1,26 @@
package rawDeepLearningClassifier.dlClassification.delphinID; package rawDeepLearningClassifier.dlClassification.delphinID;
import java.io.File;
import PamController.SettingsPane; import PamController.SettingsPane;
import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.Spinner;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.FontWeight; import javafx.scene.text.FontWeight;
import pamViewFX.PamGuiManagerFX;
import pamViewFX.fxGlyphs.PamGlyphDude;
import pamViewFX.fxNodes.PamHBox;
import pamViewFX.fxNodes.PamSpinner;
import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.PamVBox;
import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane;
/** /**
* Settings pane for delphin ID. * Settings pane for delphin ID.
@ -15,60 +28,141 @@ import pamViewFX.fxNodes.PamVBox;
* @author Jamie Macaulay * @author Jamie Macaulay
* *
*/ */
public class DelphinIDPane extends SettingsPane<DelphinIDParams> { public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
/** /**
* The main pane. * The main pane.
*/ */
private Pane mainPane; private Pane mainPane;
/** /**
* Reference to the delphinID classifier * Reference to the delphinID classifier
*/ */
private DelphinIDClassifier delphinUIClassifier; private DelphinIDClassifier delphinUIClassifier;
private PamSpinner<Double> detectionDensitySpinner;
private Slider decisionSlider;
private DelphinIDParams currentParams;
private File currentSelectedFile;
public DelphinIDPane(DelphinIDClassifier delphinUIClassifier) { public DelphinIDPane(DelphinIDClassifier delphinUIClassifier) {
super(null); super(null);
this.delphinUIClassifier = delphinUIClassifier; this.delphinUIClassifier = delphinUIClassifier;
mainPane = createPane(); mainPane = createPane();
} }
private Pane createPane() { private Pane createPane() {
//font to use for title labels. //font to use for title labels.
Font font= Font.font(null, FontWeight.BOLD, 11); Font font= Font.font(null, FontWeight.BOLD, 11);
Node classifierIcon; Label classifierIcon;
classifierIcon = delphinUIClassifier.getModelUI().getIcon(); classifierIcon = new Label("DelphinID");
PamGuiManagerFX.titleFont2style(classifierIcon);
//todo - will need to figure out colour of icon using CSS.
Node icon = PamGlyphDude.createPamIcon("mdi2r-rss", Color.BLACK, PamGuiManagerFX.iconSize);
icon.getStyleClass().add(getName());
icon.setRotate(45);
classifierIcon.setGraphic(icon);
classifierIcon.setContentDisplay(ContentDisplay.RIGHT);
// String settings = currentParams.toString();
// classifierIcon.setTooltip(new Tooltip(settings));
PamVBox vBox = new PamVBox(); PamVBox vBox = new PamVBox();
vBox.setSpacing(5.); vBox.setSpacing(5.);
/**Classification thresholds etc to set.**/
Label detectionDensity = new Label("Detection Density");
detectionDensity.setFont(font);
String tooltip = "Set the minimum detection density to attempt to classify.";
detectionDensity.setTooltip(new Tooltip(tooltip));
detectionDensitySpinner = new PamSpinner<Double>(0.0, 1.0, 0.3, 0.1);
detectionDensitySpinner.setPrefWidth(70);
detectionDensitySpinner.setEditable(true);
detectionDensitySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
PamHBox minDensityHolder = new PamHBox();
minDensityHolder.setAlignment(Pos.CENTER_RIGHT);
minDensityHolder.setSpacing(5);
Label minDensity = new Label("Min. density");
minDensityHolder.getChildren().addAll(minDensity, detectionDensitySpinner);
/**Classification thresholds etc to set.**/ /**Classification thresholds etc to set.**/
Label classiferInfoLabel2 = new Label("Decision Threshold"); Label classiferInfoLabel2 = new Label("Decision Threshold");
classiferInfoLabel2.setTooltip(new Tooltip("Set the minimum prediciton value for selected classes. If a prediction exceeds this value " classiferInfoLabel2.setTooltip(new Tooltip("Set the minimum prediciton value for selected classes. If a prediction exceeds this value "
+ "a detection will be saved.")); + "a detection will be saved."));
classiferInfoLabel2.setFont(font); classiferInfoLabel2.setFont(font);
decisionSlider = new Slider();
vBox.getChildren().addAll(classifierIcon, classiferInfoLabel2); decisionSlider.setMin(0);
decisionSlider.setMax(1);
decisionSlider.setMajorTickUnit(0.2);
decisionSlider.setMinorTickCount(10);
decisionSlider.valueProperty().addListener((obsVal, oldVal, newVal)->{
classiferInfoLabel2.setText(String.format("Decision Threshold %.2f", newVal));
});
decisionSlider.setShowTickMarks(true);
decisionSlider.setShowTickLabels(true);
vBox.getChildren().addAll(classifierIcon, detectionDensity, minDensityHolder, classiferInfoLabel2, decisionSlider);
return vBox; return vBox;
} }
@Override @Override
public DelphinIDParams getParams(DelphinIDParams currParams) { public DelphinIDParams getParams(DelphinIDParams currParams) {
// TODO Auto-generated method stub currParams.threshold = decisionSlider.getValue();
return null; currParams.minDetectionDensity = detectionDensitySpinner.getValue();
return currParams;
} }
@Override @Override
public void setParams(DelphinIDParams input) { public void setParams(DelphinIDParams input) {
// TODO Auto-generated method stub this.currentParams = input;
decisionSlider.setValue(input.threshold);
detectionDensitySpinner.getValueFactory().setValue(input.minDetectionDensity);
if (input.modelPath!=null) {
//this might
currentSelectedFile = new File(currentParams.modelPath);
//this might change the paramsClone values if the model contains pamguard compatible metadata
newModelSelected(currentSelectedFile);
}
} }
private void newModelSelected(File currentSelectedFile2) {
if (currentParams!=null && currentParams.defaultSegmentLen != null) {
//System.out.println("Defualt segment length: " + paramsClone.defaultSegmentLen);
//cannot use because, if the parent datablock has changed, samplerate will be out of date.
// int defaultsamples = (int) this.soundSpotClassifier.millis2Samples(paramsClone.defaultSegmentLen);
// float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate();
int defaultsamples = StandardModelPane.getDefaultSamples(delphinUIClassifier, currentParams);
//work out the window length in samples
delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue(defaultsamples);
// dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2);
delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().setDisable(true);
}
else {
delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().setDisable(false);
}
}
@Override @Override
public String getName() { public String getName() {
return "delphinIDParams"; return "delphinIDParams";
@ -82,7 +176,7 @@ public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
@Override @Override
public void paneInitialized() { public void paneInitialized() {
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
} }

View File

@ -8,5 +8,11 @@ public class DelphinIDParams extends StandardModelParams {
* *
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* The minimum detection density.
*/
public double minDetectionDensity = 0.3;
} }

View File

@ -48,6 +48,11 @@ public class DelphinIDWorker extends ArchiveModelWorker {
System.err.println("Error: could not find whistle2image transform in DelphinID JSON file. Model will not work."); System.err.println("Error: could not find whistle2image transform in DelphinID JSON file. Model will not work.");
this.setModel(null); // set model to null to make sure nothing works and errors are thrown this.setModel(null); // set model to null to make sure nothing works and errors are thrown
} }
dlParams.binaryClassification = new boolean[dlParams.classNames.length];
for (int i=0; i<dlParams.classNames.length; i++) {
dlParams.binaryClassification[i]=true;
}
} }

View File

@ -1,6 +1,8 @@
package rawDeepLearningClassifier.dlClassification.delphinID; package rawDeepLearningClassifier.dlClassification.delphinID;
import java.awt.Color; import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.Raster; import java.awt.image.Raster;
import java.util.ArrayList; import java.util.ArrayList;
@ -72,7 +74,7 @@ public class Whistles2Image extends FreqTransform {
for (int i=0; i<imaged.length; i++) { for (int i=0; i<imaged.length; i++) {
for (int j=0; j<imaged[0].length; j++) { for (int j=0; j<imaged[0].length; j++) {
color = raster.getPixel(i, j, color); color = raster.getPixel(i, j, color);
imaged[i][j] = color[0]/255.; //normalize imaged[i][j] = (255-color[0])/255.; //normalize
} }
} }
// //
@ -173,7 +175,7 @@ public class Whistles2Image extends FreqTransform {
* @param markerSize - the marker size in pixels * @param markerSize - the marker size in pixels
* @return an image with y axis as frequency and x axis as time. * @return an image with y axis as frequency and x axis as time.
*/ */
private BufferedImage makeScatterImage(ArrayList<double[][]> points, double[] size, double[] xlims, double[] ylims, double markerSize) { public static BufferedImage makeScatterImage(ArrayList<double[][]> points, double[] size, double[] xlims, double[] ylims, double markerSize) {
BufferedImage canvas = new BufferedImage((int) size[0], (int) size[1], BufferedImage.TYPE_INT_RGB); BufferedImage canvas = new BufferedImage((int) size[0], (int) size[1], BufferedImage.TYPE_INT_RGB);
@ -187,9 +189,13 @@ public class Whistles2Image extends FreqTransform {
x = ((points.get(j)[i][0]-xlims[0])/(xlims[1]-xlims[0]))*size[0]; x = ((points.get(j)[i][0]-xlims[0])/(xlims[1]-xlims[0]))*size[0];
y = ((points.get(j)[i][1]-ylims[0])/(ylims[1]-ylims[0]))*size[1]; y = ((points.get(j)[i][1]-ylims[0])/(ylims[1]-ylims[0]))*size[1];
// System.out.println("Fill oval: x" + x + " y: " + y + " time: " + points.get(j)[i][0]); //System.out.println("Fill oval: x" + x + " y: " + y + " time: " + points.get(j)[i][0]);
Graphics2D g2 = (Graphics2D) canvas.getGraphics();
canvas.getGraphics().fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize);
} }
} }
@ -206,6 +212,7 @@ public class Whistles2Image extends FreqTransform {
public double[] size; public double[] size;
} }

View File

@ -60,7 +60,17 @@ public class SegmenterProcess extends PamProcess {
/** /**
* Holds groups of data units which are within a defined segment. * Holds groups of data units which are within a defined segment.
*/ */
private SegmenterGroupDataBlock segmenterGroupDataBlock; private SegmenterGroupDataBlock segmenterGroupDataBlock;
/**
* The first clock update - segments for detection groups (not raw sound data) are referenced from this.
*/
private long firstClockUpdate;
/**
* The current segmenter detection group.
*/
private SegmenterDetectionGroup segmenterDetectionGroup = null;
public SegmenterProcess(DLControl pamControlledUnit, PamDataBlock parentDataBlock) { public SegmenterProcess(DLControl pamControlledUnit, PamDataBlock parentDataBlock) {
@ -111,7 +121,7 @@ public class SegmenterProcess extends PamProcess {
*/ */
@Override @Override
public ArrayList getCompatibleDataUnits(){ public ArrayList getCompatibleDataUnits(){
return new ArrayList<Class<? extends PamDataUnit>>(Arrays.asList(RawDataUnit.class, ClickDetection.class, ClipDataUnit.class)); return new ArrayList<Class<? extends PamDataUnit>>(Arrays.asList(RawDataUnit.class, ClickDetection.class, ClipDataUnit.class, ConnectedRegionDataUnit.class));
} }
@ -177,6 +187,8 @@ public class SegmenterProcess extends PamProcess {
if (rawDataBlock==null) return; if (rawDataBlock==null) return;
setParentDataBlock(rawDataBlock); setParentDataBlock(rawDataBlock);
this.firstClockUpdate = -1;
} }
@ -232,10 +244,27 @@ public class SegmenterProcess extends PamProcess {
//TODO //TODO
//this contains no raw data so we are branching off on a completely different processing path here. //this contains no raw data so we are branching off on a completely different processing path here.
//Whislte data units are saved to a buffer and then fed to the deep learning algorohtm //Whislte data units are saved to a buffer and then fed to the deep learning algorithms
if (segmenterDetectionGroup==null) {
//iterate until we find the correct time
long segmentStart = firstClockUpdate;
while() {
}
}
} }
public void masterClockUpdate(long milliSeconds, long sampleNumber) {
super.masterClockUpdate(milliSeconds, sampleNumber);
if (firstClockUpdate<0) {
firstClockUpdate = milliSeconds;
}
}
/** /**

View File

@ -0,0 +1,91 @@
package test.rawDeepLearningClassifier;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import org.jamdev.jdl4pam.utils.DLMatFile;
import org.junit.jupiter.api.Test;
import rawDeepLearningClassifier.dlClassification.delphinID.Whistles2Image;
import us.hebi.matlab.mat.format.Mat5;
import us.hebi.matlab.mat.types.MatFile;
import us.hebi.matlab.mat.types.Matrix;
public class DelphinIDTest {
@Test
public void whistle2ImageTest() {
System.out.println("Whislte2Image test started");
/**
* Test whether the Whistles2Image transform works properly
*/
String relMatPath = "./src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat";
Path path = Paths.get(relMatPath);
// Create MAT file with a scalar in a nested struct
try {
MatFile matFile = Mat5.readFromFile(path.toString());
Matrix array = matFile.getArray("tfvalues");
//the values for the whistle detector.
double[][] whistleValues = DLMatFile.matrix2array(array);
//the image after compression
array = matFile.getArray("image1compressedgrayscale");
double[][] compressedWhistleImage = DLMatFile.matrix2array(array);
//the whistle2Image transform image
array = matFile.getArray("image1originalgrayscalenorm");
double[][] whislteImage = DLMatFile.matrix2array(array);
//now perform the image transform in Java
double[] freqLimits = new double[] {0., 20000.};
double[] size = new double[] {680., 480.};
ArrayList<double[][]> whistleImageArr = new ArrayList<double[][]>();
whistleImageArr.add(whistleValues);
BufferedImage canvas = Whistles2Image.makeScatterImage(whistleImageArr, size, new double[]{48, 48. + 4.}, freqLimits, 5.);
double[][] imaged = new double[(int) size[0]][(int) size[1]];
float[] color = new float[3];
Raster raster = canvas.getData();
for (int i=0; i<imaged.length; i++) {
for (int j=0; j<imaged[0].length; j++) {
color = raster.getPixel(i, j, color);
imaged[i][j] = (255-color[0])/255.; //normalize
}
}
//now save this image to a MATFILE
// Create MAT file with a scalar in a nested struct
MatFile matFileWrite = Mat5.newMatFile()
.addArray("image1originalgrayscalenorm",DLMatFile.array2Matrix(imaged));
// Serialize to disk using default configurations
Mat5.writeToFile(matFileWrite, "C:\\Users\\Jamie Macaulay\\MATLAB Drive\\MATLAB\\PAMGUARD\\deep_learning\\delphinID\\whistle_image_example_java.mat");
System.out.println("Whislte2Image test finished");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
assertEquals(false, false);
}
}
}