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.validator.PamValidator;
import rawDeepLearningClassifier.dlClassification.DLClassiferModel;
import rawDeepLearningClassifier.dlClassification.StandardClassifierModel;
/**
* Settings pane for SoundSpot
@ -163,7 +164,7 @@ public abstract class StandardModelPane extends SettingsPane<StandardModelParams
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
int defaultsamples = getDefaultSamples();
int defaultsamples = getDefaultSamples(dlClassifierModel, paramsClone);
dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2);
});
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();
int defaultsamples = getDefaultSamples();
int defaultsamples = getDefaultSamples(dlClassifierModel, paramsClone);
//work out the window length in samples
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();
int defaultsamples = (int) (paramsClone.defaultSegmentLen.doubleValue()*sR/1000.0);
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
public void prepModel(StandardModelParams dlParams, DLControl dlControl) {
public synchronized void prepModel(StandardModelParams dlParams, DLControl dlControl) {
//ClassLoader origCL = Thread.currentThread().getContextClassLoader();
try {
@ -198,6 +199,8 @@ public class ArchiveModelWorker extends GenericModelWorker {
* @throws IOException
*/
public ArchiveModel loadModel(String currentPath2) throws MalformedModelException, IOException {
System.out.println("HELLO MODEL: " +currentPath2 );
return new SimpleArchiveModel(new File(currentPath2));
}

View File

@ -1,13 +1,26 @@
package rawDeepLearningClassifier.dlClassification.delphinID;
import java.io.File;
import PamController.SettingsPane;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.Spinner;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
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 rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane;
/**
* Settings pane for delphin ID.
@ -15,7 +28,7 @@ import pamViewFX.fxNodes.PamVBox;
* @author Jamie Macaulay
*
*/
public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
/**
* The main pane.
@ -27,6 +40,14 @@ public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
*/
private DelphinIDClassifier delphinUIClassifier;
private PamSpinner<Double> detectionDensitySpinner;
private Slider decisionSlider;
private DelphinIDParams currentParams;
private File currentSelectedFile;
public DelphinIDPane(DelphinIDClassifier delphinUIClassifier) {
super(null);
this.delphinUIClassifier = delphinUIClassifier;
@ -35,40 +56,113 @@ public class DelphinIDPane extends SettingsPane<DelphinIDParams> {
private Pane createPane() {
//font to use for title labels.
Font font= Font.font(null, FontWeight.BOLD, 11);
Node classifierIcon;
classifierIcon = delphinUIClassifier.getModelUI().getIcon();
Label classifierIcon;
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();
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.**/
Label classiferInfoLabel2 = new Label("Decision Threshold");
classiferInfoLabel2.setTooltip(new Tooltip("Set the minimum prediciton value for selected classes. If a prediction exceeds this value "
+ "a detection will be saved."));
classiferInfoLabel2.setFont(font);
decisionSlider = new Slider();
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, classiferInfoLabel2);
vBox.getChildren().addAll(classifierIcon, detectionDensity, minDensityHolder, classiferInfoLabel2, decisionSlider);
return vBox;
}
@Override
public DelphinIDParams getParams(DelphinIDParams currParams) {
// TODO Auto-generated method stub
return null;
currParams.threshold = decisionSlider.getValue();
currParams.minDetectionDensity = detectionDensitySpinner.getValue();
return currParams;
}
@Override
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
public String getName() {
return "delphinIDParams";

View File

@ -9,4 +9,10 @@ public class DelphinIDParams extends StandardModelParams {
*/
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.");
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;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.util.ArrayList;
@ -72,7 +74,7 @@ public class Whistles2Image extends FreqTransform {
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] = 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
* @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);
@ -187,9 +189,13 @@ public class Whistles2Image extends FreqTransform {
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];
// 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]);
canvas.getGraphics().fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize);
Graphics2D g2 = (Graphics2D) canvas.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize);
}
}
@ -209,4 +215,5 @@ public class Whistles2Image extends FreqTransform {
}

View File

@ -62,6 +62,16 @@ public class SegmenterProcess extends PamProcess {
*/
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) {
super(pamControlledUnit, parentDataBlock);
@ -111,7 +121,7 @@ public class SegmenterProcess extends PamProcess {
*/
@Override
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));
}
@ -178,6 +188,8 @@ public class SegmenterProcess extends PamProcess {
setParentDataBlock(rawDataBlock);
this.firstClockUpdate = -1;
}
/*
@ -232,12 +244,29 @@ public class SegmenterProcess extends PamProcess {
//TODO
//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;
}
}
/**
* A new raw data unit.
* @param obs - the PAM observable

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);
}
}
}