mirror of
https://github.com/PAMGuard/PAMGuard.git
synced 2024-11-25 08:32:32 +00:00
Matched click classifier and deep learning unit tests
This commit is contained in:
parent
aa6d289984
commit
db11658301
@ -6,9 +6,8 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-21.0.2.13-hotspot">
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
|
@ -1,5 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/rawDeepLearningClassifer/segmenter/SegmenterProcess.java=UTF-8
|
||||
encoding//src/test=UTF-8
|
||||
encoding//src/test/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/src=UTF-8
|
||||
|
@ -1,9 +1,9 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=18
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=18
|
||||
org.eclipse.jdt.core.compiler.compliance=21
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
@ -13,4 +13,4 @@ org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=18
|
||||
org.eclipse.jdt.core.compiler.source=21
|
||||
|
@ -258,6 +258,8 @@ public class Correlations {
|
||||
correlationValue = newPeak[1];
|
||||
return newPeak[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Measure the time delay between pulses on two channels. Inputs in this case are the
|
||||
* spectrum data (most of the cross correlation is done in the frequency domain)<p>
|
||||
|
@ -1139,8 +1139,9 @@ public class PamArrayUtils {
|
||||
for (int j=0; j<matrix.getNumCols(); j++) {
|
||||
arrayRow[j] = matrix.getDouble(i, j);
|
||||
}
|
||||
arrayOut[i]=arrayRow;
|
||||
}
|
||||
return null;
|
||||
return arrayOut;
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,12 +10,17 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import pamViewFX.PamGuiManagerFX;
|
||||
import pamViewFX.fxGlyphs.PamGlyphDude;
|
||||
import pamViewFX.fxNodes.PamBorderPane;
|
||||
import pamViewFX.fxNodes.PamButton;
|
||||
import pamViewFX.fxNodes.PamVBox;
|
||||
import pamViewFX.fxNodes.hidingPane.HidingPane;
|
||||
import pamViewFX.fxNodes.internalNode.PamInternalPane;
|
||||
import pamViewFX.fxNodes.pamAxis.PamDateAxis;
|
||||
import pamViewFX.fxStyles.PamStylesManagerFX;
|
||||
import userDisplayFX.UserDisplayNodeFX;
|
||||
import userDisplayFX.UserDisplayNodeParams;
|
||||
|
||||
@ -29,6 +34,8 @@ import userDisplayFX.UserDisplayNodeParams;
|
||||
*/
|
||||
public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX {
|
||||
|
||||
private static final double HIDE_PANE_WIDTH = 400;
|
||||
|
||||
/**
|
||||
* Reference to the data map control.
|
||||
*/
|
||||
@ -59,7 +66,12 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX {
|
||||
*/
|
||||
private ScalePaneFX scalePane;
|
||||
|
||||
private PamBorderPane topHolder;
|
||||
private PamVBox settingsPane;
|
||||
|
||||
/**
|
||||
* Axis which shows the current dates
|
||||
*/
|
||||
private PamDateAxis dateAxis;
|
||||
|
||||
public DataMapPaneFX(DataMapControl dataMapControl){
|
||||
this.dataMapControl=dataMapControl;
|
||||
@ -73,50 +85,55 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX {
|
||||
|
||||
//create all the different panes,
|
||||
summaryPane = new SummaryPaneFX(dataMapControl, this);
|
||||
summaryPane.getStyleClass().add("pane");
|
||||
|
||||
scalePane=new ScalePaneFX(dataMapControl,this);
|
||||
scalePane.getStyleClass().add("pane");
|
||||
|
||||
scrollingDataPanel= new ScrollingDataPaneFX(dataMapControl, this);
|
||||
|
||||
//create top section
|
||||
topHolder=new PamBorderPane();
|
||||
topHolder.getStyleClass().add("pane");
|
||||
topHolder.setLeft(summaryPane);
|
||||
topHolder.setRight(scalePane);
|
||||
topHolder.setPadding(new Insets(10,10,10,10));
|
||||
topHolder.setPrefHeight(120);
|
||||
//create the setting spane
|
||||
settingsPane=new PamVBox();
|
||||
// settingsPane.getChildren().add(summaryPane);
|
||||
settingsPane.getChildren().add(scalePane);
|
||||
settingsPane.setPadding(new Insets(40,10,10,10));
|
||||
settingsPane.setPrefWidth(HIDE_PANE_WIDTH);
|
||||
|
||||
// //have a horizontal scroll pane
|
||||
// PamScrollPane topScrollHolder=new PamScrollPane(topHolder);
|
||||
// topScrollHolder.setPrefHeight(180);
|
||||
// topScrollHolder.setVbarPolicy(ScrollBarPolicy.NEVER);
|
||||
|
||||
|
||||
// topScrollHolder.setVbarPolicy(ScrollBarPolicy.NEVER)
|
||||
//topHolder.prefHeightProperty().bind(summaryPane.prefHeightProperty());
|
||||
|
||||
//hiding summary pane
|
||||
hidingSummaryPane=new HidingPane(Side.TOP, topHolder, this, false);
|
||||
hidingSummaryPane.getStyleClass().add("pane");
|
||||
hidingSummaryPane=new HidingPane(Side.RIGHT, settingsPane, this, true);
|
||||
hidingSummaryPane.setVisibleImmediatly(false);
|
||||
hidingSummaryPane.showHidePane(true);
|
||||
hidingSummaryPane.getStylesheets().addAll(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); //style as a settings pane.
|
||||
hidingSummaryPane.getStyleClass().add("pane-trans");
|
||||
hidingSummaryPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS());
|
||||
StackPane.setAlignment(hidingSummaryPane, Pos.TOP_RIGHT);
|
||||
hidingSummaryPane.setPrefWidth(HIDE_PANE_WIDTH);
|
||||
|
||||
//style the show button.
|
||||
showButton=hidingSummaryPane.getShowButton();
|
||||
showButton.getStyleClass().add("transparent-button-square");
|
||||
showButton.setStyle("-fx-background-radius: 0 0 10 10;");
|
||||
|
||||
// showButton.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize));
|
||||
showButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-down", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize));
|
||||
showButton.setPrefWidth(60);
|
||||
scrollingDataPanel.setTop(showButton);
|
||||
PamBorderPane.setAlignment(showButton, Pos.TOP_CENTER);
|
||||
showButton.getStyleClass().add("close-button-left");
|
||||
showButton.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS());
|
||||
|
||||
|
||||
this.setTop(hidingSummaryPane);
|
||||
this.setCenter(scrollingDataPanel);
|
||||
// showButton.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize));\
|
||||
showButton.setGraphic( PamGlyphDude.createPamIcon("mdi2c-cog", Color.WHITE, PamGuiManagerFX.iconSize));
|
||||
showButton.setPrefHeight(60);
|
||||
scrollingDataPanel.setRight(showButton);
|
||||
|
||||
StackPane.setAlignment(showButton, Pos.CENTER_RIGHT);
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getChildren().addAll(scrollingDataPanel, hidingSummaryPane, showButton);
|
||||
|
||||
dateAxis = new PamDateAxis();
|
||||
dateAxis.setMinHeight(50);
|
||||
dateAxis.prefWidthProperty().bind(scrollingDataPanel.widthProperty());
|
||||
|
||||
this.setTop(dateAxis);
|
||||
this.setCenter(stackPane);
|
||||
}
|
||||
|
||||
public void newSettings() {
|
||||
@ -153,7 +170,7 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX {
|
||||
public void newDataSources() {
|
||||
scrollingDataPanel.newDataSources();
|
||||
summaryPane.newDataSources();
|
||||
hidingSummaryPane.resetHideAnimation();
|
||||
//hidingSummaryPane.resetHideAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,16 +274,12 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX {
|
||||
* @param timeEnd - the end of loaded data in millis.
|
||||
*/
|
||||
public void selectedDataTime(Long timeStart, Long timeEnd) {
|
||||
System.out.println("SELECTED DATA TIME: " + timeStart + " " + timeEnd);
|
||||
summaryPane.setSelectedDataTime(timeStart, timeEnd);
|
||||
dateAxis.setUpperBound(timeEnd);
|
||||
dateAxis.setLowerBound(timeStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pane which holds all top controls. Sits within a hiding pane.
|
||||
* @return the pane which holds top controls and indicators.
|
||||
*/
|
||||
public PamBorderPane getTopHolder() {
|
||||
return topHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMinorDisplay() {
|
||||
|
@ -13,6 +13,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.layout.Pane;
|
||||
import pamViewFX.PamGuiManagerFX;
|
||||
import pamViewFX.fxNodes.PamBorderPane;
|
||||
@ -24,6 +25,7 @@ import pamViewFX.fxNodes.sliders.PamSlider;
|
||||
|
||||
/**
|
||||
* Allows uses to change the horizontal and vertical scales on the data map.
|
||||
*
|
||||
* @author Jamie Macaulay
|
||||
*
|
||||
*/
|
||||
@ -42,7 +44,7 @@ public class ScalePaneFX extends PamBorderPane {
|
||||
/**
|
||||
* The slider which determines time scale.
|
||||
*/
|
||||
private PamSlider timeSlider;
|
||||
private Slider timeSlider;
|
||||
|
||||
/**
|
||||
* Shows the time scale in pix/hour
|
||||
@ -74,7 +76,7 @@ public class ScalePaneFX extends PamBorderPane {
|
||||
/**
|
||||
* Holds everything.
|
||||
*/
|
||||
private PamHBox holder;
|
||||
private PamVBox holder;
|
||||
|
||||
|
||||
|
||||
@ -82,7 +84,7 @@ public class ScalePaneFX extends PamBorderPane {
|
||||
this.dataMapControl = dataMapControl;
|
||||
this.dataMapPane = dataMapPane;
|
||||
|
||||
holder=new PamHBox();
|
||||
holder=new PamVBox();
|
||||
holder.setSpacing(20);
|
||||
holder.getChildren().add(createScalePane());
|
||||
|
||||
@ -192,7 +194,10 @@ public class ScalePaneFX extends PamBorderPane {
|
||||
controlPane.add(new Label("Time"),0,1);
|
||||
|
||||
//create time slider
|
||||
timeSlider=new PamSlider(0, timeScaleChoices.length-1, 1);
|
||||
timeSlider=new Slider(0, timeScaleChoices.length-1, 1);
|
||||
timeSlider.setShowTickLabels(true);
|
||||
timeSlider.setShowTickMarks(true);
|
||||
|
||||
controlPane.add(timeSlider,1,1);
|
||||
//add listener to time slider to change datamap.
|
||||
timeSlider.valueProperty().addListener((ov, oldVal, newVal)->{
|
||||
|
@ -159,10 +159,6 @@ public class SummaryPaneFX extends PamBorderPane {
|
||||
long[] dataExtent;
|
||||
dataStorePane.getChildren().clear();
|
||||
|
||||
|
||||
this.setPrefHeight(n*30+40); //set preferred height for hiding pane.
|
||||
dataMapPaneFX.getTopHolder().setPrefHeight(n*30+40);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
dataStorePane.add( dataNames[i], 0, i);
|
||||
dataStorePane.add( dataStarts[i] , 1, i);
|
||||
|
@ -134,7 +134,6 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters
|
||||
//re-sample the waveform if the sample rate is different
|
||||
this.interpWaveformMatch=interpWaveform(this.waveformMatch, sR);
|
||||
|
||||
//System.out.println("interpWaveformMatch: " + interpWaveformMatch.length + " sR " + sR);
|
||||
|
||||
//normalise
|
||||
//this.interpWaveformMatch=PamArrayUtils.normalise(interpWaveformMatch);
|
||||
@ -142,8 +141,7 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters
|
||||
//this.inteprWaveformReject=PamArrayUtils.divide(interpWaveformMatch, PamArrayUtils.max(interpWaveformMatch));
|
||||
this.interpWaveformMatch = normaliseWaveform(interpWaveformMatch, this.normalisation);
|
||||
|
||||
// System.out.println("MatchNorm: MATCH");
|
||||
// MTClassifierTest.normalizeTest(interpWaveformMatch);
|
||||
//System.out.println("interpWaveformMatch: " + interpWaveformMatch.length + " sR " + sR + " max: " + PamArrayUtils.max(interpWaveformMatch));
|
||||
|
||||
/**
|
||||
* There is an issue here because, if we have a long template waveform, then it
|
||||
@ -248,13 +246,15 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters
|
||||
* @return
|
||||
*/
|
||||
public static double[] normaliseWaveform(double[] waveform, int normeType) {
|
||||
// System.out.println("Normalise waveform: " + normeType);
|
||||
double[] newWaveform = null;
|
||||
switch(normeType) {
|
||||
case MatchedTemplateParams.NORMALIZATION_NONE:
|
||||
newWaveform = waveform;
|
||||
break;
|
||||
case MatchedTemplateParams.NORMALIZATION_PEAK:
|
||||
newWaveform =PamUtils.PamArrayUtils.divide(waveform, PamUtils.PamArrayUtils.max(waveform));
|
||||
//important to clone here or the template waveforms are normalised!
|
||||
newWaveform =PamUtils.PamArrayUtils.divide(waveform.clone(), PamUtils.PamArrayUtils.max(waveform));
|
||||
break;
|
||||
case MatchedTemplateParams.NORMALIZATION_RMS:
|
||||
newWaveform =PamUtils.PamArrayUtils.normalise(waveform);
|
||||
|
@ -2,6 +2,7 @@ package matchedTemplateClassifer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jamdev.jdl4pam.utils.DLMatFile;
|
||||
|
||||
@ -25,7 +26,7 @@ import us.hebi.matlab.mat.types.Struct;
|
||||
public class MTClassifierTest {
|
||||
|
||||
/**
|
||||
* Test the classifier using some imported data with defaul templates.
|
||||
* Test the classifier using some imported data with default templates.
|
||||
*/
|
||||
private static void testClassifier(double[] testWaveform, float sR) {
|
||||
testClassifier(testWaveform, sR, null);
|
||||
@ -80,8 +81,8 @@ public class MTClassifierTest {
|
||||
* @param sR - the sample rate of the waveform.
|
||||
* @param templates - the match templates to test.
|
||||
*/
|
||||
private static void testCorrelation(double[] testWaveform, float sR, ArrayList<MatchTemplate> templates) {
|
||||
testCorrelation(testWaveform, sR, templates, MatchedTemplateParams.NORMALIZATION_RMS);
|
||||
public static List<MatchedTemplateResult> testCorrelation(double[] testWaveform, float sR, ArrayList<MatchTemplate> templates) {
|
||||
return testCorrelation(testWaveform, sR, templates, MatchedTemplateParams.NORMALIZATION_RMS);
|
||||
}
|
||||
|
||||
|
||||
@ -93,13 +94,16 @@ public class MTClassifierTest {
|
||||
* @param sR - the sample rate of the waveform.
|
||||
* @param templates - the match templates to test.
|
||||
* @param normalisation - the normalisation type to use e.g. MatchedTemplateParams.NORMALIZATION_RMS
|
||||
* @return a list of the correlation results.
|
||||
*/
|
||||
private static void testCorrelation(double[] testWaveform, float sR, ArrayList<MatchTemplate> templates, int normalisation) {
|
||||
public static List<MatchedTemplateResult> testCorrelation(double[] testWaveform, float sR, ArrayList<MatchTemplate> templates, int normalisation) {
|
||||
|
||||
|
||||
List<MatchedTemplateResult> matchedTemplateResult = new ArrayList<MatchedTemplateResult>();
|
||||
//create the classifier object
|
||||
for (int i=0; i<templates.size(); i++){
|
||||
MTClassifier mtclassifier = new MTClassifier();
|
||||
mtclassifier.normalisation = normalisation;
|
||||
mtclassifier.normalisation = normalisation; //set the normalisation
|
||||
|
||||
//System.out.println("Template " + i + " " + templates.get(i));
|
||||
//add templates if inpout
|
||||
@ -113,9 +117,9 @@ public class MTClassifierTest {
|
||||
//System.out.println("Waveform len: " +testWaveform.length + " min: " + PamArrayUtils.min(testWaveform) + " max: " + PamArrayUtils.max(testWaveform));
|
||||
|
||||
|
||||
testWaveform=PamArrayUtils.divide(testWaveform, PamUtils.PamArrayUtils.max(testWaveform));
|
||||
//testWaveform=PamArrayUtils.divide(testWaveform, PamUtils.PamArrayUtils.max(testWaveform));
|
||||
|
||||
testWaveform = MTClassifier.normaliseWaveform(testWaveform, MatchedTemplateParams.NORMALIZATION_RMS);
|
||||
testWaveform = MTClassifier.normaliseWaveform(testWaveform, normalisation);
|
||||
|
||||
// System.out.println("Waveform max: " + PamArrayUtils.max(testWaveform) + " len: " + testWaveform.length);
|
||||
|
||||
@ -135,6 +139,7 @@ public class MTClassifierTest {
|
||||
//calculate the correlation coefficient.
|
||||
MatchedTemplateResult matchResult = mtclassifier.calcCorrelationMatch(matchClick, sR);
|
||||
|
||||
matchedTemplateResult.add(matchResult);
|
||||
|
||||
System.out.println(String.format("The match correlation for %d is %.5f", i, matchResult.matchCorr));
|
||||
//
|
||||
@ -148,7 +153,7 @@ public class MTClassifierTest {
|
||||
|
||||
|
||||
}
|
||||
|
||||
return matchedTemplateResult;
|
||||
}
|
||||
|
||||
public static void printFFt(ComplexArray complexArray) {
|
||||
@ -230,7 +235,7 @@ public class MTClassifierTest {
|
||||
mfr = Mat5.readFromFile(filePath);
|
||||
// //get array of a name "my_array" from file
|
||||
Struct mlArrayMatch = mfr.getStruct( "templates" );
|
||||
//System.out.println(mlArrayMatch.getType());
|
||||
// System.out.println(mlArrayMatch.getType() + " " + mlArrayMatch.getNumElements());
|
||||
|
||||
ArrayList<MatchTemplate> templates = new ArrayList<MatchTemplate>();
|
||||
|
||||
@ -238,12 +243,14 @@ public class MTClassifierTest {
|
||||
|
||||
for (int i=0; i<numTemplates; i++) {
|
||||
|
||||
double[][] wave = PamArrayUtils.matrix2array(mlArrayMatch.get("wave", i));
|
||||
Matrix waveM = mlArrayMatch.get("wave", i);
|
||||
|
||||
double[][] wave = PamArrayUtils.matrix2array(waveM);
|
||||
|
||||
Matrix templatesr= mlArrayMatch.get("sr", i);
|
||||
double sr= templatesr.getDouble(0);
|
||||
|
||||
System.out.println("template wave: " + wave[0].length + " num: " + numTemplates);
|
||||
// System.out.println("template wave: " + wave[0].length + " num: " + numTemplates);
|
||||
|
||||
templates.add(new MatchTemplate(null, wave[0], (float) sr));
|
||||
}
|
||||
@ -373,7 +380,7 @@ public class MTClassifierTest {
|
||||
*/
|
||||
public static void testMatchCorr() {
|
||||
|
||||
String templteFilePath= "/Users/au671271/MATLAB-Drive/MATLAB/PAMGUARD/matchedclickclassifer/DS2templates_test.mat";
|
||||
String templteFilePath= "/Users/au671271/MATLAB-Drive/MATLAB/PAMGUARD/matchedclickclassifer/_test/DS2templates_test.mat";
|
||||
//float sR = 288000; //sample rate in samples per second.
|
||||
|
||||
ArrayList<MatchTemplate> templates = importTemplates(templteFilePath);
|
||||
@ -384,7 +391,8 @@ public class MTClassifierTest {
|
||||
|
||||
|
||||
public static void main(String args[]) {
|
||||
testMatchCorrLen();
|
||||
// testMatchCorrLen();
|
||||
testMatchCorr();
|
||||
}
|
||||
|
||||
|
||||
|
827
src/pamViewFX/fxNodes/pamAxis/PamDateAxis.java
Normal file
827
src/pamViewFX/fxNodes/pamAxis/PamDateAxis.java
Normal file
@ -0,0 +1,827 @@
|
||||
package pamViewFX.fxNodes.pamAxis;
|
||||
|
||||
import com.sun.javafx.charts.ChartLayoutAnimator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.CssMetaData;
|
||||
import javafx.css.Styleable;
|
||||
import javafx.css.StyleableDoubleProperty;
|
||||
import javafx.css.StyleableProperty;
|
||||
import javafx.css.converter.SizeConverter;
|
||||
import javafx.geometry.Dimension2D;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.chart.ValueAxis;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.util.converter.TimeStringConverter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/*
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: Pedro Duque Vieira
|
||||
* Date: 15-08-2013
|
||||
* Time: 18:33
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
public class PamDateAxis extends ValueAxis<Long> {
|
||||
|
||||
/** We use these for auto ranging to pick a user friendly tick unit. (must be increasingly bigger)*/
|
||||
private static final double[] TICK_UNIT_DEFAULTS = {
|
||||
86400000, // 1 day
|
||||
172800000, // 2 das
|
||||
259200000, // 3 days
|
||||
345600000, // 4 days
|
||||
432000000, // 5 days
|
||||
518400000, // 6 days
|
||||
604800000, // 7 days
|
||||
691200000, // 8 days
|
||||
777600000, // 9 days
|
||||
864000000, // 10 days
|
||||
216000000E1, // 15 days
|
||||
388800000E1, // 20 days
|
||||
604800000E1, // 25 days
|
||||
872640000E1, // 31 days ~ 1 month
|
||||
1226880000E1, // 41 days
|
||||
1667520000E1, // 51 days
|
||||
2203200000E1, // 62 days ~ 2 months
|
||||
2868480000E1, // 77 days
|
||||
3672000000E1, // 93 days ~ 3 months
|
||||
4605120000E1, // 108 days
|
||||
5676480000E1, // 124 days ~ 4 months
|
||||
6877440000E1, // 139 days
|
||||
8216640000E1, // 155 days ~ 5 months
|
||||
9685440000E1, // 170 days
|
||||
1129248000E2, // 186 days ~ 6 months
|
||||
1445472000E2 // 366 days ~ 1 year
|
||||
};
|
||||
|
||||
/** These are matching date formatter strings */
|
||||
private static final String[] TICK_UNIT_FORMATTER_DEFAULTS = {
|
||||
"MM/dd/yy", // 1 day
|
||||
"MM/dd/yy", // 2 das
|
||||
"MM/dd/yy", // 3 days
|
||||
"MM/dd/yy", // 4 days
|
||||
"MM/dd/yy", // 5 days
|
||||
"MM/dd/yy", // 6 days
|
||||
"MM/dd/yy", // 7 days
|
||||
"MM/dd/yy", // 8 days
|
||||
"MM/dd/yy", // 9 days
|
||||
"MM/dd/yy", // 10 days
|
||||
"MM/dd/yy", // 15 days
|
||||
"MM/dd/yy", // 20 days
|
||||
"MM/dd/yy", // 25 days
|
||||
"MMM-yyyy", // 31 days ~ 1 month
|
||||
"MMM-yyyy", // 41 days
|
||||
"MMM-yyyy", // 51 days
|
||||
"MMM-yyyy", // 62 days ~ 2 months
|
||||
"MMM-yyyy", // 77 days
|
||||
"MMM-yyyy", // 93 days ~ 3 months
|
||||
"MMM-yyyy", // 108 days
|
||||
"MMM-yyyy", // 124 days ~ 4 months
|
||||
"MMM-yyyy", // 139 days
|
||||
"MMM-yyyy", // 155 days ~ 5 months
|
||||
"MMM-yyyy", // 170 days
|
||||
"MMM-yyyy", // 186 days ~ 6 months
|
||||
"yyyy" // 366 days ~ 1 year
|
||||
};
|
||||
|
||||
|
||||
private Object currentAnimationID;
|
||||
private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
|
||||
private IntegerProperty currentRangeIndexProperty = new SimpleIntegerProperty(this, "currentRangeIndex", -1);
|
||||
private DefaultFormatter defaultFormatter = new DefaultFormatter(this);
|
||||
|
||||
// -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
|
||||
|
||||
/** When true zero is always included in the visible range. This only has effect if auto-ranging is on. */
|
||||
private BooleanProperty forceZeroInRange = new BooleanPropertyBase(true) {
|
||||
@Override protected void invalidated() {
|
||||
// This will effect layout if we are auto ranging
|
||||
if(isAutoRanging()) requestAxisLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean() {
|
||||
return PamDateAxis.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "forceZeroInRange";
|
||||
}
|
||||
};
|
||||
public final boolean isForceZeroInRange() { return forceZeroInRange.getValue(); }
|
||||
public final void setForceZeroInRange(boolean value) { forceZeroInRange.setValue(value); }
|
||||
public final BooleanProperty forceZeroInRangeProperty() { return forceZeroInRange; }
|
||||
|
||||
/** The value between each major tick mark in data units. This is automatically set if we are auto-ranging. */
|
||||
private DoubleProperty tickUnit = new StyleableDoubleProperty(5) {
|
||||
@Override protected void invalidated() {
|
||||
if(!isAutoRanging()) {
|
||||
invalidateRange();
|
||||
requestAxisLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CssMetaData<PamDateAxis,Number> getCssMetaData() {
|
||||
return StyleableProperties.TICK_UNIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean() {
|
||||
return PamDateAxis.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "tickUnit";
|
||||
}
|
||||
};
|
||||
public final double getTickUnit() { return tickUnit.get(); }
|
||||
public final void setTickUnit(double value) { tickUnit.set(value); }
|
||||
public final DoubleProperty tickUnitProperty() { return tickUnit; }
|
||||
|
||||
// -------------- CONSTRUCTORS -------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a auto-ranging DateAxis
|
||||
*/
|
||||
public PamDateAxis() {
|
||||
forceZeroInRange.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-auto-ranging DateAxis with the given upper bound, lower bound and tick unit
|
||||
*
|
||||
* @param lowerBound The lower bound for this axis, ie min plottable value
|
||||
* @param upperBound The upper bound for this axis, ie max plottable value
|
||||
* @param tickUnit The tick unit, ie space between tickmarks
|
||||
*/
|
||||
public PamDateAxis(double lowerBound, double upperBound, double tickUnit) {
|
||||
super(lowerBound, upperBound);
|
||||
setTickUnit(tickUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-auto-ranging DateAxis with the given upper bound, lower bound and tick unit
|
||||
*
|
||||
* @param axisLabel The name to display for this axis
|
||||
* @param lowerBound The lower bound for this axis, ie min plottable value
|
||||
* @param upperBound The upper bound for this axis, ie max plottable value
|
||||
* @param tickUnit The tick unit, ie space between tickmarks
|
||||
*/
|
||||
public PamDateAxis(String axisLabel, double lowerBound, double upperBound, double tickUnit) {
|
||||
super(lowerBound, upperBound);
|
||||
setTickUnit(tickUnit);
|
||||
setLabel(axisLabel);
|
||||
}
|
||||
|
||||
// -------------- PROTECTED METHODS --------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get the string label name for a tick mark with the given value
|
||||
*
|
||||
* @param value The value to format into a tick label string
|
||||
* @return A formatted string for the given value
|
||||
*/
|
||||
@Override protected String getTickMarkLabel(Long value) {
|
||||
StringConverter<Long> formatter = getTickLabelFormatter();
|
||||
if (formatter == null) formatter = defaultFormatter;
|
||||
return formatter.toString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get the current axis range.
|
||||
*
|
||||
* @return A range object that can be passed to setRange() and calculateTickValues()
|
||||
*/
|
||||
@Override protected Object getRange() {
|
||||
double[] newParams = recalculateTicks();
|
||||
double newMin = newParams[0];
|
||||
double newMax = newParams[1];
|
||||
double newIndex = newParams[2];
|
||||
double newTickUnit = newParams[3];
|
||||
return new double[]{
|
||||
newMin,
|
||||
newMax,
|
||||
newTickUnit,
|
||||
getScale(),
|
||||
newIndex
|
||||
};
|
||||
}
|
||||
|
||||
private double[] recalculateTicks()
|
||||
{
|
||||
final Side side = getSide();
|
||||
final boolean vertical = Side.LEFT.equals(side) || Side.RIGHT.equals(side);
|
||||
final double length = vertical ? getHeight() : getWidth();
|
||||
// guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally
|
||||
double labelSize = getTickLabelFont().getSize() * 2;
|
||||
|
||||
double currentRange = getUpperBound() - getLowerBound();
|
||||
|
||||
// calculate the number of tick-marks we can fit in the given length
|
||||
int numOfTickMarks = (int)Math.floor(Math.abs(length)/labelSize);
|
||||
// can never have less than 2 tick marks one for each end
|
||||
numOfTickMarks = Math.max(numOfTickMarks, 2);
|
||||
// calculate tick unit for the number of ticks can have in the given data range
|
||||
double tickUnit = currentRange/(double)numOfTickMarks;
|
||||
// search for the best tick unit that fits
|
||||
double tickUnitRounded = 0;
|
||||
double minRounded = 0;
|
||||
double maxRounded = 0;
|
||||
int count = 0;
|
||||
double reqLength = Double.MAX_VALUE;
|
||||
int rangeIndex = 10;
|
||||
// loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks
|
||||
while (reqLength > length || count > 20) {
|
||||
// find a user friendly match from our default tick units to match calculated tick unit
|
||||
for (int i=0; i<TICK_UNIT_DEFAULTS.length; i++) {
|
||||
double tickUnitDefault = TICK_UNIT_DEFAULTS[i];
|
||||
if (tickUnitDefault > tickUnit) {
|
||||
tickUnitRounded = tickUnitDefault;
|
||||
rangeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// move min and max to nearest tick mark
|
||||
minRounded = Math.floor(getLowerBound() / tickUnitRounded) * tickUnitRounded;
|
||||
maxRounded = Math.ceil(getUpperBound() / tickUnitRounded) * tickUnitRounded;
|
||||
// calculate the required length to display the chosen tick marks for real, this will handle if there are
|
||||
// huge numbers involved etc or special formatting of the tick mark label text
|
||||
double maxReqTickGap = 0;
|
||||
double last = 0;
|
||||
count = 0;
|
||||
for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++) {
|
||||
double size = (vertical) ? measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getHeight() :
|
||||
measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getWidth();
|
||||
if (major == minRounded) { // first
|
||||
last = size/2;
|
||||
} else {
|
||||
maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) );
|
||||
}
|
||||
}
|
||||
reqLength = (count-1) * maxReqTickGap;
|
||||
tickUnit = tickUnitRounded;
|
||||
// check if we already found max tick unit
|
||||
if (tickUnitRounded == TICK_UNIT_DEFAULTS[TICK_UNIT_DEFAULTS.length-1]) {
|
||||
// nothing we can do so just have to use this
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new double[]{minRounded, maxRounded, rangeIndex, tickUnit};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to set the current axis range to the given range. If isAnimating() is true then this method should
|
||||
* animate the range to the new range.
|
||||
*
|
||||
* @param range A range object returned from autoRange()
|
||||
* @param animate If true animate the change in range
|
||||
*/
|
||||
@Override protected void setRange(Object range, boolean animate) {
|
||||
final double[] rangeProps = (double[]) range;
|
||||
final double lowerBound = rangeProps[0];
|
||||
final double upperBound = rangeProps[1];
|
||||
final double tickUnit = rangeProps[2];
|
||||
final double scale = rangeProps[3];
|
||||
final double rangeIndex = rangeProps[4];
|
||||
currentRangeIndexProperty.set((int)rangeIndex);
|
||||
final double oldLowerBound = getLowerBound();
|
||||
setLowerBound(lowerBound);
|
||||
setUpperBound(upperBound);
|
||||
setTickUnit(tickUnit);
|
||||
|
||||
ReadOnlyDoubleWrapper scalePropertyImplValue = (ReadOnlyDoubleWrapper) ReflectionUtils.forceMethodCall(ValueAxis.class, "scalePropertyImpl", this);
|
||||
|
||||
if(animate) {
|
||||
animator.stop(currentAnimationID);
|
||||
currentAnimationID = animator.animate(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(currentLowerBound, oldLowerBound),
|
||||
new KeyValue(scalePropertyImplValue, getScale())
|
||||
),
|
||||
new KeyFrame(Duration.millis(700),
|
||||
new KeyValue(currentLowerBound, lowerBound),
|
||||
new KeyValue(scalePropertyImplValue, scale)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
currentLowerBound.set(lowerBound);
|
||||
setScale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a list of all the data values for each tick mark in range
|
||||
*
|
||||
* @param length The length of the axis in display units
|
||||
* @param range A range object returned from autoRange()
|
||||
* @return A list of tick marks that fit along the axis if it was the given length
|
||||
*/
|
||||
@Override protected List<Long> calculateTickValues(double length, Object range) {
|
||||
final double[] rangeProps = (double[]) range;
|
||||
final double lowerBound = rangeProps[0];
|
||||
final double upperBound = rangeProps[1];
|
||||
final double tickUnit = rangeProps[2];
|
||||
List<Long> tickValues = new ArrayList<Long>();
|
||||
if (tickUnit <= 0 || lowerBound == upperBound) {
|
||||
tickValues.add((long)lowerBound);
|
||||
} else if (getTickUnit() > 0) {
|
||||
for (double major = lowerBound; major <= upperBound; major += tickUnit) {
|
||||
tickValues.add((long)major);
|
||||
if(tickValues.size()>2000) {
|
||||
// This is a ridiculous amount of major tick marks, something has probably gone wrong
|
||||
System.err.println("Warning we tried to create more than 2000 major tick marks on a NumberAxis. " +
|
||||
"Lower Bound=" + lowerBound + ", Upper Bound=" + upperBound + ", Tick Unit=" + tickUnit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tickValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a list of the data values for every minor tick mark
|
||||
*
|
||||
* @return List of data values where to draw minor tick marks
|
||||
*/
|
||||
protected List<Long> calculateMinorTickMarks() {
|
||||
final List<Long> minorTickMarks = new ArrayList<Long>();
|
||||
final double lowerBound = getLowerBound();
|
||||
final double upperBound = getUpperBound();
|
||||
final double tickUnit = getTickUnit();
|
||||
final double minorUnit = tickUnit/getMinorTickCount();
|
||||
if (getTickUnit() > 0) {
|
||||
for (double major = lowerBound; major < upperBound; major += tickUnit) {
|
||||
for (double minor=major+minorUnit; minor < (major+tickUnit); minor += minorUnit) {
|
||||
minorTickMarks.add((long)minor);
|
||||
if(minorTickMarks.size()>10000) {
|
||||
// This is a ridiculous amount of major tick marks, something has probably gone wrong
|
||||
System.err.println("Warning we tried to create more than 10000 minor tick marks on a NumberAxis. " +
|
||||
"Lower Bound=" + getLowerBound() + ", Upper Bound=" + getUpperBound() + ", Tick Unit=" + tickUnit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return minorTickMarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
|
||||
*
|
||||
* @param value tick mark value
|
||||
* @param range range to use during calculations
|
||||
* @return size of tick mark label for given value
|
||||
*/
|
||||
@Override protected Dimension2D measureTickMarkSize(Long value, Object range) {
|
||||
final double[] rangeProps = (double[]) range;
|
||||
final double rangeIndex = rangeProps[4];
|
||||
return measureTickMarkSize(value, getTickLabelRotation(), (int)rangeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
|
||||
*
|
||||
* @param value tick mark value
|
||||
* @param rotation The text rotation
|
||||
* @param rangeIndex The index of the tick unit range
|
||||
* @return size of tick mark label for given value
|
||||
*/
|
||||
private Dimension2D measureTickMarkSize(Long value, double rotation, int rangeIndex) {
|
||||
String labelText;
|
||||
StringConverter<Long> formatter = getTickLabelFormatter();
|
||||
if (formatter == null) formatter = defaultFormatter;
|
||||
if(formatter instanceof DefaultFormatter) {
|
||||
labelText = ((DefaultFormatter)formatter).toString(value, rangeIndex);
|
||||
} else {
|
||||
labelText = formatter.toString(value);
|
||||
}
|
||||
return measureTickMarkLabelSize(labelText, rotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to set the upper and lower bound and anything else that needs to be auto-ranged
|
||||
*
|
||||
* @param minValue The min data value that needs to be plotted on this axis
|
||||
* @param maxValue The max data value that needs to be plotted on this axis
|
||||
* @param length The length of the axis in display coordinates
|
||||
* @param labelSize The approximate average size a label takes along the axis
|
||||
* @return The calculated range
|
||||
*/
|
||||
@Override protected Object autoRange(double minValue, double maxValue, double length, double labelSize) {
|
||||
final Side side = getSide();
|
||||
final boolean vertical = Side.LEFT.equals(side) || Side.RIGHT.equals(side);
|
||||
// check if we need to force zero into range
|
||||
if (isForceZeroInRange()) {
|
||||
if (maxValue < 0) {
|
||||
maxValue = 0;
|
||||
} else if (minValue > 0) {
|
||||
minValue = 0;
|
||||
}
|
||||
}
|
||||
final double range = maxValue-minValue;
|
||||
|
||||
// // pad min and max by 2%, checking if the range is zero
|
||||
final double paddedRange = (range==0) ? 2 : Math.abs(range)*1.02;
|
||||
|
||||
final double padding = (paddedRange - range) / 2;
|
||||
// if min and max are not zero then add padding to them
|
||||
double paddedMin = minValue - padding;
|
||||
double paddedMax = maxValue + padding;
|
||||
// check padding has not pushed min or max over zero line
|
||||
if ((paddedMin < 0 && minValue >= 0) || (paddedMin > 0 && minValue <= 0)) {
|
||||
// padding pushed min above or below zero so clamp to 0
|
||||
paddedMin = 0;
|
||||
}
|
||||
if ((paddedMax < 0 && maxValue >= 0) || (paddedMax > 0 && maxValue <= 0)) {
|
||||
// padding pushed min above or below zero so clamp to 0
|
||||
paddedMax = 0;
|
||||
}
|
||||
// calculate the number of tick-marks we can fit in the given length
|
||||
int numOfTickMarks = (int)Math.floor(Math.abs(length)/labelSize);
|
||||
// can never have less than 2 tick marks one for each end
|
||||
numOfTickMarks = Math.max(numOfTickMarks, 2);
|
||||
// calculate tick unit for the number of ticks can have in the given data range
|
||||
double tickUnit = paddedRange/(double)numOfTickMarks;
|
||||
// search for the best tick unit that fits
|
||||
double tickUnitRounded = 0;
|
||||
double minRounded = 0;
|
||||
double maxRounded = 0;
|
||||
int count = 0;
|
||||
double reqLength = Double.MAX_VALUE;
|
||||
int rangeIndex = 10;
|
||||
// loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks
|
||||
while (reqLength > length || count > 20) {
|
||||
// find a user friendly match from our default tick units to match calculated tick unit
|
||||
for (int i=0; i<TICK_UNIT_DEFAULTS.length; i++) {
|
||||
double tickUnitDefault = TICK_UNIT_DEFAULTS[i];
|
||||
if (tickUnitDefault > tickUnit) {
|
||||
tickUnitRounded = tickUnitDefault;
|
||||
rangeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// move min and max to nearest tick mark
|
||||
minRounded = Math.floor(paddedMin / tickUnitRounded) * tickUnitRounded;
|
||||
maxRounded = Math.ceil(paddedMax / tickUnitRounded) * tickUnitRounded;
|
||||
// calculate the required length to display the chosen tick marks for real, this will handle if there are
|
||||
// huge numbers involved etc or special formatting of the tick mark label text
|
||||
double maxReqTickGap = 0;
|
||||
double last = 0;
|
||||
count = 0;
|
||||
for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++) {
|
||||
double size = (vertical) ? measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getHeight() :
|
||||
measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getWidth();
|
||||
if (major == minRounded) { // first
|
||||
last = size/2;
|
||||
} else {
|
||||
maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) );
|
||||
}
|
||||
}
|
||||
reqLength = (count-1) * maxReqTickGap;
|
||||
tickUnit = tickUnitRounded;
|
||||
// check if we already found max tick unit
|
||||
if (tickUnitRounded == TICK_UNIT_DEFAULTS[TICK_UNIT_DEFAULTS.length-1]) {
|
||||
// nothing we can do so just have to use this
|
||||
break;
|
||||
}
|
||||
}
|
||||
// calculate new scale
|
||||
final double newScale = calculateNewScale(length, minRounded, maxRounded);
|
||||
// return new range
|
||||
return new double[]{minRounded, maxRounded, tickUnitRounded, newScale, rangeIndex};
|
||||
}
|
||||
|
||||
// -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------
|
||||
|
||||
/** @treatAsPrivate implementation detail */
|
||||
private static class StyleableProperties {
|
||||
private static final CssMetaData<PamDateAxis,Number> TICK_UNIT =
|
||||
new CssMetaData<PamDateAxis,Number>("-fx-tick-unit",
|
||||
SizeConverter.getInstance(), 5.0) {
|
||||
|
||||
@Override
|
||||
public boolean isSettable(PamDateAxis n) {
|
||||
return n.tickUnit == null || !n.tickUnit.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Number> getStyleableProperty(PamDateAxis n) {
|
||||
return (StyleableProperty<Number>)n.tickUnitProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<CssMetaData<? extends Styleable, ?>>(ValueAxis.getClassCssMetaData());
|
||||
styleables.add(TICK_UNIT);
|
||||
STYLEABLES = Collections.unmodifiableList(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The CssMetaData associated with this class, which may include the
|
||||
* CssMetaData of its super classes.
|
||||
* @since JavaFX 8.0
|
||||
*/
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @since JavaFX 8.0
|
||||
*/
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
// -------------- INNER CLASSES ------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default number formatter for DateAxis, this stays in sync with auto-ranging and formats values appropriately.
|
||||
* You can wrap this formatter to add prefixes or suffixes;
|
||||
* @since JavaFX 2.0
|
||||
*/
|
||||
public static class DefaultFormatter extends StringConverter<Long> {
|
||||
private TimeStringConverter formatter;
|
||||
private String prefix = null;
|
||||
private String suffix = null;
|
||||
|
||||
private Date tempDate = new Date();
|
||||
|
||||
/** used internally */
|
||||
private DefaultFormatter() {
|
||||
formatter = new TimeStringConverter("MM/dd/yy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DefaultFormatter for the given DateAxis
|
||||
*
|
||||
* @param axis The axis to format tick marks for
|
||||
*/
|
||||
public DefaultFormatter(final PamDateAxis axis) {
|
||||
formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1);
|
||||
final ChangeListener axisListener = new ChangeListener() {
|
||||
@Override public void changed(ObservableValue observable, Object oldValue, Object newValue) {
|
||||
formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1);
|
||||
}
|
||||
};
|
||||
axis.currentRangeIndexProperty.addListener(axisListener);
|
||||
axis.autoRangingProperty().addListener(axisListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DefaultFormatter for the given DateAxis with a prefix and/or suffix.
|
||||
*
|
||||
* @param axis The axis to format tick marks for
|
||||
* @param prefix The prefix to append to the start of formatted number, can be null if not needed
|
||||
* @param suffix The suffix to append to the end of formatted number, can be null if not needed
|
||||
*/
|
||||
public DefaultFormatter(PamDateAxis axis, String prefix, String suffix) {
|
||||
this(axis);
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
private static TimeStringConverter getFormatter(int rangeIndex) {
|
||||
if (rangeIndex < 0) {
|
||||
return new TimeStringConverter("MM/dd/yy");
|
||||
} else if(rangeIndex >= TICK_UNIT_FORMATTER_DEFAULTS.length) {
|
||||
return new TimeStringConverter(TICK_UNIT_FORMATTER_DEFAULTS[TICK_UNIT_FORMATTER_DEFAULTS.length-1]);
|
||||
} else {
|
||||
return new TimeStringConverter(TICK_UNIT_FORMATTER_DEFAULTS[rangeIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object provided into its string form.
|
||||
* Format of the returned string is defined by this converter.
|
||||
* @return a string representation of the object passed in.
|
||||
* @see StringConverter#toString
|
||||
*/
|
||||
@Override public String toString(Long object) {
|
||||
return toString(object, formatter);
|
||||
}
|
||||
|
||||
private String toString(Long object, int rangeIndex) {
|
||||
return toString(object, getFormatter(rangeIndex));
|
||||
}
|
||||
|
||||
private String toString(Long object, TimeStringConverter formatter) {
|
||||
tempDate.setTime(object);
|
||||
if (prefix != null && suffix != null) {
|
||||
return prefix + formatter.toString(tempDate) + suffix;
|
||||
} else if (prefix != null) {
|
||||
return prefix + formatter.toString(tempDate);
|
||||
} else if (suffix != null) {
|
||||
return formatter.toString(tempDate) + suffix;
|
||||
} else {
|
||||
return formatter.toString(tempDate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the string provided into a Number defined by the this converter.
|
||||
* Format of the string and type of the resulting object is defined by this converter.
|
||||
* @return a Number representation of the string passed in.
|
||||
* @see StringConverter#toString
|
||||
*/
|
||||
@Override public Long fromString(String string) {
|
||||
int prefixLength = (prefix == null)? 0: prefix.length();
|
||||
int suffixLength = (suffix == null)? 0: suffix.length();
|
||||
return formatter.fromString(string.substring(prefixLength, string.length() - suffixLength)).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main (String [] args)
|
||||
{
|
||||
// Date construction test
|
||||
GregorianCalendar calendar = new GregorianCalendar(1900, 0, 1); // year, month, day
|
||||
Date date = calendar.getTime();
|
||||
TimeStringConverter timeConverter = new TimeStringConverter("MM/dd/yyyy");
|
||||
System.out.println("This is the date toString = " + timeConverter.toString(date));
|
||||
|
||||
// What is 1 day converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
date = calendar.getTime();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
Date secondDate = calendar.getTime();
|
||||
long firstDateValue = date.getTime();
|
||||
long secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (1 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 2 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 2);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (2 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 3 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 3);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (3 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 4 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 4);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (4 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 5 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 5);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (5 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 6 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 6);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (6 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 7 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 7);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (7 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 8 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 8);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (8 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 9 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 9);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (9 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 10 days converted to long
|
||||
calendar = new GregorianCalendar(1900, 0, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 10);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (10 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 15 days? With a long type
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 15);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (15 days) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 20 days? With a long type
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 20);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (20 days) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 25 days? With a long type
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 25);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (25 days) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
|
||||
// What is 1 mont converted to long
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 31);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (31 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 41 days
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 41);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (41 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 51 days
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 51);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (51 day) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 62 days ( 2 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 62);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (62 days - 2 months) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 77 days ( 2 monhts and a half
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 77);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (77 days - 2.5 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 93 days ( 3 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 93);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (93 days - 3 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 108 ( 3 monhts and a half
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 108);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (108 days - 3.5 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 124 ( 4 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 124);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (124 days - 4 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 139 ( 4.5 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 139);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (139 days - 4.5 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 139 ( 5 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 155);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (155 days - 5 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 139 ( 5.5 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 170);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (170 days - 5.5 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 139 ( 6 monhts
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 186);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (186 days - 6 month) - \t" + (secondDateValue - firstDateValue));
|
||||
|
||||
// What is 366 ( 1 year)
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 366);
|
||||
secondDate = calendar.getTime();
|
||||
secondDateValue = secondDate.getTime();
|
||||
System.out.println("This is the difference of value between the first and second date (366 days - 1 year) - \t" + (secondDateValue - firstDateValue));
|
||||
}
|
||||
|
||||
}
|
58
src/pamViewFX/fxNodes/pamAxis/ReflectionUtils.java
Normal file
58
src/pamViewFX/fxNodes/pamAxis/ReflectionUtils.java
Normal file
@ -0,0 +1,58 @@
|
||||
package pamViewFX.fxNodes.pamAxis;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: Pedro
|
||||
* Date: 11-09-2013
|
||||
* Time: 23:21
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Object forceMethodCall(Class classInstance, String methodName, Object source, Class[] paramTypes, Object[] params) {
|
||||
Object returnedObject = null;
|
||||
try {
|
||||
Method method = classInstance.getDeclaredMethod(methodName, paramTypes);
|
||||
method.setAccessible(true);
|
||||
returnedObject = method.invoke(source, params);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return returnedObject;
|
||||
}
|
||||
|
||||
public static Object forceMethodCall(Class classInstance, String methodName, Object source, Object... params) {
|
||||
Class[] paramTypes = new Class[]{};
|
||||
if (params == null) {
|
||||
params = new Object[]{};
|
||||
}
|
||||
List<Class> derivedTypes = new ArrayList<>();
|
||||
for (Object p : params) {
|
||||
derivedTypes.add(p.getClass());
|
||||
}
|
||||
if (derivedTypes.size() > 0) {
|
||||
paramTypes = derivedTypes.toArray(new Class[derivedTypes.size()]);
|
||||
}
|
||||
return forceMethodCall(classInstance, methodName, source, paramTypes, params);
|
||||
}
|
||||
|
||||
public static Object forceFieldCall(Class classInstance, String fieldName, Object source) {
|
||||
Object returnedObject = null;
|
||||
try {
|
||||
Field field = classInstance.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
returnedObject = field.get(source);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return returnedObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
package test.matchedTemplateClassifier;
|
||||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import PamUtils.PamArrayUtils;
|
||||
import PamUtils.complex.ComplexArray;
|
||||
import Spectrogram.WindowFunction;
|
||||
import clickDetector.ClickLength;
|
||||
import fftManager.FastFFT;
|
||||
import matchedTemplateClassifer.MTClassifier;
|
||||
import matchedTemplateClassifer.MTClassifierTest;
|
||||
import matchedTemplateClassifer.MTProcess;
|
||||
import matchedTemplateClassifer.MatchTemplate;
|
||||
import matchedTemplateClassifer.MatchedTemplateParams;
|
||||
import matchedTemplateClassifer.MatchedTemplateResult;
|
||||
|
||||
/**
|
||||
* Tests for the matched click classifier.
|
||||
*/
|
||||
public class MatchedTemplateClassifierTest {
|
||||
|
||||
|
||||
/**
|
||||
* Test the match correlation algorithm by cross correlating a waveform with itself. Results
|
||||
* are tested against the matlab xcorr funtion
|
||||
*/
|
||||
@Test
|
||||
public void testMatchCorr() {
|
||||
|
||||
System.out.println("Matched template classifier test: match corr");
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Test against MATLAB Xcorr function
|
||||
*
|
||||
* load('/Users/au671271/MATLAB-Drive/MATLAB/PAMGUARD/matchedclickclassifer/
|
||||
* _test/DS2templates_test.mat')
|
||||
*
|
||||
* wave = templates(1).wave; %test correlating waves with themselves using xcorr
|
||||
*
|
||||
* r = xcorr( wave , wave); disp(['Max correlation no-norm: ' num2str(max(r))])
|
||||
*
|
||||
* r = xcorr( normalize(wave) , normalize(wave)); disp(['Max correlation rms: '
|
||||
* num2str(max(r))])
|
||||
*
|
||||
* r = xcorr( wave/max(wave) , wave/max(wave)); disp(['Max correlation peak: '
|
||||
* num2str(max(r))])
|
||||
*/
|
||||
|
||||
//Note that the return value of two identical waveforms depends on normalisation.
|
||||
|
||||
String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS2templates_test.mat";
|
||||
//float sR = 288000; //sample rate in samples per second.
|
||||
Path path = Paths.get(templteFilePath);
|
||||
String templteFilePathR = path.toAbsolutePath().normalize().toString();
|
||||
|
||||
ArrayList<MatchTemplate> templates = MTClassifierTest.importTemplates(templteFilePathR);
|
||||
|
||||
List<MatchedTemplateResult> matchedClickResult ;
|
||||
/**
|
||||
* Correlate a template waveform with itself and check the result is 1.
|
||||
*/
|
||||
matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_RMS);
|
||||
assertEquals(matchedClickResult.get(0).matchCorr, 1.0, 0.01);
|
||||
|
||||
|
||||
matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_PEAK);
|
||||
assertEquals(matchedClickResult.get(0).matchCorr, 134.5961, 0.01);
|
||||
|
||||
|
||||
matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_NONE);
|
||||
assertEquals(matchedClickResult.get(0).matchCorr, 7.8457, 0.01);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the match correlation algorithm combined with click length algorithm. Here we awant to test that
|
||||
* a long waveform
|
||||
*/
|
||||
@Test
|
||||
public void testMatchCorrLen() {
|
||||
|
||||
System.out.println("Matched template classifier test: match corr len");
|
||||
|
||||
String testClicksPath = "./src/test/resources/matchedTemplateClassifier/DS3clks_test.mat";
|
||||
Path path = Paths.get(testClicksPath);
|
||||
testClicksPath=path.toAbsolutePath().normalize().toString();
|
||||
|
||||
String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS3templates_test.mat";
|
||||
path = Paths.get(templteFilePath);
|
||||
templteFilePath=path.toAbsolutePath().normalize().toString();
|
||||
|
||||
//import some example clicks
|
||||
float sR = 288000; //sample rate in samples per second.
|
||||
ArrayList<MatchTemplate> clicks = MTClassifierTest.importClicks(testClicksPath, sR);
|
||||
|
||||
//import some templates
|
||||
ArrayList<MatchTemplate> templates = MTClassifierTest.importTemplates(templteFilePath);
|
||||
|
||||
int index = 24; //the index of the test clck to use.
|
||||
//values in MATLAB are9.73577287114938 8.82782814105430 3.51936216182390
|
||||
// System.out.println("Number of clicks: " + clicks.size() + " UID " + clicks.get(index).name);
|
||||
|
||||
System.out.println("------Standard Length--------");
|
||||
List<MatchedTemplateResult> matchedClickResultLen1 = MTClassifierTest.testCorrelation(clicks.get(index).waveform, sR, templates);
|
||||
|
||||
System.out.println("------Restricted Length--------");
|
||||
|
||||
int restrictedBins= 2048;
|
||||
|
||||
ClickLength clickLength = new ClickLength();
|
||||
int[][] lengthPoints = clickLength.createLengthData(clicks.get(index), sR, 5.5, 3, false, null);
|
||||
|
||||
double[] waveformLen = MTProcess.createRestrictedLenghtWave(clicks.get(index).waveform, lengthPoints[0],
|
||||
restrictedBins, WindowFunction.hann(restrictedBins));
|
||||
|
||||
List<MatchedTemplateResult> matchedClickResultLen2 = MTClassifierTest.testCorrelation(waveformLen, sR, templates);
|
||||
|
||||
assertEquals(matchedClickResultLen1.get(0).matchCorr, matchedClickResultLen2.get(0).matchCorr, 0.05);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the FFT method of correlating two identical waveforms.
|
||||
*/
|
||||
@Test
|
||||
public void testFFTCorrelation() {
|
||||
|
||||
System.out.println("Matched template classifier test: xcorr");
|
||||
|
||||
String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS2templates_test.mat";
|
||||
//float sR = 288000; //sample rate in samples per second.
|
||||
Path path = Paths.get(templteFilePath);
|
||||
String templteFilePathR = path.toAbsolutePath().normalize().toString();
|
||||
|
||||
ArrayList<MatchTemplate> templates = MTClassifierTest.importTemplates(templteFilePathR);
|
||||
|
||||
double[] waveform = templates.get(0).getWaveData()[0];
|
||||
waveform = MTClassifier.normaliseWaveform(waveform, MatchedTemplateParams.NORMALIZATION_RMS);
|
||||
|
||||
FastFFT fft = new FastFFT();
|
||||
ComplexArray testClick = fft.rfft(waveform, waveform.length);
|
||||
ComplexArray matchTemplate = fft.rfft(waveform, waveform.length).conj();
|
||||
|
||||
int fftLength = testClick.length()*2;
|
||||
ComplexArray matchResult= new ComplexArray(fftLength);
|
||||
|
||||
for (int i=0; i<Math.min(testClick.length(), matchTemplate.length()); i++) {
|
||||
matchResult.set(i, testClick.get(i).times(matchTemplate.get(i)));
|
||||
}
|
||||
|
||||
fft.ifft(matchResult, fftLength, true);
|
||||
|
||||
double[] matchReal = new double[matchResult.length()];
|
||||
for (int i=0; i<matchResult.length(); i++) {
|
||||
matchReal[i]=2*matchResult.getReal(i);
|
||||
}
|
||||
|
||||
System.out.println("Max correlation result: " + PamArrayUtils.max(matchReal));
|
||||
|
||||
assertEquals(PamArrayUtils.max(matchReal), 1.0, 0.01);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -89,4 +89,16 @@ public class GenericDLClassifierTest {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Test the ketos classifier and tests are working properly. This tests loading the ketos model and also using
|
||||
* functions in KetosWorker.
|
||||
*/
|
||||
@Test
|
||||
public void humpbackWhaleTest() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -58,7 +58,8 @@ public class KetosDLClassifierTest {
|
||||
// }
|
||||
|
||||
/**
|
||||
* Test the ketos classifier and tests are working properly.
|
||||
* Test the ketos classifier and tests are working properly. This tests loading the ketos model and also using
|
||||
* functions in KetosWorker.
|
||||
*/
|
||||
@Test
|
||||
public void ketosClassifierTest() {
|
||||
@ -114,15 +115,24 @@ public class KetosDLClassifierTest {
|
||||
AudioData soundData = DLUtils.loadWavFile(wavFilePath);
|
||||
double[] soundDataD = soundData.getScaledSampleAmplitudes();
|
||||
|
||||
|
||||
long duration = (long) Math.ceil((genericModelParams.defaultSegmentLen/1000)*soundData.sampleRate);
|
||||
System.out.println("duration: " + duration + " " + soundData.sampleRate + " " + genericModelParams.defaultSegmentLen);
|
||||
|
||||
//dont't
|
||||
for (int i=1; i<ketosPredicitons.length; i++) {
|
||||
|
||||
//dont't use the first and last because these are edge cases with zero padding
|
||||
for (int i=1; i<ketosPredicitons.length-1; i++) {
|
||||
|
||||
GroupedRawData groupedRawData = new GroupedRawData(0, 1, 0, duration, (int) duration);
|
||||
int startChunk =(int) (ketosPredicitons[i][0]*soundData.sampleRate);
|
||||
|
||||
/**
|
||||
* This is super weird but Ketos has some sort of very strange system of
|
||||
* grabbing chunks of data from a sound file - seems like it grabs a little more
|
||||
* data pre the official start time. Whatever the reason this does not matter
|
||||
* for PG usually because segments simply start at the start of the wav file.
|
||||
* However for testing we have to get this right to compare results and so
|
||||
* 0.0157 is subtract from the sound chunk
|
||||
*/
|
||||
int startChunk =(int) ((ketosPredicitons[i][0]-0.0157)*soundData.sampleRate);
|
||||
|
||||
|
||||
groupedRawData.copyRawData(soundDataD, startChunk, (int) duration, 0);
|
||||
@ -136,7 +146,7 @@ public class KetosDLClassifierTest {
|
||||
boolean testPassed= output[1]> ketosPredicitons[i][2]-0.1 && output[1]< ketosPredicitons[i][2]+0.1;
|
||||
System.out.println( i+ " : Ketos whale network output: " + output[0] + " " + output[1] + " " + testPassed);
|
||||
|
||||
//assertTrue(output[1]> ketosPredicitons[i][2]-0.1 && output[1]< ketosPredicitons[i][2]+0.1);
|
||||
assertTrue(testPassed);
|
||||
|
||||
}
|
||||
|
||||
@ -151,4 +161,6 @@ public class KetosDLClassifierTest {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
103
src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java
Normal file
103
src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java
Normal file
@ -0,0 +1,103 @@
|
||||
package test.rawDeepLearningClassifier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.jamdev.jdl4pam.utils.DLUtils;
|
||||
import org.jamdev.jpamutils.wavFiles.AudioData;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams;
|
||||
import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction;
|
||||
import rawDeepLearningClassifier.dlClassification.ketos.KetosDLParams;
|
||||
import rawDeepLearningClassifier.dlClassification.ketos.KetosWorker2;
|
||||
import rawDeepLearningClassifier.dlClassification.koogu.KooguModelWorker;
|
||||
import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData;
|
||||
|
||||
public class KooguDLClassifierTest {
|
||||
|
||||
//
|
||||
// /**
|
||||
// * Test the koogu classifier and tests are working properly. This tests loading the koogu model and also using
|
||||
// * functions in KooguWorker.
|
||||
// */
|
||||
// @Test
|
||||
// public void kooguClassifierTest() {
|
||||
//
|
||||
// //relative paths to the resource folders.
|
||||
// String relModelPath = "./src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/hallo-kw-det_v1.ktpb";
|
||||
// String relWavPath = "./src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/jasco_reduced.wav";
|
||||
//
|
||||
// Path path = Paths.get(relModelPath);
|
||||
//
|
||||
// KooguModelWorker kooguWorker = new KooguModelWorker();
|
||||
//
|
||||
// StandardModelParams genericModelParams = new StandardModelParams();
|
||||
// genericModelParams.modelPath = path.toAbsolutePath().normalize().toString();
|
||||
//
|
||||
// //prep the model - all setting are included within the model
|
||||
// kooguWorker.prepModel(genericModelParams, null);
|
||||
// System.out.println("seglen: " + genericModelParams.defaultSegmentLen);
|
||||
//
|
||||
// /****Now run a file ***/
|
||||
// path = Paths.get(relWavPath);
|
||||
// String wavFilePath = path.toAbsolutePath().normalize().toString();
|
||||
//
|
||||
// try {
|
||||
//
|
||||
//
|
||||
// AudioData soundData = DLUtils.loadWavFile(wavFilePath);
|
||||
// double[] soundDataD = soundData.getScaledSampleAmplitudes();
|
||||
//
|
||||
//
|
||||
// long duration = (long) Math.ceil((genericModelParams.defaultSegmentLen/1000)*soundData.sampleRate);
|
||||
// System.out.println("duration: " + duration + " " + soundData.sampleRate + " " + genericModelParams.defaultSegmentLen);
|
||||
//
|
||||
// //dont't use the first and last because these are edge cases with zero padding
|
||||
// for (int i=1; i<ketosPredicitons.length-1; i++) {
|
||||
//
|
||||
// GroupedRawData groupedRawData = new GroupedRawData(0, 1, 0, duration, (int) duration);
|
||||
//
|
||||
// /**
|
||||
// * This is super weird but Ketos has some sort of very strange system of
|
||||
// * grabbing chunks of data from a sound file - seems like it grabs a little more
|
||||
// * data pre the official start time. Whatever the reason this does not matter
|
||||
// * for PG usually because segments simply start at the start of the wav file.
|
||||
// * However for testing we have to get this right to compare results and so
|
||||
// * 0.0157 is subtract from the sound chunk
|
||||
// */
|
||||
// int startChunk =(int) ((ketosPredicitons[i][0]-0.0157)*soundData.sampleRate);
|
||||
//
|
||||
//
|
||||
// groupedRawData.copyRawData(soundDataD, startChunk, (int) duration, 0);
|
||||
//
|
||||
// ArrayList<GroupedRawData> groupedData = new ArrayList<GroupedRawData>();
|
||||
// groupedData.add(groupedRawData);
|
||||
//
|
||||
// ArrayList<GenericPrediction> genericPrediciton = ketosWorker2.runModel(groupedData, soundData.sampleRate, 0);
|
||||
// float[] output = genericPrediciton.get(0).getPrediction();
|
||||
//
|
||||
// boolean testPassed= output[1]> ketosPredicitons[i][2]-0.1 && output[1]< ketosPredicitons[i][2]+0.1;
|
||||
// System.out.println( i+ " : Ketos whale network output: " + output[0] + " " + output[1] + " " + testPassed);
|
||||
//
|
||||
// assertTrue(testPassed);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// ketosWorker2.closeModel();
|
||||
//
|
||||
// } catch (IOException | UnsupportedAudioFileException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// assertEquals(false, true);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package test.rawDeepLearningClassifier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class StandardClassifierTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
System.out.println("Hello Units test 2");
|
||||
assertEquals(2+2, 4);
|
||||
}
|
||||
|
||||
|
||||
}
|
Binary file not shown.
BIN
src/test/resources/matchedTemplateClassifier/DS3clks_test.mat
Normal file
BIN
src/test/resources/matchedTemplateClassifier/DS3clks_test.mat
Normal file
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user