From 291b00e1b1c5484a9fcc8be8caed7d8c04183a71 Mon Sep 17 00:00:00 2001 From: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:19:22 +0000 Subject: [PATCH 1/2] Merge from DG with fixes to MatchedTemplate classifier. (#119) * Stop command small change so command is available as a constant * Lots of small updates to enable opening of a secondary configuration for batch processing control. * Fix user input bug in viewer which created exponential copies of user comments! * Adding TAST trigger alarm action To be competed when GW provide correct string for interface * Echo offline detection Fix up affected datablocks for offline echo detection * fix module import System for importing modules from other psfx files was not working. Probably wasn't working for quite some time. Now fixed. * Bug fixes to Match Template classifier 1) When a large template was imported only 1: fftLength of the mathc waveform was used and so clicks would be correlated with noise. The peak of the template is now used when the peak search function is selected. 2) The plus button in the tab pane had disappeared. 3) Seems like the decimators were the wrong round. So the waveforms were using an up sample function when they should have been using a decimator function. and vice versa...major bug when using different sample rates! * Fix merge Merged in a single commit from Jamies fork that had updates to the template classifier. Then had to make a few changes to get it working with other changes J had made that must have been in other commits. --------- Co-authored-by: Jamie Mac --- .classpath | 1 - src/PamUtils/PamArrayUtils.java | 266 ++++++++++++++++-- src/PamUtils/TxtFileUtils.java | 19 +- src/clickDetector/ClickBTDisplay.java | 2 +- .../BasicIdentifierPaneFX.java | 238 ++++++++++------ .../SweepClassifierPaneFX.java | 17 +- .../ImportTemplateCSV.java | 13 +- .../MTClassifier.java | 102 +++++-- src/matchedTemplateClassifer/MTProcess.java | 13 +- .../layoutFX/MTSettingsPane.java | 2 +- .../offline/MTClassifierOfflineTask.java | 34 ++- src/pamViewFX/fxNodes/PamTabPane.java | 2 + .../fxNodes/flipPane/PamFlipPane.java | 141 +++++++++- .../fxNodes/utilityPanes/FilterPaneFX.java | 2 +- .../fxNodes/utilityPanes/LatLongPane.java | 116 ++++++-- .../fxNodes/utilityPanes/LatLongStrip.java | 244 +++++++++++----- .../fxNodes/utilsFX/TextUtilsFX.java | 40 +++ 17 files changed, 971 insertions(+), 281 deletions(-) create mode 100644 src/pamViewFX/fxNodes/utilsFX/TextUtilsFX.java diff --git a/.classpath b/.classpath index 6b516690..188c7291 100644 --- a/.classpath +++ b/.classpath @@ -8,7 +8,6 @@ - diff --git a/src/PamUtils/PamArrayUtils.java b/src/PamUtils/PamArrayUtils.java index d18ae641..e6467abd 100644 --- a/src/PamUtils/PamArrayUtils.java +++ b/src/PamUtils/PamArrayUtils.java @@ -22,19 +22,19 @@ public class PamArrayUtils { /** * Calculate the mean of one dimension within a list of points. e.g. the points might be a list of [x y z] co-ordinates in * which case the dim=0 would return the mean of all x points. - * @param array - a list of points + * @param successJump - a list of points * @param InitialtoIgnorePercentage: ignore the first percentage of results * @param dim - the dimension of the point to calculate the average for * @return the mean of one dimension of the list of the points. */ - public static double mean(ArrayList array, double InitialtoIgnorePercentage, int dim){ + public static double mean(ArrayList successJump, double InitialtoIgnorePercentage, int dim){ double meanTotal=0; int n=0; - int forStart=(int) Math.round((InitialtoIgnorePercentage)*array.size()); + int forStart=(int) Math.round((InitialtoIgnorePercentage)*successJump.size()); - for (int i=forStart; i array, double initialtoIgnorePercentage, int dim){ + public static double std(ArrayList successJump, double initialtoIgnorePercentage, int dim){ double std=0.0; int n=0; - int forStart=(int) Math.round((initialtoIgnorePercentage)*array.size()); + int forStart=(int) Math.round((initialtoIgnorePercentage)*successJump.size()); - double meanTotal= mean(array, initialtoIgnorePercentage, dim); + double meanTotal= mean(successJump, initialtoIgnorePercentage, dim); //calculate standard deviation - for (int k=forStart;kmax) { + index = count; + max=cur; + } + count++; + } + + + return index; + } + + /** + * Get the minimum index of an array + * @param arr - the array to find the position of the maximum value. + * m value of. + * @return the index of the minimum value + */ + public static int minPos(double[] arr) { + double max = Double.POSITIVE_INFINITY; + int index = -1; + + int count = 0; + for(double cur: arr) { + if (cur= 0; i--) { PamDataUnit subDet = superDet.getSubDetection(i); - if (subDet.getChannelBitmap() == clickDetection.getChannelBitmap()) { + if (subDet!=null && subDet.getChannelBitmap() == clickDetection.getChannelBitmap()) { double ici = (double) (clickDetection.getTimeMilliseconds() - subDet.getTimeMilliseconds())/1000.; clickDetection.setTempICI(ici); break; diff --git a/src/clickDetector/layoutFX/clickClassifiers/BasicIdentifierPaneFX.java b/src/clickDetector/layoutFX/clickClassifiers/BasicIdentifierPaneFX.java index 59123830..e2cf0f39 100644 --- a/src/clickDetector/layoutFX/clickClassifiers/BasicIdentifierPaneFX.java +++ b/src/clickDetector/layoutFX/clickClassifiers/BasicIdentifierPaneFX.java @@ -1,13 +1,18 @@ package clickDetector.layoutFX.clickClassifiers; import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamSymbolFX; import pamViewFX.fxNodes.flipPane.PamFlipPane; import pamViewFX.fxNodes.table.TableSettingsPane; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; import javafx.scene.Node; +import javafx.scene.canvas.Canvas; import javafx.scene.control.Button; import javafx.scene.control.Dialog; +import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.cell.CheckBoxTableCell; import clickDetector.BasicClickIdParameters; @@ -15,6 +20,7 @@ import clickDetector.ClickControl; import clickDetector.ClickTypeParams; import clickDetector.ClickClassifiers.ClickIdentifier; import clickDetector.ClickClassifiers.basic.BasicClickIdentifier; +import clickDetector.ClickClassifiers.basicSweep.*; /** * Pane for the basic click classifier. @@ -23,33 +29,33 @@ import clickDetector.ClickClassifiers.basic.BasicClickIdentifier; * */ public class BasicIdentifierPaneFX implements ClassifyPaneFX { - + /** * Reference to the basicClickIdentifier. */ private ClickIdentifier basicClickIdentifier; - + /** * Reference to the click control. */ protected ClickControl clickControl; - + /** * Pane which holds table data. */ - private TableSettingsPane settingsPane; - -// /** -// * Hiding pane which slides out to allow users to change click type settings. -// */ -// protected HidingPane hidingPane; - + protected TableSettingsPane clickTypesTable; + + // /** + // * Hiding pane which slides out to allow users to change click type settings. + // */ + // protected HidingPane hidingPane; + /** * Holds click classifier controls inside hiding pane. */ protected PamBorderPane clickTypeHolder; - + /** * A list of click classifiers currently shown in the table.It Would have been much easier to have this in params * but didn't want to add any FX related as these should be GUI independent classes. @@ -60,19 +66,19 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { * Holds the table. */ protected PamBorderPane mainHolderPane; - -// /** -// * The width of the hiding pane -// */ -// private double hidingPaneWidth=900; + + // /** + // * The width of the hiding pane + // */ + // private double hidingPaneWidth=900; /** * Cloned copy of BasicClickIdParameters. */ private BasicClickIdParameters basicClickIdParameters; - + private PamBorderPane mainPane; - + private PamFlipPane flipPane; /** @@ -85,7 +91,7 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { this.basicClickIdentifier= basicClickIdentifier; this.clickControl=clickControl; mainPane= new PamBorderPane(); - + flipPane = new PamFlipPane(); flipPane.getFrontPane().setCenter(createSettingsPane()); flipPane.getBackPane().setCenter(clickTypeHolder); @@ -93,35 +99,36 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { mainPane.setCenter(flipPane); } - - - + + + /** * Create the controls for the basic click identifier pane. * @return node with all controls for basic click classifier. */ protected Node createSettingsPane(){ - + mainHolderPane=new PamBorderPane(); - mainHolderPane.setCenter(settingsPane=new ClickClassifierTable(clickClassifiers)); - + mainHolderPane.setPadding(new Insets(5,5,5,5)); + mainHolderPane.setCenter(clickTypesTable=new ClickClassifierTable(clickClassifiers)); + clickTypeHolder=new PamBorderPane(); //clickTypeHolder.setPrefWidth(hidingPaneWidth); - + return mainHolderPane; - + } - -// /** -// * Added purely so can be override and hiding pane set in different location if required -// */ -// public void createHidingPane(){ -// hidingPane=new HidingPane(Side.RIGHT, clickTypeHolder, mainPane, false); -// //hidingPane.showHidePane(false); -// mainHolderPane.setRight(hidingPane); -// hidingPane.showHidePane(false); -// } - + + // /** + // * Added purely so can be override and hiding pane set in different location if required + // */ + // public void createHidingPane(){ + // hidingPane=new HidingPane(Side.RIGHT, clickTypeHolder, mainPane, false); + // //hidingPane.showHidePane(false); + // mainHolderPane.setRight(hidingPane); + // hidingPane.showHidePane(false); + // } + @Override @@ -133,7 +140,7 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { public void setParams() { basicClickIdParameters = ((BasicClickIdentifier) basicClickIdentifier).getIdParameters().clone(); //change the observable list. - + } @Override @@ -151,9 +158,9 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { @Override public void setActive(boolean b) { // TODO Auto-generated method stub - + } - + /** * Class which extends TableSettingsPane and creates a sliding pane instead of a dialog when an item is added. * @author Jamie Macaulay @@ -164,33 +171,43 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { public ClickClassifierTable(ObservableList data) { super(data); //need to set up all the rows. - - TableColumn name = new TableColumn("Name"); - name.setCellValueFactory(cellData -> cellData.getValue().name); - name.setEditable(true); - + + TableColumn icon = new TableColumn("Name"); + icon.setCellValueFactory(cellData -> cellData.getValue().name); + icon.setEditable(false); + + icon.setCellFactory((tableColumn) -> { + TableCell tableCell = new ClickTypeNameCell(); + return tableCell; + }); + + // TableColumn name = new TableColumn("Name"); + // name.setCellValueFactory(cellData -> cellData.getValue().name); + // name.setEditable(true); + + TableColumn code = new TableColumn("Species Code"); code.setCellValueFactory(cellData -> cellData.getValue().code); - + TableColumn checkCol = new TableColumn<>("Enable"); checkCol.setCellValueFactory( cellData -> cellData.getValue().enableClassifier); checkCol.setCellFactory(CheckBoxTableCell.forTableColumn(checkCol)); checkCol.setEditable(true); checkCol.setMaxWidth( 100 ); checkCol.setMinWidth( 100 ); - - + + TableColumn discard = new TableColumn<>("Discard"); discard.setCellValueFactory( cellData -> cellData.getValue().discardClassifier); discard.setCellFactory(CheckBoxTableCell.forTableColumn(checkCol)); discard.setEditable(true); discard.setMaxWidth( 100 ); discard.setMinWidth( 100 ); - + getTableView().setEditable(true); - - getTableView().getColumns().addAll(checkCol, name, code, discard); + + getTableView().getColumns().addAll(checkCol, icon, code, discard); } @@ -206,33 +223,33 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { showFlipPane(true); return null; } - + @Override public void editData(ClickTypeProperty data){ setClassifierPane(data); showFlipPane(true); } - + @Override public void createNewData(){ //create a new classifier. clickClassifiers.add(createClickTypeProperty()); } - + } - -// /** -// * Show the hiding pane which contains classifier settings -// * NOTE: needed to add this to stop a stack overflow error in BasicClickIdentifier 06/09/2016 -// * @param show - true to show pane. -// */ -// public void showHidingPane(boolean show){ -// if (hidingPane==null){ -// this.createHidingPane(); -// } -// hidingPane.showHidePane(true); -// } - + + // /** + // * Show the hiding pane which contains classifier settings + // * NOTE: needed to add this to stop a stack overflow error in BasicClickIdentifier 06/09/2016 + // * @param show - true to show pane. + // */ + // public void showHidingPane(boolean show){ + // if (hidingPane==null){ + // this.createHidingPane(); + // } + // hidingPane.showHidePane(true); + // } + /** * Show the flip pane. * NOTE: needed to add this to stop a stack overflow error in BasicClickIdentifier 06/09/2016 @@ -247,33 +264,34 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { flipPane.flipToFront(); } } - + /** * Create click classifier. */ public ClickTypeProperty createClickTypeProperty(){ return new ClickTypeProperty(new ClickTypeParams(clickClassifiers.size())); } - + /** * Set classifier pane within hiding pane. * @param clickTypeProperty */ public void setClassifierPane(ClickTypeProperty clickTypeProperty){ - - + ClickTypePaneFX clickTypePane=new ClickTypePaneFX(); clickTypePane.setParams(clickTypeProperty); - + clickTypeHolder.setCenter(clickTypePane.getContentNode()); - + //now need to make sure on closing the pane that settings are saved. Need to //remove the old click type from the list and add new one in the same position. getFlipPaneCloseButton().setOnAction((action)->{ - //System.out.println("CLOSE FLIP PANE"); showFlipPane(false); //this should update the click type property in the observable list thus changing the table clickTypePane.getParams(clickTypeProperty); + + //need to refresh table to show symbol. + clickTypesTable.getTableView().refresh(); }); } @@ -285,16 +303,16 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { return flipPane.getBackButton(); } -// /** -// * Get the hiding pane which holds settings for different click types. -// * @return the HidingPane for click classifiers. -// */ -// public HidingPane getClickTypeHidingPane() { -// if (hidingPane==null) { -// this.createHidingPane(); -// } -// return hidingPane; -// } + // /** + // * Get the hiding pane which holds settings for different click types. + // * @return the HidingPane for click classifiers. + // */ + // public HidingPane getClickTypeHidingPane() { + // if (hidingPane==null) { + // this.createHidingPane(); + // } + // return hidingPane; + // } /** * Get the pane which holds the ClickTypePaneFX. @@ -303,15 +321,15 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { public PamBorderPane getClickTypeHolder() { return clickTypeHolder; }; - + /** * Get the table which holds a list of classifier * @return table which holds a list of classifiers */ public TableSettingsPane getTablePane() { - return this.settingsPane; + return this.clickTypesTable; }; - + /** * Get list of click classifiers * @return list of click classifiers @@ -329,4 +347,48 @@ public class BasicIdentifierPaneFX implements ClassifyPaneFX { return flipPane; } + + /** + * Shows a name alongside a graphic showing the current symbol. + * @author Jamie Macaulay. + * + */ + private class ClickTypeNameCell extends TableCell { + + /** + * Symbol is drawn on the canvas + */ + Canvas canvas; + + public static final double SYMBOL_SIZE = 20; + + public ClickTypeNameCell() { + super(); + canvas = new Canvas(SYMBOL_SIZE, SYMBOL_SIZE); + } + + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + + this.setText(item); + + this.setGraphic(null); + + if (this.getTableRow()!=null && this.getTableRow().getItem()!=null) { + + SweepClassifierSet clickProperty = (SweepClassifierSet) this.getTableRow().getItem().getClickType(); + + if (clickProperty.symbol!=null) { + canvas.getGraphicsContext2D().clearRect(0, 0, SYMBOL_SIZE, SYMBOL_SIZE); + PamSymbolFX pamSymbolFX = new PamSymbolFX(clickProperty.symbol); + pamSymbolFX.draw(canvas.getGraphicsContext2D(), new Point2D(SYMBOL_SIZE/2,SYMBOL_SIZE/2), SYMBOL_SIZE, SYMBOL_SIZE); + this.setGraphic(canvas); + } + } + + } + }; + } diff --git a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java index e9a0979b..ba4fbf5e 100644 --- a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java +++ b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java @@ -1,10 +1,16 @@ package clickDetector.layoutFX.clickClassifiers; + import clickDetector.ClickControl; import clickDetector.ClickClassifiers.basicSweep.SweepClassifier; import clickDetector.ClickClassifiers.basicSweep.SweepClassifierParameters; import clickDetector.ClickClassifiers.basicSweep.SweepClassifierSet; +import javafx.scene.text.FontPosture; +import javafx.scene.text.FontWeight; +import javafx.scene.layout.Region; +import javafx.scene.text.Font; +import javafx.geometry.Insets; /** * Slightly different pane for the sweep classifier. @@ -38,12 +44,17 @@ public class SweepClassifierPaneFX extends BasicIdentifierPaneFX { @Override public void setClassifierPane(ClickTypeProperty clickTypeProperty){ SweepClassifierSetPaneFX sweepPane=new SweepClassifierSetPaneFX(sweepClickClassifier); - + + //set padding - want the flip pane not to have padding so back button reaches edge of node. + ((Region) sweepPane.getContentNode()).setPadding(new Insets(5,5,5,5)); //make it so the title of the pane is the same as the name as the classifier getFlipPane().getAdvLabel().textProperty().unbind(); - getFlipPane().getAdvLabel().textProperty().bind( sweepPane.getNameTextProperty()); + getFlipPane().getAdvLabel().textProperty().bind(sweepPane.getNameTextProperty()); + // removed DG 2023 12 14 since not everything was in this merge that was required Needs to be put back. +// getFlipPane().getPreAdvLabel().graphicProperty().bind(sweepPane.getNameGraphicProperty()); + sweepPane.classifierItemRow = sweepClickClassifier.getSweepClassifierParams().getSetRow((SweepClassifierSet) clickTypeProperty.getClickType()); sweepPane.setParams(clickTypeProperty); @@ -54,6 +65,8 @@ public class SweepClassifierPaneFX extends BasicIdentifierPaneFX { getFlipPaneCloseButton().setOnAction((action)->{ showFlipPane(false); sweepPane.getParams(clickTypeProperty); + //need to refresh table to show symbol. + clickTypesTable.getTableView().refresh(); }); } diff --git a/src/matchedTemplateClassifer/ImportTemplateCSV.java b/src/matchedTemplateClassifer/ImportTemplateCSV.java index 0713f026..69b1063e 100644 --- a/src/matchedTemplateClassifer/ImportTemplateCSV.java +++ b/src/matchedTemplateClassifer/ImportTemplateCSV.java @@ -2,7 +2,9 @@ package matchedTemplateClassifer ; import java.io.File; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import PamUtils.PamArrayUtils; import PamUtils.TxtFileUtils; @@ -44,16 +46,21 @@ public class ImportTemplateCSV implements TemplateImport { //System.out.println("i: " + i + " : " + data.get(0).get(i)); waveform[i]=data.get(0).get(i); } +// System.out.println("String sR = " + data.get(1).get(0)); + + + //used big decimal here because String.,floatValue did not handle numbers like 3.85e05 + float sR = new BigDecimal(data.get(1).get(0)).floatValue(); - float sR=data.get(1).get(0).floatValue(); +// float sR=data.get(1).get(0).floatValue(); //System.out.println("imported waveform"); //PamArrayUtils.printArrayRaw(waveform); //now create waveform -// System.out.println("Create a waveform with " + waveform.length + " samples with a sample rate of " -// + sR + " Hz"); + System.out.println("Import a waveform with " + waveform.length + " samples with a sample rate of " + + sR + " Hz "); MatchTemplate matchedTemplate = new MatchTemplate(filePath.getName(), waveform, sR); //TODO return matchedTemplate; diff --git a/src/matchedTemplateClassifer/MTClassifier.java b/src/matchedTemplateClassifer/MTClassifier.java index 476d4735..ddde2bc7 100644 --- a/src/matchedTemplateClassifer/MTClassifier.java +++ b/src/matchedTemplateClassifer/MTClassifier.java @@ -129,7 +129,7 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters if (fft==null) fft=new FastFFT(); - //System.out.println("interpWaveform: " + waveformMatch.waveform.length + " sR " + waveformMatch.sR); +// System.out.println("interpWaveform: " + waveformMatch.waveform.length + " sR " + waveformMatch.sR); //re-sample the waveform if the sample rate is different this.interpWaveformMatch=interpWaveform(this.waveformMatch, sR); @@ -145,7 +145,12 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters // System.out.println("MatchNorm: MATCH"); // MTClassifierTest.normalizeTest(interpWaveformMatch); - waveformMatchFFT = fft.rfft(interpWaveformMatch, length); + /** + * There is an issue here because, if we have a long template waveform, then it + * will become truncated and the actual waveform may be missed. This means we + * have to use the peak of the template + */ + waveformMatchFFT = calcTemplateFFT(interpWaveformMatch, length); //need to calculate the complex conjugate - note that originally I was flipping the array but this means //the max value does not equal one with identical waveforms...doh. @@ -157,6 +162,42 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters return waveformMatchFFT; } + + /** + * Calculate the FFT of an interpolate match template. + * @param interpTemplateWaveform - the waveform interpolated to the correct sample rate. + * @param length - the length of the FFT. + * @return the FFT of the waveform as a complex array. + */ + private ComplexArray calcTemplateFFT(double[] interpTemplateWaveform, int length) { + + ComplexArray fftTemplate; + /** + * There is an issue here because, if we have a long template waveform, then it + * will become truncated and the actual waveform may be missed. This means we + * have to use the peak of the template + */ + if (interpTemplateWaveform.length>length) { + //If the template is long then need to find the peak, otherwise we will end up cross correlating with noise at the + //start of the template. + //because this is a template and not a random click we don't need to be so clever with how we find peaks. Find + //the maximum and use around that. + int pos = PamArrayUtils.maxPos(interpTemplateWaveform); + + int startind = Math.max(0, pos-length/2); + int endind = startind+length-1; + + double[] peakTemplate = ArrayUtils.subarray(interpTemplateWaveform, startind, endind); + fftTemplate = fft.rfft(peakTemplate, length); + } + else { + //template waveform is padded by fft function + fftTemplate = fft.rfft(interpTemplateWaveform, length); + } + + return fftTemplate; + } + /** @@ -181,13 +222,17 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters // this.inteprWaveformReject=PamArrayUtils.divide(inteprWaveformReject, PamArrayUtils.max(inteprWaveformReject)); this.inteprWaveformReject = normaliseWaveform(inteprWaveformReject, this.normalisation); - // System.out.println("MatchNorm: REJECT "); // MTClassifierTest.normalizeTest(inteprWaveformReject); // MTClassifierTest.printWaveform(inteprWaveformReject); - //System.out.println("waveformReject: " +inteprWaveformReject.length + " fftLength: " + getFFTLength(sR)); - waveformRejectFFT = fft.rfft(inteprWaveformReject, length); + /** + * There is an issue here because, if we have a long template waveform, then it + * will become truncated and the actual waveform may be missed. This means we + * have to use the peak of the template + */ + waveformRejectFFT = calcTemplateFFT(inteprWaveformReject, length); + //need to calculate the complex conjugate - note that originally I was flipping the array but this means //the max value does not equal one with identical waveforms...doh. @@ -293,7 +338,7 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters ComplexArray matchTemplate = getWaveformMatchFFT(sR, matchResult.length()); - //System.out.println("Match template length: " + matchTemplate.length() + "Click : " + click.length()); +// System.out.println("Match template length: " + matchTemplate.length() + "Click : " + click.length()); for (int i=0; isR) { + if (waveformMatch.sRsR){ + //decimate // //TODO - make a better decimator? // double[] interpWaveformMatch=reSampleWaveform(waveformMatch.waveform, waveformMatch.sR, sR); // return interpWaveformMatch; @@ -494,11 +541,9 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters return wavInterpolator.decimate(waveformMatch.waveform, waveformMatch.sR, (float) sR); } else { - //nothing needed/ + //nothing needed return waveformMatch.waveform; - } - - + } } @@ -529,7 +574,8 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters // // TODO Auto-generated method stub // return PamInterp.interpLinear(x, waveform, xi); - return PamInterp.interpWaveform(waveform, 1/binSize); +// System.out.println("Interp waveform: " + binSize); + return PamInterp.interpWaveform(waveform, 1./binSize); } diff --git a/src/matchedTemplateClassifer/MTProcess.java b/src/matchedTemplateClassifer/MTProcess.java index 32c46040..92e86a2f 100644 --- a/src/matchedTemplateClassifer/MTProcess.java +++ b/src/matchedTemplateClassifer/MTProcess.java @@ -5,6 +5,7 @@ import java.util.Arrays; import PamController.PamController; import PamDetection.RawDataUnit; +import PamUtils.PamArrayUtils; import PamUtils.complex.ComplexArray; import PamView.symbol.PamSymbolManager; import PamguardMVC.PamDataBlock; @@ -288,6 +289,7 @@ public class MTProcess extends PamInstantProcess { @SuppressWarnings("unused") private double[] getWaveData(RawDataHolder clickDetection, int i) { double[] waveform; + if (this.getMTParams().peakSearch) { waveform = createRestrictedLenghtWave(clickDetection, i, lengthData[i], this.getMTParams().restrictedBins); @@ -377,7 +379,14 @@ public class MTProcess extends PamInstantProcess { */ private double[] createRestrictedLenghtWave(RawDataHolder click, int chan, int[] lengthPoints, int restrictedBins) { - return createRestrictedLenghtWave(click, chan, lengthPoints, restrictedBins, getWindow(restrictedBins)); +// System.out.println("Create restricted length wave: " + lengthPoints[0] + " to " + lengthPoints[1]); +// System.out.println("Max before restrict: " + PamArrayUtils.max(click.getWaveData()[chan])); + + double[] wave = createRestrictedLenghtWave(click, chan, lengthPoints, restrictedBins, getWindow(restrictedBins)); + +// System.out.println("Max after restrict: " + PamArrayUtils.max(click.getWaveData()[chan])); + + return wave; } @@ -412,7 +421,7 @@ public class MTProcess extends PamInstantProcess { ArrayList results = new ArrayList(); - //System.out.println("Click waveform max: " + PamArrayUtils.max(clickWaveform) + " sample rate: " + sR); + System.out.println("Click waveform max: " + PamArrayUtils.max(clickWaveform) + " sample rate: " + sR); //normalisation and picking peak has already been performed diff --git a/src/matchedTemplateClassifer/layoutFX/MTSettingsPane.java b/src/matchedTemplateClassifer/layoutFX/MTSettingsPane.java index 2ca0748e..c9c545f6 100644 --- a/src/matchedTemplateClassifer/layoutFX/MTSettingsPane.java +++ b/src/matchedTemplateClassifer/layoutFX/MTSettingsPane.java @@ -328,7 +328,7 @@ public class MTSettingsPane extends SettingsPane { private Node createClassifierPane(){ //with just one classifier. - pamTabPane = new PamTabPane(); + pamTabPane = new PamTabPane(); pamTabPane.setAddTabButton(true); // pamTabPane.getAddTabButton().setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.ADD, PamGuiManagerFX.iconSize)); pamTabPane.getAddTabButton().setGraphic(PamGlyphDude.createPamIcon("mdi2p-plus", PamGuiManagerFX.iconSize)); diff --git a/src/matchedTemplateClassifer/offline/MTClassifierOfflineTask.java b/src/matchedTemplateClassifer/offline/MTClassifierOfflineTask.java index 99b9ca68..f2544265 100644 --- a/src/matchedTemplateClassifer/offline/MTClassifierOfflineTask.java +++ b/src/matchedTemplateClassifer/offline/MTClassifierOfflineTask.java @@ -40,19 +40,31 @@ public class MTClassifierOfflineTask extends OfflineTask> { @Override public boolean processDataUnit(PamDataUnit dataUnit) { - count++; - mtClassifierControl.getMTProcess().newClickData(dataUnit); + try { +// System.out.println("MT new data unit: " + dataUnit); + + + count++; + mtClassifierControl.getMTProcess().newClickData(dataUnit); + + //since an annotation has been added might need to do this so that the data unit is actually saved. + DataUnitFileInformation fileInfo = dataUnit.getDataUnitFileInformation(); + +// System.out.println("file info: " + fileInfo); + if (fileInfo != null) { + fileInfo.setNeedsUpdate(true); + } + dataUnit.updateDataUnit(System.currentTimeMillis()); + + + return true; - //since an annotation has been added might need to do this so that the data unit is actually saved. - DataUnitFileInformation fileInfo = dataUnit.getDataUnitFileInformation(); - - //System.out.println("file info: " + fileInfo); - if (fileInfo != null) { - fileInfo.setNeedsUpdate(true); } - dataUnit.updateDataUnit(System.currentTimeMillis()); - - return true; + catch (Exception e) { + e.printStackTrace(); + } + return false; + } diff --git a/src/pamViewFX/fxNodes/PamTabPane.java b/src/pamViewFX/fxNodes/PamTabPane.java index 835b5699..9116ed82 100644 --- a/src/pamViewFX/fxNodes/PamTabPane.java +++ b/src/pamViewFX/fxNodes/PamTabPane.java @@ -96,10 +96,12 @@ public class PamTabPane extends TabPane { } /** + * TODO - the button is removed and then added again it does not seem to appear.... * Set whether a button shows to add tabs to the TabPane * @param addTabButton - true to show a button next to the last tab which allows new tabs to be added. */ public void setAddTabButton(boolean addTabButton) { + if (this.addTabButton==addTabButton) return; this.addTabButton = addTabButton; } diff --git a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java index 1f0a4ca8..c5a12aaf 100644 --- a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java +++ b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java @@ -1,17 +1,26 @@ package pamViewFX.fxNodes.flipPane; -import javafx.geometry.Insets; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.geometry.Orientation; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.control.Labeled; +import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamButton; import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.utilsFX.TextUtilsFX; /** * Flip pane which has is supposed to be used for advanced settings. The front @@ -32,15 +41,34 @@ public class PamFlipPane extends FlipPane { private PamBorderPane frontPane; - private Label advLabel; private PamButton backButton; + /** + * Text field in the title of the advanced pane. This can be used to change settings. + */ + private TextField advLabel; + + /** + * Label which sits before the text field in the advanced settings pane title + */ + private Label preLabel; + + /** + * Label after the the text field in the advanced pane label - this can be set to say "settings" for example with the text field + * then editable to change the name of a parameter. + */ + private Label postLabel; + public PamFlipPane() { super(); this.advPane = createAdvSettingsPane(); this.getFront().getChildren().add(frontPane = new PamBorderPane()); + +// this.getFront().setStyle("-fx-background-color: grey;"); +// this.getBack().setStyle("-fx-background-color: grey;"); + this.getBack().getChildren().add(advPane); this.setFlipTime(FLIP_TIME); @@ -77,6 +105,23 @@ public class PamFlipPane extends FlipPane { public PamBorderPane getAdvPane() { return advPane; } + + + /** + * Set the advanced pane content. + * @param - the content to set. + */ + public void setAdvPaneContent(Node content) { + advPane.setCenter(content); + } + + /** + * Set the front pane content. + * @param - the content to set. + */ + public void setFrontContent(Node content) { + frontPane.setCenter(content); + } /** @@ -86,29 +131,70 @@ public class PamFlipPane extends FlipPane { backButton = new PamButton(); backButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-left", Color.WHITE, PamGuiManagerFX.iconSize)); +// backButton.setStyle("-fx-background-color: -color-base-6"); + //backButton.setStyle("-fx-padding: 0,0,0,0"); backButton.setOnAction((action)->{ // System.out.println("FLIP BACK TO FRONT"); this.flipToFront(); }); + //make the back button blue so users can easily see the button. + backButton.setStyle("-fx-background-radius: 0 5 5 0; -fx-border-radius: 0 5 5 0; -fx-background-color: -color-accent-6"); + //backButton.setPrefWidth(150); - PamBorderPane advPane = new PamBorderPane(); - advPane.setPadding(new Insets(5,5,5,5)); + //advPane.setPadding(new Insets(5,5,5,5)); + + // holds the title of the advanced pane. This consists of a label for a graphic, + // an editable text field and a label after the editable settings field + PamHBox titleHolder = new PamHBox(); + titleHolder.getChildren().addAll(preLabel = new Label(), advLabel = new TextField("Adv. "), postLabel = new Label("Settings")); + preLabel.setId("label-title2"); + postLabel.setId("label-title2"); + titleHolder.setAlignment(Pos.CENTER); + postLabel.setTextAlignment(TextAlignment.LEFT); + postLabel.setAlignment(Pos.CENTER_LEFT); + + advLabel.setAlignment(Pos.CENTER); +// advLabel.prefColumnCountProperty().bind(advLabel.textProperty().length().subtract(3)); + // Set Max and Min Width to PREF_SIZE so that the TextField is always PREF + advLabel.setMinWidth(Region.USE_PREF_SIZE); + advLabel.setMaxWidth(Region.USE_PREF_SIZE); + + //pretty complicated to make sure the text field is the same size as the text that is being typed. + advLabel.textProperty().addListener((ov, prevText, currText) -> { + // Do this in a Platform.runLater because of Textfield has no padding at first time and so on + Platform.runLater(() -> { + Text text = new Text(currText); + text.setFont(advLabel.getFont()); // Set the same font, so the size is the same + double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField + + advLabel.getPadding().getLeft() + advLabel.getPadding().getRight() // Add the padding of the TextField + + 2d; // Add some spacing + advLabel.setPrefWidth(width); // Set the width + advLabel.positionCaret(advLabel.getCaretPosition()); // If you remove this line, it flashes a little bit + }); + }); + advLabel.setId("label-title2"); + advLabel.setStyle("-fx-background-color: transparent"); + + titleHolder.setMaxWidth(Double.MAX_VALUE); //need to make sure label is in center. + + //holds the back button and the title pane. PamHBox buttonHolder = new PamHBox(); - buttonHolder.setBackground(null); //buttonHolder.setStyle("-fx-background-color: red;"); buttonHolder.setAlignment(Pos.CENTER_LEFT); - buttonHolder.getChildren().addAll(backButton, advLabel = new Label("Adv. Settings")); + buttonHolder.getChildren().addAll(backButton, titleHolder); + advLabel.setAlignment(Pos.CENTER); advLabel.setMaxWidth(Double.MAX_VALUE); //need to make sure label is in center. - PamGuiManagerFX.titleFont2style(advLabel); - +// PamGuiManagerFX.titleFont2style(advLabel); + + advLabel.setAlignment(Pos.CENTER); - HBox.setHgrow(advLabel, Priority.ALWAYS); + HBox.setHgrow(titleHolder, Priority.ALWAYS); advPane.setTop(buttonHolder); @@ -117,16 +203,45 @@ public class PamFlipPane extends FlipPane { } - public Label getAdvLabel() { + public TextField getAdvLabel() { return advLabel; } - public void setAdvLabel(Label advLabel) { - this.advLabel = advLabel; - } +// public void setAdvLabel(Label advLabel) { +// this.advLabel = advLabel; +// } public PamButton getBackButton() { return backButton; } + + /** + * Get the label located before the editable label in the title + * @return the label before the editable label + */ + public Label getPreAdvLabel() { + return preLabel; + } + + /** + * Get the label located after the editable label in the title + * @return the label after the editable label + */ + public Label getPostAdvLabel() { + return postLabel; + } + + /** + * True if the flip pane is showing the front. + */ + public boolean isShowingFront() { + return super.flipFrontProperty().get(); + } + + public void setAdvLabelEditable(boolean b) { + this.advLabel.setEditable(b); + + } + } diff --git a/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java b/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java index c758442f..33eb11c1 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java +++ b/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java @@ -143,7 +143,7 @@ public class FilterPaneFX extends SettingsPane { } else { mainPane.setTop(createFilterPane()); - mainPane.setBottom(createBodeGraph()); + mainPane.setCenter(createBodeGraph()); } } diff --git a/src/pamViewFX/fxNodes/utilityPanes/LatLongPane.java b/src/pamViewFX/fxNodes/utilityPanes/LatLongPane.java index 6edc9771..711491dd 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/LatLongPane.java +++ b/src/pamViewFX/fxNodes/utilityPanes/LatLongPane.java @@ -1,19 +1,24 @@ package pamViewFX.fxNodes.utilityPanes; +import org.controlsfx.control.SegmentedButton; + import PamController.SettingsPane; import PamUtils.LatLong; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.control.RadioButton; -import javafx.scene.control.ToggleGroup; -import pamViewFX.fxNodes.PamBorderPane; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleButton; +import javafx.scene.text.TextAlignment; +import pamViewFX.PamGuiManagerFX; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamVBox; /** * Pane with controls to to set the values for a Latitude and Longitude. The pane allows users to * change the Latitude and Longitude in both Degrees, Decimal minutes and Degrees, Minutes, Seconds + * * @author Jamie Macaulay * */ @@ -27,37 +32,62 @@ public class LatLongPane extends SettingsPane{ /** * The radio button to select decimal minutes */ - private RadioButton decimalMinutes; + private ToggleButton decimalMinutes; /** * Radio button to input minutes and seconds. */ - private RadioButton minutesSeconds; + private ToggleButton minutesSeconds; /** * Lat long strip */ private LatLongStrip latStrip, longStrip; - private PamBorderPane mainPane; + private PamVBox mainPane; + + private ToggleButton decimal; + + /** + * Segmented button that also selection of the latitude and longitude format type. + */ + private SegmentedButton segmentedButton; public LatLongPane(String title) { super(null); + + + mainPane = new PamVBox(); + mainPane.setSpacing(5); + mainPane.setAlignment(Pos.CENTER); + + + Label titleLabel = new Label(title); + titleLabel.maxWidth(Double.MAX_VALUE); + titleLabel.setTextAlignment(TextAlignment.LEFT); + PamGuiManagerFX.titleFont2style(titleLabel); + mainPane.getChildren().add(titleLabel); + latLong= new LatLong(); - mainPane = new PamBorderPane(); + + decimalMinutes = new ToggleButton("Degrees, Decimal minutes"); + minutesSeconds = new ToggleButton("Degrees, Minutes, Seconds"); + decimal = new ToggleButton("Decimal"); + + segmentedButton = new SegmentedButton(); + segmentedButton.getButtons().addAll(decimalMinutes, minutesSeconds, decimal); PamHBox top = new PamHBox(); top.setSpacing(5); - top.getChildren().add(new Label("Unit type :")); - top.getChildren().add(decimalMinutes = new RadioButton("Degrees, Decimal minutes")); - top.getChildren().add(minutesSeconds = new RadioButton("Degrees, Minutes, Seconds")); + top.getChildren().add(segmentedButton); - ToggleGroup bg = new ToggleGroup(); - decimalMinutes.setToggleGroup(bg); - minutesSeconds.setToggleGroup(bg); + // ToggleGroup bg = new ToggleGroup(); + // decimalMinutes.setToggleGroup(bg); + // minutesSeconds.setToggleGroup(bg); + // decimal.setToggleGroup(bg); decimalMinutes.setOnAction((action)->{ actionPerformed(action); @@ -66,7 +96,12 @@ public class LatLongPane extends SettingsPane{ minutesSeconds.setOnAction((action)->{ actionPerformed(action); }); - mainPane.setTop(top); + + decimal.setOnAction((action)->{ + actionPerformed(action); + }); + + mainPane.getChildren().add(top); PamVBox cent = new PamVBox(); cent.setSpacing(5); @@ -75,8 +110,12 @@ public class LatLongPane extends SettingsPane{ cent.getChildren().add(latStrip = new LatLongStrip(true)); cent.getChildren().add(longStrip = new LatLongStrip(false)); - mainPane.setCenter(cent); + //bit of a hack that makes sure controls are aligned for the latitude and longitude. + latStrip.getTitleLabel().prefWidthProperty().bind(longStrip.getTitleLabel().widthProperty()); + mainPane.getChildren().add(cent); + + decimal.setSelected(true); } @@ -85,16 +124,17 @@ public class LatLongPane extends SettingsPane{ */ public void actionPerformed(javafx.event.ActionEvent action) { + if (action.getSource() == decimalMinutes) { LatLong.setFormatStyle(LatLong.FORMAT_DECIMALMINUTES); - // if (latStrip != null) { - // latStrip.setDecimalMinutes(true); - // longStrip.setDecimalMinutes(true); - // } + // if (latStrip != null) { + // latStrip.setDecimalMinutes(true); + // longStrip.setDecimalMinutes(true); + // } latStrip.showControls(LatLong.FORMAT_DECIMALMINUTES); longStrip.showControls(LatLong.FORMAT_DECIMALMINUTES); } - else if (action.getSource() == minutesSeconds) { + else if (action.getSource() == minutesSeconds){ LatLong.setFormatStyle(LatLong.FORMAT_MINUTESSECONDS); // if (latStrip != null) { // latStrip.setDecimalMinutes(false); @@ -105,19 +145,26 @@ public class LatLongPane extends SettingsPane{ latStrip.showControls(LatLong.FORMAT_MINUTESSECONDS); longStrip.showControls(LatLong.FORMAT_MINUTESSECONDS); } - + else if (action.getSource() == decimal){ + LatLong.setFormatStyle(LatLong.FORMAT_DECIMAL); + latStrip.showControls(LatLong.FORMAT_DECIMAL); + longStrip.showControls(LatLong.FORMAT_DECIMAL); + } } private void showLatLong() { - decimalMinutes.setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_DECIMALMINUTES); - minutesSeconds.setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_MINUTESSECONDS); - latStrip.showControls(LatLong.getFormatStyle() ); - longStrip.showControls(LatLong.getFormatStyle() ); - latStrip.sayValue(latLong.getLatitude()); - longStrip.sayValue(latLong.getLongitude()); + + decimalMinutes .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_DECIMALMINUTES); + minutesSeconds .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_MINUTESSECONDS); + decimal .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_DECIMAL); + + latStrip .showControls(LatLong.getFormatStyle() ); + longStrip .showControls(LatLong.getFormatStyle() ); + latStrip .setValue(latLong.getLatitude()); + longStrip .setValue(latLong.getLongitude()); } @@ -127,6 +174,21 @@ public class LatLongPane extends SettingsPane{ */ @Override public LatLong getParams(LatLong currentParams) { + + Toggle selectedButton = this.segmentedButton.getToggleGroup().getSelectedToggle(); + + if (selectedButton == decimalMinutes) { + LatLong.setFormatStyle(LatLong.FORMAT_DECIMALMINUTES); + + } + else if (selectedButton == minutesSeconds){ + LatLong.setFormatStyle(LatLong.FORMAT_MINUTESSECONDS); + + } + else if (selectedButton == decimal){ + LatLong.setFormatStyle(LatLong.FORMAT_DECIMAL); + } + latLong = new LatLong(latStrip.getValue(), longStrip.getValue()); if (Double.isNaN(latLong.getLatitude()) || Double.isNaN(latLong.getLongitude())) { return null; diff --git a/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java b/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java index 2073dace..2e80e8f0 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java +++ b/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java @@ -17,24 +17,30 @@ import pamViewFX.fxNodes.PamHBox; * */ public class LatLongStrip extends PamBorderPane { - + Label formattedText; - TextField degrees, minutes, seconds, decminutes; - Label dl, ml, sl, dml; + TextField degrees, minutes, seconds, decminutes, decimal; + Label dl, ml, sl, dml, dec; ComboBox nsew; boolean isLatitude; -// boolean decimalMinutes = true; - + // boolean decimalMinutes = true; + /** * HBox to hold decimal minutes, degrees, seconds controls. */ private PamHBox degHBox; - + /** * HBox to hold decimal controls */ private PamHBox decHBox; - + + /** + * The format type e.g. LatLong.FORMAT_DECIMALMINUTES. + */ + private int formatType = LatLong.FORMAT_DECIMALMINUTES; + private Label titleLabel; + /** * Construct a strip of controls to include in a larger dialog. @@ -48,7 +54,7 @@ public class LatLongStrip extends PamBorderPane { isLatitude = latitude; createDialogStrip(showBorder); } - + /** * Construct a strip of controls to include in a larger dialog. *

By default the strip will have a titled border with the @@ -59,27 +65,33 @@ public class LatLongStrip extends PamBorderPane { isLatitude = latitude; createDialogStrip(true); } - + private void createDialogStrip(boolean showBorder) { - - String borderTitle; - if (isLatitude) borderTitle = "Latitude"; - else borderTitle = "Longitude"; - - Label title= new Label(borderTitle); - PamGuiManagerFX.titleFont2style(title); -// title.setFont(PamGuiManagerFX.titleFontSize2); + + String title; + if (isLatitude) title = "Latitude"; + else title = "Longitude"; + + // title.setFont(PamGuiManagerFX.titleFontSize2); degrees = new TextField(); + degrees.setEditable(true); degrees.setPrefColumnCount(4); minutes = new TextField(); minutes.setPrefColumnCount(3); + minutes.setEditable(true); seconds = new TextField(); seconds.setPrefColumnCount(6); + seconds.setEditable(true); decminutes = new TextField(); decminutes.setPrefColumnCount(6); - + decminutes.setEditable(true); + + decimal=new TextField(); + decimal.setPrefColumnCount(9); + decimal.setEditable(true); + nsew = new ComboBox(); nsew.setOnAction((action)->{ double v = getValue(); @@ -91,6 +103,8 @@ public class LatLongStrip extends PamBorderPane { ml = new Label("min."); sl = new Label("sec."); dml = new Label("dec min."); + dec = new Label("decimal deg."); + formattedText = new Label("Position"); if (isLatitude) { nsew.getItems().add("N"); @@ -100,123 +114,192 @@ public class LatLongStrip extends PamBorderPane { nsew.getItems().add("E"); nsew.getItems().add("W"); } - + degrees.setOnKeyPressed((key)->{ newTypedValues(key); }); - + minutes.setOnKeyPressed((key)->{ newTypedValues(key); }); - + seconds.setOnKeyPressed((key)->{ newTypedValues(key); }); - + decminutes.setOnKeyPressed((key)->{ newTypedValues(key); }); - + + decimal.setOnKeyPressed((key)->{ + newTypedValues(key); + }); + nsew.setOnKeyPressed((key)->{ newTypedValues(key); }); - + degHBox = new PamHBox(); degHBox.setSpacing(5); degHBox.setAlignment(Pos.CENTER_LEFT); - - this.setRight(nsew); - this.setCenter(degHBox); + + PamHBox holder= new PamHBox(); + holder.setAlignment(Pos.CENTER_LEFT); + holder.setSpacing(5); + holder.getChildren().addAll(titleLabel = new Label(title), degHBox, nsew); + + this.setCenter(holder); this.setBottom(formattedText); - showControls( LatLong.FORMAT_DECIMALMINUTES); + showControls(formatType); } private void newTypedValues(KeyEvent e) { double v = getValue(); - // now need to put that into the fields that - // are not currently shown so that they are - // ready if needed. - if (e != null) { - sayValue(v, true); - } +// // now need to put that into the fields that +// // are not currently shown so that they are +// // ready if needed. +// +// if (e != null) { +// setValue(v, true); +// } + // and say the formated version sayFormattedValue(v); } - + public void showControls(int formatStyle) { - boolean decimal = (formatStyle == LatLong.FORMAT_DECIMALMINUTES); + + if (formatType==formatStyle) { + return; + } + + //important this comes before setting format style. + double currentValue = getValue(); + + this.formatType = formatStyle; + degHBox.getChildren().clear(); - if (decimal) { + + System.out.println("FORMATSTYLE: " + formatStyle + " val " + currentValue); + + switch (formatType) { + case LatLong.FORMAT_DECIMALMINUTES: degHBox.getChildren().add(dl); degHBox.getChildren().add(degrees); degHBox.getChildren().add(dml); degHBox.getChildren().add(decminutes); - } - else { + break; + case LatLong.FORMAT_MINUTESSECONDS: degHBox.getChildren().add(dl); degHBox.getChildren().add(degrees); degHBox.getChildren().add(ml); degHBox.getChildren().add(minutes); degHBox.getChildren().add(sl); degHBox.getChildren().add(seconds); + break; + case LatLong.FORMAT_DECIMAL: + degHBox.getChildren().add(dec); + degHBox.getChildren().add(decimal); + break; + } - minutes.setVisible(decimal == false); - ml.setVisible(decimal == false); - seconds.setVisible(decimal == false); - sl.setVisible(decimal == false); - decminutes.setVisible(decimal); - dml.setVisible(decimal); + + setValue(currentValue); + sayFormattedValue(getValue()); } - + /** * Set data in the lat long dialog strip * @param value Lat or Long in decimal degrees. */ - public void sayValue(double value) { - sayValue(value, false); + public void setValue(double value) { + setValue(value, false); } - - public void sayValue(double value, boolean hiddenOnly) { + + public void setValue(double value, boolean hiddenOnly) { + + System.out.println("Set value: " + value); if (value >= 0) { nsew.getSelectionModel().select(0); } else { nsew.getSelectionModel().select(1); } - + double deg = LatLong.getSignedDegrees(value); -// System.out.println("Deg: " + LatLong.getSignedDegrees(value) + " value: " +value); - - if (degrees.isVisible() == false || !hiddenOnly) degrees.setText(String.format("%d", (int)Math.abs(deg))); - if (minutes.isVisible() == false || !hiddenOnly) minutes.setText(String.format("%d", LatLong.getIntegerMinutes(value))); - if (decminutes.isVisible() == false || !hiddenOnly) decminutes.setText(String.format("%3.5f", LatLong.getDecimalMinutes(value))); - if (seconds.isVisible() == false || !hiddenOnly) seconds.setText(String.format("%3.5f", LatLong.getSeconds(value))); - if (nsew.isVisible() == false || !hiddenOnly) nsew.getSelectionModel().select(deg >= 0 ? 0 : 1); + // System.out.println("Deg: " + LatLong.getSignedDegrees(value) + " value: " +value); + + + // if (degrees.isVisible() == false || !hiddenOnly) degrees.setText(String.format("%d", (int)Math.abs(deg))); + // if (minutes.isVisible() == false || !hiddenOnly) minutes.setText(String.format("%d", LatLong.getIntegerMinutes(value))); + // if (decminutes.isVisible() == false || !hiddenOnly) decminutes.setText(String.format("%3.5f", LatLong.getDecimalMinutes(value))); + // if (seconds.isVisible() == false || !hiddenOnly) seconds.setText(String.format("%3.5f", LatLong.getSeconds(value))); + // if (nsew.isVisible() == false || !hiddenOnly) nsew.getSelectionModel().select(deg >= 0 ? 0 : 1); + // if (decimal.isVisible() == false || !hiddenOnly) decimal.setText(String.format("%.8f", value)); + + switch (formatType) { + case LatLong.FORMAT_DECIMALMINUTES: + + degrees.setText(String.format("%d", (int)Math.abs(deg))); + decminutes.setText(String.format("%3.5f", LatLong.getDecimalMinutes(value))); + + break; + case LatLong.FORMAT_MINUTESSECONDS: + + degrees.setText(String.format("%d", (int)Math.abs(deg))); + minutes.setText(String.format("%d", LatLong.getIntegerMinutes(value))); + seconds.setText(String.format("%3.5f", LatLong.getSeconds(value))); + + break; + case LatLong.FORMAT_DECIMAL: + + decimal.setText(String.format("%.8f", value)); + + break; + + } + sayFormattedValue(value); } - + /** - * Get the value for the latitude and longitude - * @return the value. + * Get the value for the latitude or longitude in decimal + * @return the value - the value in decimal */ public double getValue() { + double deg = 0; double min = 0; double sec = 0; double sin = 1.; - if (nsew.getSelectionModel().getSelectedIndex()== 1) sin = -1.; + + if (nsew.getSelectionModel().getSelectedIndex() == 1) sin = -1.; + + + if (formatType == LatLong.FORMAT_DECIMAL){ + try { + deg = Double.valueOf(decimal.getText()); + return deg; + } + catch (NumberFormatException ex) { + return Double.NaN; + } + } + try { - deg = Integer.valueOf(degrees.getText()); + deg = Integer.valueOf(degrees.getText()); } catch (NumberFormatException Ex) { return Double.NaN; } - if (LatLong.getFormatStyle() == LatLong.FORMAT_DECIMALMINUTES){ + + + if (formatType == LatLong.FORMAT_DECIMALMINUTES){ try { min = Double.valueOf(decminutes.getText()); } @@ -237,12 +320,13 @@ public class LatLongStrip extends PamBorderPane { catch (NumberFormatException ex) { return Double.NaN; } + } deg += min/60 + sec/3600; deg *= sin; return deg; } - + /** * Clear the latitude/longitude data from the pane. */ @@ -251,9 +335,10 @@ public class LatLongStrip extends PamBorderPane { minutes.setText(""); seconds.setText(""); decminutes.setText(""); + decimal.setText(""); } - - + + public void sayFormattedValue(double value) { if (isLatitude) { formattedText.setText(LatLong.formatLatitude(value)); @@ -262,13 +347,13 @@ public class LatLongStrip extends PamBorderPane { formattedText.setText(LatLong.formatLongitude(value)); } } -// public boolean isDecimalMinutes() { -// return decimalMinutes; -// } -// public void setDecimalMinutes(boolean decimalMinutes) { -// this.decimalMinutes = decimalMinutes; -// showControls(); -// } + // public boolean isDecimalMinutes() { + // return decimalMinutes; + // } + // public void setDecimalMinutes(boolean decimalMinutes) { + // this.decimalMinutes = decimalMinutes; + // showControls(); + // } /** * Set the pane to be enabled or disabled. @@ -281,5 +366,16 @@ public class LatLongStrip extends PamBorderPane { seconds.setDisable(!enabled); decminutes.setDisable(!enabled); nsew.setDisable(!enabled); + decimal.setDisable(!enabled); } + + /** + * Get the title label. + * @return the title label. + */ + public Label getTitleLabel() { + return titleLabel; + } + + } diff --git a/src/pamViewFX/fxNodes/utilsFX/TextUtilsFX.java b/src/pamViewFX/fxNodes/utilsFX/TextUtilsFX.java new file mode 100644 index 00000000..d47098d7 --- /dev/null +++ b/src/pamViewFX/fxNodes/utilsFX/TextUtilsFX.java @@ -0,0 +1,40 @@ +package pamViewFX.fxNodes.utilsFX; + +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextBoundsType; + +public class TextUtilsFX { + + static final Text helper; + static final double DEFAULT_WRAPPING_WIDTH; + static final double DEFAULT_LINE_SPACING; + static final String DEFAULT_TEXT; + static final TextBoundsType DEFAULT_BOUNDS_TYPE; + static { + helper = new Text(); + DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth(); + DEFAULT_LINE_SPACING = helper.getLineSpacing(); + DEFAULT_TEXT = helper.getText(); + DEFAULT_BOUNDS_TYPE = helper.getBoundsType(); + } + + public static double computeTextWidth(Font font, String text, double help0) { + // Toolkit.getToolkit().getFontLoader().computeStringWidth(field.getText(), + // field.getFont()); + + helper.setText(text); + helper.setFont(font); + + helper.setWrappingWidth(0.0D); + helper.setLineSpacing(0.0D); + double d = Math.min(helper.prefWidth(-1.0D), help0); + helper.setWrappingWidth((int) Math.ceil(d)); + d = Math.ceil(helper.getLayoutBounds().getWidth()); + + helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH); + helper.setLineSpacing(DEFAULT_LINE_SPACING); + helper.setText(DEFAULT_TEXT); + return d; + } +} \ No newline at end of file From d1b40c1d86651d5f10e1638a866977351736eb1a Mon Sep 17 00:00:00 2001 From: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:48:36 +0000 Subject: [PATCH 2/2] Release V2.02.09e fixes to WMD stub removal to make it a lot faster. --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- src/PamController/PamguardVersionInfo.java | 6 +++--- src/PamView/PamGui.java | 20 +++++++++----------- src/whistlesAndMoans/StubRemover.java | 8 +++++++- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 96e1ff68..fa93561f 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.pamguard Pamguard Pamguard Java12+ - 2.02.09c + 2.02.09e Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/pom.xml b/pom.xml index 18f1ee7d..8c39e533 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.pamguard Pamguard - 2.02.09d + 2.02.09e Pamguard Java12+ Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index 7172293c..263632b0 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -24,19 +24,19 @@ public class PamguardVersionInfo { * PAMGuard can work with. */ static public final String minJavaVersion = "11.0.0"; - static public final String maxJavaVersion = "19.99.99"; + static public final String maxJavaVersion = "21.99.99"; /** * Version number, major version.minorversion.sub-release. * Note: can't go higher than sub-release 'f' */ - static public final String version = "2.02.09c"; + static public final String version = "2.02.09e"; /** * Release date */ - static public final String date = "10 November 2023"; + static public final String date = "18 December 2023"; // /** // * Release type - Beta or Core diff --git a/src/PamView/PamGui.java b/src/PamView/PamGui.java index 0ebec8a3..bbb4fc73 100644 --- a/src/PamView/PamGui.java +++ b/src/PamView/PamGui.java @@ -601,17 +601,15 @@ public class PamGui extends PamView implements WindowListener, PamSettings { fileMenu.add(menuItem); } - if (SMRUEnable.isEnable()) { - menuItem = new JMenuItem("Import PAMGuard Modules"); - menuItem.setToolTipText("Import module settings from a different PAMGuard configuration (psfx files only"); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - importSettings(); - } - }); - fileMenu.add(menuItem); - } + menuItem = new JMenuItem("Import PAMGuard Modules"); + menuItem.setToolTipText("Import module settings from a different PAMGuard configuration (psfx files only"); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + importSettings(); + } + }); + fileMenu.add(menuItem); fileMenu.addSeparator(); diff --git a/src/whistlesAndMoans/StubRemover.java b/src/whistlesAndMoans/StubRemover.java index e475a36e..3667b179 100644 --- a/src/whistlesAndMoans/StubRemover.java +++ b/src/whistlesAndMoans/StubRemover.java @@ -82,7 +82,13 @@ public class StubRemover { private int searchStubSize(List sliceData, int currentSlice, int peakInd, int searchDir, int diagGap, int currentSize) { int nSlice = sliceData.size(); int nextSliceInd = currentSlice + searchDir; - if (nextSliceInd < 0 || nextSliceInd >= nSlice-1) { + /** + * This function is only every used to throw away very small stubs, so there is no need to get the full size + * of every one. It's OK to return as soon as the size is bigger than the minimum required to make + * something worth keeping. This reduces the time spent tracing down every little alley which was + * severely impacting performance for larger whistles. + */ + if (nextSliceInd < 0 || nextSliceInd >= nSlice-1 || currentSize > whistleControl.getWhistleToneParameters().minPixels) { return currentSize; } SliceData nextSlice = sliceData.get(nextSliceInd);