Matched click classifier and deep learning unit tests

This commit is contained in:
Jamie Mac 2024-03-13 13:43:04 +00:00
parent aa6d289984
commit db11658301
20 changed files with 1360 additions and 161 deletions

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

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

View File

@ -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.getStyleClass().add("close-button-left");
showButton.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS());
// 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.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);
this.setTop(hidingSummaryPane);
this.setCenter(scrollingDataPanel);
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() {

View File

@ -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)->{

View File

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

View File

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

View File

@ -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,11 +117,11 @@ 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);
// System.out.println("Waveform max: " + PamArrayUtils.max(testWaveform) + " len: " + testWaveform.length);
//calculate the click FFT.
@ -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();
}

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

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

View File

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

View File

@ -88,5 +88,17 @@ 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() {
}
}

View File

@ -21,49 +21,50 @@ import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData;
import org.junit.jupiter.api.Test;
public class KetosDLClassifierTest {
// /**
// * Reference to the DL Control
// *
// */
// private DLControl testDLControl;
//
//
// private KetosClassifier ketosClassifier_test;
//
// public KetosClassifierTest() {
// System.out.println("hello unit test start");
// /**
// * Reference to the DL Control
// *
// */
// private DLControl testDLControl;
//
//
// private KetosClassifier ketosClassifier_test;
//
// try {
//
// if (PamController.getInstance()==null || PamController.getInstance().getRunMode() != PamController.RUN_NORMAL) {
// PamGUIManager.setType(PamGUIManager.NOGUI);
// PamController.create(PamController.RUN_NORMAL, null);
// }
//
// testDLControl = new DLControl("Test_deep_learning");
//
// ketosClassifier_test = (KetosClassifier) testDLControl.getDLModel(KetosClassifier.MODEL_NAME);
//
// //set the ketos model as the correct model in the test.
// testDLControl.getDLParams().modelSelection= testDLControl.getDLModels().indexOf(ketosClassifier_test);
//
// System.out.println("hello unit test complete");
// }
// catch (Exception e) {
// e.printStackTrace();
// }
// }
// public KetosClassifierTest() {
// System.out.println("hello unit test start");
// try {
//
// if (PamController.getInstance()==null || PamController.getInstance().getRunMode() != PamController.RUN_NORMAL) {
// PamGUIManager.setType(PamGUIManager.NOGUI);
// PamController.create(PamController.RUN_NORMAL, null);
// }
//
// testDLControl = new DLControl("Test_deep_learning");
//
// ketosClassifier_test = (KetosClassifier) testDLControl.getDLModel(KetosClassifier.MODEL_NAME);
//
// //set the ketos model as the correct model in the test.
// testDLControl.getDLParams().modelSelection= testDLControl.getDLModels().indexOf(ketosClassifier_test);
//
// System.out.println("hello unit test complete");
// }
// catch (Exception e) {
// e.printStackTrace();
// }
// }
/**
* 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() {
/**
* List of the predicitons
* Start time (seconds), Length of the segment (seconds), prediciton
@ -88,67 +89,78 @@ public class KetosDLClassifierTest {
{80, 5.0176, 0.20126265},
{85, 5.0176, 0.9797412},
{90, 5.0176, 1}};
//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);
//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";
KetosWorker2 ketosWorker2 = new KetosWorker2();
Path path = Paths.get(relModelPath);
KetosDLParams genericModelParams = new KetosDLParams();
genericModelParams.modelPath = path.toAbsolutePath().normalize().toString();
KetosWorker2 ketosWorker2 = new KetosWorker2();
//prep the model - all setting are included within the model
ketosWorker2.prepModel(genericModelParams, null);
System.out.println("seglen: " + genericModelParams.defaultSegmentLen);
KetosDLParams genericModelParams = new KetosDLParams();
genericModelParams.modelPath = path.toAbsolutePath().normalize().toString();
/****Now run a file ***/
path = Paths.get(relWavPath);
String wavFilePath = path.toAbsolutePath().normalize().toString();
//prep the model - all setting are included within the model
ketosWorker2.prepModel(genericModelParams, null);
System.out.println("seglen: " + genericModelParams.defaultSegmentLen);
try {
/****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++) {
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++) {
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);
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(output[1]> ketosPredicitons[i][2]-0.1 && output[1]< ketosPredicitons[i][2]+0.1);
}
ketosWorker2.closeModel();
assertTrue(testPassed);
} catch (IOException | UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
assertEquals(false, true);
}
}
ketosWorker2.closeModel();
} catch (IOException | UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
assertEquals(false, true);
}
}
}

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

View File

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