From 9ba3e7e3f7a4485b4053d83aac9bfb6bc56e40ec Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Mon, 29 Apr 2024 16:58:47 +0100 Subject: [PATCH 01/17] Updates to get DelphinID working including building a testing framework. --- src/dataPlotsFX/TDControlFX.java | 4 +- src/pamViewFX/PamGuiTabFX.java | 2 +- .../dlClassification/DLDataUnit.java | 4 +- .../dlClassification/DLTaskThread.java | 6 +- .../StandardClassifierModel.java | 56 +++- .../animalSpot/SoundSpotClassifier.java | 6 +- .../animalSpot/SoundSpotResult.java | 4 +- .../animalSpot/SoundSpotWorker.java | 4 +- .../archiveModel/ArchiveModelClassifier.java | 4 +- .../archiveModel/ArchiveModelWorker.java | 10 +- .../delphinID/DelphinIDClassifier.java | 13 +- .../delphinID/DelphinIDPrediction.java | 4 +- .../delphinID/DelphinIDTest.java | 243 ++++++++++++++++++ .../delphinID/DelphinIDWorker.java | 152 +++++++---- .../dlClassification/delphinID/DelphinUI.java | 40 ++- .../delphinID/Whistles2Image.java | 132 +++++++--- .../genericModel/GenericDLClassifier.java | 35 +-- .../genericModel/GenericModelWorker.java | 6 +- ...rediction.java => StandardPrediction.java} | 8 +- .../ketos/KetosClassifier.java | 4 +- .../dlClassification/ketos/KetosResult.java | 4 +- .../dlClassification/ketos/KetosWorker.java | 4 +- .../koogu/KooguClassifier.java | 2 +- .../logging/ModelResultBinaryFactory.java | 6 +- .../segmenter/SegmenterDetectionGroup.java | 13 +- .../segmenter/SegmenterGroupDataBlock.java | 2 +- .../GenericDLClassifierTest.java | 6 +- .../KetosDLClassifierTest.java | 4 +- .../KooguDLClassifierTest.java | 4 +- .../localization/LocalizationHandler.java | 20 +- 30 files changed, 598 insertions(+), 204 deletions(-) create mode 100644 src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java rename src/rawDeepLearningClassifier/dlClassification/genericModel/{GenericPrediction.java => StandardPrediction.java} (90%) diff --git a/src/dataPlotsFX/TDControlFX.java b/src/dataPlotsFX/TDControlFX.java index 3f162249..1c46fc4b 100644 --- a/src/dataPlotsFX/TDControlFX.java +++ b/src/dataPlotsFX/TDControlFX.java @@ -114,7 +114,9 @@ public class TDControlFX extends TDControl implements UserDisplayNodeFX { ArrayList dataBlocks=new ArrayList(); PamDataBlock dataBlock=this.tdDisplayController.getUserDisplayProcess().getParentDataBlock(); if (TDDataProviderRegisterFX.getInstance().findDataProvider(dataBlock)!=null) dataBlocks.add(dataBlock); - if (dataBlock!=null) System.out.println("TDControldFX: parent datablock "+dataBlock.getDataName()); + if (dataBlock!=null) { + System.out.println("TDControldFX: parent datablock "+dataBlock.getDataName()); + } else{ System.out.println("TDControldFX: parent datablock null"); return dataBlocks; diff --git a/src/pamViewFX/PamGuiTabFX.java b/src/pamViewFX/PamGuiTabFX.java index c458f21e..9e1f3fb2 100644 --- a/src/pamViewFX/PamGuiTabFX.java +++ b/src/pamViewFX/PamGuiTabFX.java @@ -201,7 +201,7 @@ public class PamGuiTabFX extends PamTabFX { * @return the internal pane which has been added */ public PamGuiInternalPane addInternalPane(UserDisplayNodeFX userDisplayNodeFX){ - System.out.println("UserDisplayNodeFX: " + userDisplayNodeFX); +// System.out.println("UserDisplayNodeFX: " + userDisplayNodeFX); if (userDisplayNodeFX==null || userDisplayNodeFX.getNode()==null) return null; for (PamGuiInternalPane internalPane: this.internalPanes) { diff --git a/src/rawDeepLearningClassifier/dlClassification/DLDataUnit.java b/src/rawDeepLearningClassifier/dlClassification/DLDataUnit.java index 869f9894..4fa03e38 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLDataUnit.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLDataUnit.java @@ -2,7 +2,7 @@ package rawDeepLearningClassifier.dlClassification; import PamguardMVC.DataUnitBaseData; import PamguardMVC.PamDataUnit; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * A data unit created from classification results of DL model. this data unit holds one model results, i.e. @@ -37,7 +37,7 @@ public class DLDataUnit extends PamDataUnit { public DLDataUnit(DataUnitBaseData baseData, float[] data) { super(baseData); //System.out.println("DLDataUnit: " + this.getChannelBitmap()); - this.modelResult = new GenericPrediction(data); + this.modelResult = new StandardPrediction(data); } public DLDataUnit(DataUnitBaseData baseData, PredictionResult modelResult) { diff --git a/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java b/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java index 51d6c6e7..c1d77c41 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java @@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import PamguardMVC.PamDataUnit; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.segmenter.GroupedRawData; /** @@ -52,7 +52,7 @@ public abstract class DLTaskThread extends Thread { System.out.println("DL TASK THREAD: " + "The queue size is " + queue.size()); ArrayList groupedRawData = queue.remove(0); - ArrayList modelResult = dlModelWorker.runModel(groupedRawData, + ArrayList modelResult = dlModelWorker.runModel(groupedRawData, groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); //TODO channel? for (int i =0; i modelResult = (ArrayList) getDLWorker().runModel(groupedRawData, + ArrayList modelResult = (ArrayList) getDLWorker().runModel(groupedRawData, groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); if (modelResult==null) { @@ -205,14 +205,58 @@ public abstract class StandardClassifierModel implements DLClassiferModel, PamSe } @Override - public void newDLResult(GenericPrediction soundSpotResult, PamDataUnit groupedRawData) { - soundSpotResult.setClassNameID(GenericDLClassifier.getClassNameIDs(getDLParams())); - soundSpotResult.setBinaryClassification(GenericDLClassifier.isBinaryResult(soundSpotResult, getDLParams())); + public void newDLResult(StandardPrediction soundSpotResult, PamDataUnit groupedRawData) { + soundSpotResult.setClassNameID(getClassNameIDs(getDLParams())); + soundSpotResult.setBinaryClassification(isDecision(soundSpotResult, getDLParams())); newResult(soundSpotResult, groupedRawData); } } + /** + * Make a decision on whether a result passed a decision + * @param modelResult - the model result. + * @param modelParmas - the model parameters. + * @return true if a threshold has been met. + */ + public boolean isDecision(StandardPrediction modelResult, StandardModelParams modelParmas) { + return isBinaryResult(modelResult, modelParmas); + } + + + + /** + * Get the class name IDs + * @return an array of class name IDs + */ + public static short[] getClassNameIDs(StandardModelParams standardModelParams) { + if (standardModelParams.classNames==null || standardModelParams.classNames.length<=0) return null; + short[] nameIDs = new short[standardModelParams.classNames.length]; + for (int i = 0 ; igenericModelParams.threshold && genericModelParams.binaryClassification[i]) { + // System.out.println("SoundSpotClassifier: prediciton: " + i + " passed threshold with val: " + modelResult.getPrediction()[i]); + return true; + } + } + return false; + } + @Override public void closeModel() { @@ -225,7 +269,7 @@ public abstract class StandardClassifierModel implements DLClassiferModel, PamSe * @param modelResult - the model result; * @param groupedRawData - the grouped raw data. */ - protected void newResult(GenericPrediction modelResult, PamDataUnit groupedRawData) { + protected void newResult(StandardPrediction modelResult, PamDataUnit groupedRawData) { if (groupedRawData instanceof GroupedRawData) { this.dlControl.getDLClassifyProcess().newModelResult(modelResult, (GroupedRawData) groupedRawData); } diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java index 214149a3..d9374432 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java @@ -15,7 +15,7 @@ import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; /** @@ -53,7 +53,7 @@ public class SoundSpotClassifier extends StandardClassifierModel { /** * The deep learning model worker. */ - private DLModelWorker soundSpotWorker; + private DLModelWorker soundSpotWorker; public SoundSpotClassifier(DLControl dlControl) { @@ -171,7 +171,7 @@ public class SoundSpotClassifier extends StandardClassifierModel { @Override - public DLModelWorker getDLWorker() { + public DLModelWorker getDLWorker() { if (soundSpotWorker==null) { soundSpotWorker = new SoundSpotWorker(); } diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotResult.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotResult.java index 88178ab1..e94a9c56 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotResult.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotResult.java @@ -1,13 +1,13 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * Result from the SoundSpotClassifier. * @author Jamie Macaulay * */ -public class SoundSpotResult extends GenericPrediction { +public class SoundSpotResult extends StandardPrediction { public SoundSpotResult(float[] prob, boolean isBinary) { super(prob, isBinary); diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java index b7115630..735db07c 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java @@ -7,7 +7,7 @@ import org.jamdev.jdl4pam.animalSpot.AnimalSpotParams; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** @@ -19,7 +19,7 @@ import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction * @author Jamie Macaulay * */ -public class SoundSpotWorker extends DLModelWorker { +public class SoundSpotWorker extends DLModelWorker { /** diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelClassifier.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelClassifier.java index cd58771f..5a7ff152 100644 --- a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelClassifier.java @@ -14,7 +14,7 @@ import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.dlClassification.ketos.KetosDLParams; import rawDeepLearningClassifier.dlClassification.ketos.KetosUI; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; @@ -81,7 +81,7 @@ public abstract class ArchiveModelClassifier extends StandardClassifierModel { @Override - public DLModelWorker getDLWorker() { + public DLModelWorker getDLWorker() { return getModelWorker(); } diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java index ab78a47b..8cc339f5 100644 --- a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java @@ -26,7 +26,7 @@ import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; import rawDeepLearningClassifier.dlClassification.genericModel.GenericModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * @@ -228,8 +228,8 @@ public class ArchiveModelWorker extends GenericModelWorker { @Override - public GenericPrediction makeModelResult(float[] prob, double time) { - GenericPrediction prediction = new GenericPrediction(prob); + public StandardPrediction makeModelResult(float[] prob, double time) { + StandardPrediction prediction = new StandardPrediction(prob); prediction.setAnalysisTime(time); return prediction; } @@ -251,6 +251,10 @@ public class ArchiveModelWorker extends GenericModelWorker { public ArchiveModel getModel() { return dlModel; } + + protected void setModel(ArchiveModel dlModel) { + this.dlModel = dlModel; + } @Override public boolean isModelNull() { diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java index 375efb79..b55c8655 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java @@ -13,7 +13,7 @@ import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; /** @@ -86,6 +86,15 @@ public class DelphinIDClassifier extends StandardClassifierModel { return delphinIDParams; } + + + @Override + public boolean isDecision(StandardPrediction modelResult, StandardModelParams modelParmas) { + //TODO + //DelphinID uses a different decision making process to most of the standard classifiers which just pass a binary threhsoild. + return false; + } + @Override @@ -111,7 +120,7 @@ public class DelphinIDClassifier extends StandardClassifierModel { @Override - public DLModelWorker getDLWorker() { + public DLModelWorker getDLWorker() { if (delphinIDWorker==null) { delphinIDWorker = new DelphinIDWorker(); } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java index b5140ee4..158f013a 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java @@ -1,8 +1,8 @@ package rawDeepLearningClassifier.dlClassification.delphinID; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; -public class DelphinIDPrediction extends GenericPrediction{ +public class DelphinIDPrediction extends StandardPrediction{ public DelphinIDPrediction(float[] prob) { super(prob); diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java new file mode 100644 index 00000000..ad358d4a --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -0,0 +1,243 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + +import java.io.IOException; +import java.util.ArrayList; + +import PamUtils.PamArrayUtils; +import PamguardMVC.DataUnitBaseData; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; +import rawDeepLearningClassifier.segmenter.SegmenterDetectionGroup; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; +import whistleClassifier.WhistleContour; +import whistlesAndMoans.AbstractWhistleDataUnit; + + +/** + * A delphinID test suite. + * + * @author Jamie Macaulay + * + */ +public class DelphinIDTest { + + public static DelphinIDWorker prepDelphinIDModel(String modelPath) { + + //create the delphinID worker. + DelphinIDWorker delphinIDWorker = new DelphinIDWorker(); + + StandardModelParams params = new StandardModelParams(); + params.modelPath = modelPath; + + //prepare the model + delphinIDWorker.prepModel(params, null); + + return delphinIDWorker; + } + + + /** + * Load whistle contours from a MAT file. () + * + * @param filePath - the file path. + * + * @return a list of whistle contour objects from the mat file. + */ + public static ArrayList getWhistleContoursMAT(String filePath){ + + ArrayList contours = new ArrayList(); + + // SegmenterDetectionGroup segmenterDetectionGroup = new SegmenterDetectionGroup(0, 0, 0, 0); + + // Read scalar from nested struct + try { + Mat5File matFile = Mat5.readFromFile(filePath); + Struct whistlesStruct = matFile.getStruct("whistles"); + + double fftLen = matFile.getMatrix("fftlen").getDouble(0); + double fftHop = matFile.getMatrix("ffthop").getDouble(0); + double sampleRate = matFile.getMatrix("samplerate").getDouble(0); + + for (int i=0; i< whistlesStruct.getNumElements(); i++) { + DataUnitBaseData basicData = new DataUnitBaseData(); + + long timeMillis = ((Matrix)whistlesStruct.get("millis", i)).getLong(0); + basicData.setTimeMilliseconds(timeMillis); + + long sampleDuration = ((Matrix)whistlesStruct.get("sampleDuration", i)).getLong(0); + basicData.setSampleDuration(sampleDuration); + + basicData.setMillisecondDuration(1000.*(sampleDuration/sampleRate)); + + int channelMap = ((Matrix)whistlesStruct.get("channelMap", i)).getInt(0); + basicData.setChannelBitmap(channelMap); + + long uid = ((Matrix)whistlesStruct.get("UID", i)).getLong(0); + basicData.setUID(uid); + + long startSample = ((Matrix)whistlesStruct.get("startSample", i)).getLong(0); + basicData.setStartSample(startSample); + + int nSlices = ((Matrix)whistlesStruct.get("nSlices", i)).getInt(0); + + double[] freq = new double[nSlices]; + double[] times = new double[nSlices]; + + Matrix contourStruct = whistlesStruct.getMatrix("contour", i); + for (int j=0; j segmentWhsitleData(ArrayList whistles, long dataStartMillis, + double segLen, double segHop){ + + ArrayList group = new ArrayList(); + + //find the maximum whistle time + long maxTime = Long.MIN_VALUE; + long endTime = 0; + for (AbstractWhistleDataUnit whislte: whistles) { + endTime = (long) (whislte.getTimeMilliseconds()+whislte.getDurationInMilliseconds()); + if (endTime>maxTime) maxTime=endTime; + } + + long segStart = dataStartMillis; + long segEnd = (long) (segStart+segLen); + + long whistleStart; + long whistleEnd; + SegmenterDetectionGroup whistleGroup; + while (segStart=segStart && whistleStart=segStart && whistleEnd whistleContours = getWhistleContoursMAT(whistleContourPath); + + + //segment the whistle detections + ArrayList segments = segmentWhsitleData(whistleContours, dataStartMillis, + segLen, segHop); + + for (int i=0; i aSegment = new ArrayList(); + aSegment.add(segments.get(i)); + + //the prediciton. + ArrayList predicition = model.runModel(aSegment, sampleRate, 1); + + float[] output = predicition.get(0).getPrediction(); + + } + + // for (int i=0; i dataUnits, float sampleRate, int iChan){ - + //Get a list of of the model transforms. ArrayList modelTransforms = getModelTransforms(); @SuppressWarnings("unchecked") - ArrayList whistleGroups = (ArrayList) dataUnits; - + ArrayList whistleGroups = (ArrayList) dataUnits; + //the number of chunks. int numChunks = whistleGroups.size(); //data input into the model - a stack of spectrogram images. float[][][] transformedDataStack = new float[numChunks][][]; -// -// //generate the spectrogram stack. -// AudioData soundData; -// double[][] transformedData2; //spec data -// double[] transformedData1; //waveform data -// for (int j=0; j points = whistContours2Points(whistleGroup); - Canvas canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getDurationInMilliseconds()}, freqLimits, 5.); - - WritableImage image = canvas.getGraphicsContext2D().getCanvas().snapshot(null, null); + //does not work becaue it has to be on the AWT thread. + BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getDurationInMilliseconds()}, freqLimits, 5.); double[][] imaged = new double[(int) size[0]][(int) size[1]]; - Color color; + int color; for (int i=0; i whistContours2Points(SegmenterDetectionGroup whistleGroup) { + + ArrayList contours = new ArrayList(); + + AbstractWhistleDataUnit whistleContour; + + long segStart = whistleGroup.getTimeMilliseconds(); + + for (int i=0; i points, double[] size, double[] xlims, double[] ylims, double markerSize) { +// +// Canvas canvas = new Canvas(size[0], size[1]); +// +// double x, y; +// for (int j=0; j points, double[] size, double[] xlims, double[] ylims, double markerSize) { - Canvas canvas = new Canvas(size[0], size[1]); + BufferedImage canvas = new BufferedImage((int) size[0], (int) size[1], BufferedImage.TYPE_INT_RGB); double x, y; - for (int i=0; igenericModelParams.threshold && genericModelParams.binaryClassification[i]) { - // System.out.println("SoundSpotClassifier: prediciton: " + i + " passed threshold with val: " + modelResult.getPrediction()[i]); - return true; - } - } - return false; - } - - // @Override // public ArrayList checkSettingsOK() { // return checkSettingsOK(genericModelParams, dlControl); @@ -234,7 +203,7 @@ public class GenericDLClassifier extends StandardClassifierModel { } @Override - public DLModelWorker getDLWorker() { + public DLModelWorker getDLWorker() { return this.genericModelWorker; } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java index fa7ecdd7..cb182031 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java @@ -18,7 +18,7 @@ import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams * @author Jamie Macaulay * */ -public class GenericModelWorker extends DLModelWorker { +public class GenericModelWorker extends DLModelWorker { /** * The generic model @@ -52,8 +52,8 @@ public class GenericModelWorker extends DLModelWorker { } @Override - public GenericPrediction makeModelResult(float[] prob, double time) { - GenericPrediction model = new GenericPrediction(prob); + public StandardPrediction makeModelResult(float[] prob, double time) { + StandardPrediction model = new StandardPrediction(prob); model.setAnalysisTime(time); return model; } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericPrediction.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/StandardPrediction.java similarity index 90% rename from src/rawDeepLearningClassifier/dlClassification/genericModel/GenericPrediction.java rename to src/rawDeepLearningClassifier/dlClassification/genericModel/StandardPrediction.java index 57e83218..b2f93dd7 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericPrediction.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/StandardPrediction.java @@ -9,7 +9,7 @@ import rawDeepLearningClassifier.dlClassification.PredictionResult; * @author Jamie Macaulay * */ -public class GenericPrediction implements PredictionResult { +public class StandardPrediction implements PredictionResult { /** @@ -45,14 +45,14 @@ public class GenericPrediction implements PredictionResult { * @param classNameID - the ID's of the class names. * @param isBinary - true if the model result passed a binary test (usually one species above a threshold) */ - public GenericPrediction(float[] prob, short[] classNameID, boolean isBinary) { + public StandardPrediction(float[] prob, short[] classNameID, boolean isBinary) { this.prob=prob; this.classNameID = classNameID; this.binaryPass= isBinary; } - public GenericPrediction(float[] prob, boolean isBinary) { + public StandardPrediction(float[] prob, boolean isBinary) { this(prob, null, isBinary); } @@ -60,7 +60,7 @@ public class GenericPrediction implements PredictionResult { * Create a result for the Sound Spot classifier. * @param prob - the probability of each class. */ - public GenericPrediction(float[] prob) { + public StandardPrediction(float[] prob) { this(prob, null, false); } diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java index 437f8250..d224a013 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java @@ -14,7 +14,7 @@ import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; /** @@ -77,7 +77,7 @@ public class KetosClassifier extends StandardClassifierModel { @Override - public DLModelWorker getDLWorker() { + public DLModelWorker getDLWorker() { return getKetosWorker(); } diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosResult.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosResult.java index 510c311d..ca4777ff 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosResult.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosResult.java @@ -1,6 +1,6 @@ package rawDeepLearningClassifier.dlClassification.ketos; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** @@ -8,7 +8,7 @@ import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction * @author Jamie Macaulay * */ -public class KetosResult extends GenericPrediction { +public class KetosResult extends StandardPrediction { public KetosResult(float[] prob) { diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java index 3a937c1b..a42422db 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java @@ -15,7 +15,7 @@ import PamView.dialog.warn.WarnOnce; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * @@ -25,7 +25,7 @@ import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction * @author Jamie Macaulay * */ -public class KetosWorker extends DLModelWorker { +public class KetosWorker extends DLModelWorker { /** diff --git a/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java index 411d6551..df7e049a 100644 --- a/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java @@ -4,7 +4,7 @@ import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelClassifier; import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * Classifier which uses deep learning models from Koogus' framework. diff --git a/src/rawDeepLearningClassifier/logging/ModelResultBinaryFactory.java b/src/rawDeepLearningClassifier/logging/ModelResultBinaryFactory.java index 91b10536..84a56dee 100644 --- a/src/rawDeepLearningClassifier/logging/ModelResultBinaryFactory.java +++ b/src/rawDeepLearningClassifier/logging/ModelResultBinaryFactory.java @@ -8,7 +8,7 @@ import PamUtils.PamArrayUtils; import rawDeepLearningClassifier.dlClassification.PredictionResult; import rawDeepLearningClassifier.dlClassification.animalSpot.SoundSpotResult; import rawDeepLearningClassifier.dlClassification.dummyClassifier.DummyModelResult; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.dlClassification.ketos.KetosResult; /** @@ -146,7 +146,7 @@ public class ModelResultBinaryFactory { break; default: //ideally should never be used. - result = new GenericPrediction(data, isBinary); + result = new StandardPrediction(data, isBinary); break; } //System.out.println("New model result: "+ type); @@ -173,7 +173,7 @@ public class ModelResultBinaryFactory { return KETOS; } //must be last because this is often sub classed - if (modelResult instanceof GenericPrediction) { + if (modelResult instanceof StandardPrediction) { return GENERIC; } if (modelResult instanceof DummyModelResult) { diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java index 1a00c4a3..b41b3083 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -19,9 +19,16 @@ public class SegmenterDetectionGroup extends GroupDetection { * @param startSample - the stratSample of the SEGMENT. * @param duration - the duration of the SEGMENT. */ - public SegmenterDetectionGroup(long timeMilliseconds, int channelBitmap, long startSample, long duration) { - super(timeMilliseconds, channelBitmap, startSample, duration); - // TODO Auto-generated constructor stub + public SegmenterDetectionGroup(long timeMilliseconds, int channelBitmap, long startSample, double duration) { + super(timeMilliseconds, channelBitmap, startSample, (long) duration); + this.setDurationInMilliseconds(duration); + } + + @Override + public boolean isAllowSubdetectionSharing() { + //segmetns share sub detections + return true; } + } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java index cbd18e3a..3bda33fd 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java @@ -12,7 +12,7 @@ public class SegmenterGroupDataBlock extends PamDataBlock gwenericPrediciton = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); + ArrayList gwenericPrediciton = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); float[] output = gwenericPrediciton.get(0).getPrediction(); @@ -165,7 +165,7 @@ public class GenericDLClassifierTest { groupedData.add(groupedRawData); - ArrayList genericPrediction = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); + ArrayList genericPrediction = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); float[] output = genericPrediction.get(0).getPrediction(); diff --git a/src/test/rawDeepLearningClassifier/KetosDLClassifierTest.java b/src/test/rawDeepLearningClassifier/KetosDLClassifierTest.java index b898810b..fc590764 100644 --- a/src/test/rawDeepLearningClassifier/KetosDLClassifierTest.java +++ b/src/test/rawDeepLearningClassifier/KetosDLClassifierTest.java @@ -13,7 +13,7 @@ import javax.sound.sampled.UnsupportedAudioFileException; import org.jamdev.jdl4pam.utils.DLUtils; import org.jamdev.jpamutils.wavFiles.AudioData; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.dlClassification.ketos.KetosDLParams; import rawDeepLearningClassifier.dlClassification.ketos.KetosWorker2; import rawDeepLearningClassifier.segmenter.GroupedRawData; @@ -140,7 +140,7 @@ public class KetosDLClassifierTest { ArrayList groupedData = new ArrayList(); groupedData.add(groupedRawData); - ArrayList genericPrediciton = ketosWorker2.runModel(groupedData, soundData.sampleRate, 0); + ArrayList 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; diff --git a/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java b/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java index 6a13ec46..e3ea9c5b 100644 --- a/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java +++ b/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java @@ -16,7 +16,7 @@ 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.genericModel.StandardPrediction; import rawDeepLearningClassifier.dlClassification.koogu.KooguModelWorker; import rawDeepLearningClassifier.segmenter.GroupedRawData; import us.hebi.matlab.mat.format.Mat5; @@ -102,7 +102,7 @@ public class KooguDLClassifierTest { groupedData.add(groupedRawData); - ArrayList genericPrediciton = kooguWorker.runModel(groupedData, soundData.sampleRate, 0); + ArrayList genericPrediciton = kooguWorker.runModel(groupedData, soundData.sampleRate, 0); float[] output = genericPrediciton.get(0).getPrediction(); boolean testPassed= output[1]> kooguPredicitions[i][2]-0.1 && output[1]< kooguPredicitions[i][2]+0.1; diff --git a/src/tethys/localization/LocalizationHandler.java b/src/tethys/localization/LocalizationHandler.java index 69105b5e..3751df93 100644 --- a/src/tethys/localization/LocalizationHandler.java +++ b/src/tethys/localization/LocalizationHandler.java @@ -1,17 +1,17 @@ package tethys.localization; -import nilus.CylindricalCoordinateType; -import nilus.LocalizationType; -import nilus.Localize.Effort.CoordinateReferenceSystem; +//import nilus.CylindricalCoordinateType; +//import nilus.LocalizationType; +//import nilus.Localize.Effort.CoordinateReferenceSystem; public class LocalizationHandler { - public LocalizationType getLoc() { - LocalizationType lt = new LocalizationType(); - CylindricalCoordinateType cct = new CylindricalCoordinateType(); -// cct.set - CoordinateReferenceSystem cr; - return null; - } +// public LocalizationType getLoc() { +// LocalizationType lt = new LocalizationType(); +// CylindricalCoordinateType cct = new CylindricalCoordinateType(); +//// cct.set +// CoordinateReferenceSystem cr; +// return null; +// } } From fa6991eb8029ae5cf44837942a989d517feb814a Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Tue, 30 Apr 2024 17:06:11 +0100 Subject: [PATCH 02/17] Finished testing suite for DelphinID --- src/PamUtils/PamArrayUtils.java | 22 +++++- src/rawDeepLearningClassifier/DLControl.java | 3 +- .../archiveModel/ArchiveModelWorker.java | 1 + .../delphinID/DelphinIDTest.java | 74 ++++++++++++++++--- .../delphinID/DelphinIDWorker.java | 4 +- .../delphinID/Whistles2Image.java | 43 +++++++++-- .../segmenter/SegmenterDataBlock.java | 2 +- .../segmenter/SegmenterDetectionGroup.java | 22 ++++++ 8 files changed, 147 insertions(+), 24 deletions(-) diff --git a/src/PamUtils/PamArrayUtils.java b/src/PamUtils/PamArrayUtils.java index 997a7138..672529c3 100644 --- a/src/PamUtils/PamArrayUtils.java +++ b/src/PamUtils/PamArrayUtils.java @@ -719,6 +719,17 @@ public class PamArrayUtils { } } + /** + * Print an array to the console. + * @param array to print + */ + public static void printArray(float[] array) { + if (array==null) System.out.println("null"); + for (int i=0; i transforms = DLTransformsFactory.makeDLTransforms(modelParams.dlTransforms); // ///HACK here for now to fix an issue with dB and Ketos transforms having zero length somehow... diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java index ad358d4a..c2450f91 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -3,16 +3,19 @@ package rawDeepLearningClassifier.dlClassification.delphinID; import java.io.IOException; import java.util.ArrayList; +import org.jamdev.jdl4pam.utils.DLMatFile; + import PamUtils.PamArrayUtils; import PamguardMVC.DataUnitBaseData; +import PamguardMVC.PamDataUnit; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.segmenter.SegmenterDetectionGroup; import us.hebi.matlab.mat.format.Mat5; import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.MatFile; import us.hebi.matlab.mat.types.Matrix; import us.hebi.matlab.mat.types.Struct; -import whistleClassifier.WhistleContour; import whistlesAndMoans.AbstractWhistleDataUnit; @@ -24,10 +27,10 @@ import whistlesAndMoans.AbstractWhistleDataUnit; */ public class DelphinIDTest { - public static DelphinIDWorker prepDelphinIDModel(String modelPath) { + public static DelphinIDWorkerTest prepDelphinIDModel(String modelPath) { //create the delphinID worker. - DelphinIDWorker delphinIDWorker = new DelphinIDWorker(); + DelphinIDWorkerTest delphinIDWorker = new DelphinIDWorkerTest(); StandardModelParams params = new StandardModelParams(); params.modelPath = modelPath; @@ -134,7 +137,7 @@ public class DelphinIDTest { SegmenterDetectionGroup whistleGroup; while (segStart dataUnits, float sampleRate, int iChan){ + float[][][] data = super.dataUnits2ModelInput(dataUnits, sampleRate, iChan); + + this.lastModelInput = data; + + + return data; + } + + public float[][][] getLastModelInput() { + return lastModelInput; + } + + } /** * Main class for running the test. @@ -199,15 +225,20 @@ public class DelphinIDTest { long dataStartMillis = 1340212413000L; //path to the .mat containing whistle contours. - String whistleContourPath = "D:\\Dropbox\\PAMGuard_dev\\Deep_Learning\\delphinID\\testencounter415\\whistle_contours.mat"; + String whistleContourPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_contours.mat"; //the path to the model String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_4s_encounter415.zip"; + + //the path to the model + String matImageSave = "C:/Users/Jamie Macaulay/MATLAB Drive/MATLAB/PAMGUARD/deep_learning/delphinID/whistleimages.mat"; + + //create MatFile for saving the image data to. + MatFile matFile = Mat5.newMatFile(); //get the whislte contours form a .mat file. ArrayList whistleContours = getWhistleContoursMAT(whistleContourPath); - //segment the whistle detections ArrayList segments = segmentWhsitleData(whistleContours, dataStartMillis, segLen, segHop); @@ -216,7 +247,13 @@ public class DelphinIDTest { System.out.println("Segment " + i + " contains " + segments.get(i).getSubDetectionsCount() + " whistles"); } - DelphinIDWorker model = prepDelphinIDModel(modelPath); + //prepare the model - this loads the zip file and loads the correct transforms. + DelphinIDWorkerTest model = prepDelphinIDModel(modelPath); + model.setEnableSoftMax(false); + + + //initialise strcuture for image data + Struct imageStruct = Mat5.newStruct(segments.size(), 1); for (int i=0; i predicition = model.runModel(aSegment, sampleRate, 1); float[] output = predicition.get(0).getPrediction(); + + System.out.println(); + System.out.print("Segment: " +(aSegment.get(0).getSegmentStartMillis()-dataStartMillis)/1000.); + for (int j=0; j points = whistContours2Points(whistleGroup); //does not work becaue it has to be on the AWT thread. - BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getDurationInMilliseconds()}, freqLimits, 5.); + BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getSegmentDuration()/1000.}, freqLimits, 5.); double[][] imaged = new double[(int) size[0]][(int) size[1]]; - int color; + float[] color = new float[3]; + Raster raster = canvas.getData(); for (int i=0; i=segStart && whistleStart=segStart && whistleEnd { //need this to notify classifier in viewer mode. return true; } - + } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java index b41b3083..93bbb7e3 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -10,6 +10,17 @@ import PamguardMVC.PamDataUnit; */ public class SegmenterDetectionGroup extends GroupDetection { + + /** + * The duration of the segment in millis. + */ + private double segDuration; + + /** + * The start time fo the segment in millis. + */ + private long segMillis; + /** * Constructor for a group of detections within a detection. Note that some * longer detections (e.g. whistles) may have sections outside the segment. @@ -22,6 +33,8 @@ public class SegmenterDetectionGroup extends GroupDetection { public SegmenterDetectionGroup(long timeMilliseconds, int channelBitmap, long startSample, double duration) { super(timeMilliseconds, channelBitmap, startSample, (long) duration); this.setDurationInMilliseconds(duration); + this.segMillis =timeMilliseconds; + this.segDuration = duration; } @Override @@ -29,6 +42,15 @@ public class SegmenterDetectionGroup extends GroupDetection { //segmetns share sub detections return true; } + + + public long getSegmentStartMillis() { + return segMillis; + } + + public double getSegmentDuration() { + return segDuration; + } } From ee694146c3349b63efe44780e6359fabf30569ff Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Thu, 2 May 2024 16:00:21 +0100 Subject: [PATCH 03/17] More updates to get DelphinID working --- .../animalSpot/StandardModelPane.java | 7 +- .../archiveModel/ArchiveModelWorker.java | 7 +- .../delphinID/DelphinIDPane.java | 130 +++++++++++++++--- .../delphinID/DelphinIDParams.java | 6 + .../delphinID/DelphinIDWorker.java | 5 + .../delphinID/Whistles2Image.java | 15 +- .../segmenter/SegmenterProcess.java | 35 ++++- .../DelphinIDTest.java | 91 ++++++++++++ .../DelphinID/whistle_image_example.mat | Bin 0 -> 19051 bytes 9 files changed, 266 insertions(+), 30 deletions(-) create mode 100644 src/test/rawDeepLearningClassifier/DelphinIDTest.java create mode 100644 src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java index 231a6d3e..7dda72ad 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java @@ -32,6 +32,7 @@ import pamViewFX.fxNodes.PamSpinner; import pamViewFX.fxNodes.PamVBox; import pamViewFX.validator.PamValidator; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; /** * Settings pane for SoundSpot @@ -163,7 +164,7 @@ public abstract class StandardModelPane extends SettingsPane { - +public class DelphinIDPane extends SettingsPane { + /** * The main pane. */ private Pane mainPane; - + /** * Reference to the delphinID classifier */ private DelphinIDClassifier delphinUIClassifier; + private PamSpinner detectionDensitySpinner; + + private Slider decisionSlider; + + private DelphinIDParams currentParams; + + private File currentSelectedFile; + public DelphinIDPane(DelphinIDClassifier delphinUIClassifier) { super(null); this.delphinUIClassifier = delphinUIClassifier; mainPane = createPane(); } - + private Pane createPane() { - //font to use for title labels. Font font= Font.font(null, FontWeight.BOLD, 11); - Node classifierIcon; - classifierIcon = delphinUIClassifier.getModelUI().getIcon(); - - + Label classifierIcon; + classifierIcon = new Label("DelphinID"); + PamGuiManagerFX.titleFont2style(classifierIcon); + //todo - will need to figure out colour of icon using CSS. + Node icon = PamGlyphDude.createPamIcon("mdi2r-rss", Color.BLACK, PamGuiManagerFX.iconSize); + icon.getStyleClass().add(getName()); + icon.setRotate(45); + classifierIcon.setGraphic(icon); + classifierIcon.setContentDisplay(ContentDisplay.RIGHT); + + + // String settings = currentParams.toString(); + // classifierIcon.setTooltip(new Tooltip(settings)); + PamVBox vBox = new PamVBox(); vBox.setSpacing(5.); - + + /**Classification thresholds etc to set.**/ + Label detectionDensity = new Label("Detection Density"); + detectionDensity.setFont(font); + String tooltip = "Set the minimum detection density to attempt to classify."; + detectionDensity.setTooltip(new Tooltip(tooltip)); + detectionDensitySpinner = new PamSpinner(0.0, 1.0, 0.3, 0.1); + detectionDensitySpinner.setPrefWidth(70); + detectionDensitySpinner.setEditable(true); + detectionDensitySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + + PamHBox minDensityHolder = new PamHBox(); + minDensityHolder.setAlignment(Pos.CENTER_RIGHT); + minDensityHolder.setSpacing(5); + Label minDensity = new Label("Min. density"); + minDensityHolder.getChildren().addAll(minDensity, detectionDensitySpinner); + /**Classification thresholds etc to set.**/ Label classiferInfoLabel2 = new Label("Decision Threshold"); classiferInfoLabel2.setTooltip(new Tooltip("Set the minimum prediciton value for selected classes. If a prediction exceeds this value " + "a detection will be saved.")); classiferInfoLabel2.setFont(font); - - - vBox.getChildren().addAll(classifierIcon, classiferInfoLabel2); - + + decisionSlider = new Slider(); + decisionSlider.setMin(0); + decisionSlider.setMax(1); + decisionSlider.setMajorTickUnit(0.2); + decisionSlider.setMinorTickCount(10); + decisionSlider.valueProperty().addListener((obsVal, oldVal, newVal)->{ + classiferInfoLabel2.setText(String.format("Decision Threshold %.2f", newVal)); + }); + decisionSlider.setShowTickMarks(true); + decisionSlider.setShowTickLabels(true); + + vBox.getChildren().addAll(classifierIcon, detectionDensity, minDensityHolder, classiferInfoLabel2, decisionSlider); + return vBox; } @Override public DelphinIDParams getParams(DelphinIDParams currParams) { - // TODO Auto-generated method stub - return null; + currParams.threshold = decisionSlider.getValue(); + currParams.minDetectionDensity = detectionDensitySpinner.getValue(); + return currParams; } @Override public void setParams(DelphinIDParams input) { - // TODO Auto-generated method stub + this.currentParams = input; + decisionSlider.setValue(input.threshold); + detectionDensitySpinner.getValueFactory().setValue(input.minDetectionDensity); + + if (input.modelPath!=null) { + //this might + currentSelectedFile = new File(currentParams.modelPath); + + //this might change the paramsClone values if the model contains pamguard compatible metadata + newModelSelected(currentSelectedFile); + } } + private void newModelSelected(File currentSelectedFile2) { + if (currentParams!=null && currentParams.defaultSegmentLen != null) { + + //System.out.println("Defualt segment length: " + paramsClone.defaultSegmentLen); + + //cannot use because, if the parent datablock has changed, samplerate will be out of date. + // int defaultsamples = (int) this.soundSpotClassifier.millis2Samples(paramsClone.defaultSegmentLen); + + + // float sR = dlClassifierModel.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); + + int defaultsamples = StandardModelPane.getDefaultSamples(delphinUIClassifier, currentParams); + + //work out the window length in samples + delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue(defaultsamples); + // dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2); + + delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().setDisable(true); + } + else { + delphinUIClassifier.getDLControl().getSettingsPane().getSegmentLenSpinner().setDisable(false); + } + + } + + + @Override public String getName() { return "delphinIDParams"; @@ -82,7 +176,7 @@ public class DelphinIDPane extends SettingsPane { @Override public void paneInitialized() { // TODO Auto-generated method stub - + } } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDParams.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDParams.java index 8cbe9398..e655270c 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDParams.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDParams.java @@ -8,5 +8,11 @@ public class DelphinIDParams extends StandardModelParams { * */ private static final long serialVersionUID = 1L; + + /** + * The minimum detection density. + */ + public double minDetectionDensity = 0.3; + } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java index 547f8bc8..b3ef293d 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -48,6 +48,11 @@ public class DelphinIDWorker extends ArchiveModelWorker { System.err.println("Error: could not find whistle2image transform in DelphinID JSON file. Model will not work."); this.setModel(null); // set model to null to make sure nothing works and errors are thrown } + + dlParams.binaryClassification = new boolean[dlParams.classNames.length]; + for (int i=0; i points, double[] size, double[] xlims, double[] ylims, double markerSize) { + public static BufferedImage makeScatterImage(ArrayList points, double[] size, double[] xlims, double[] ylims, double markerSize) { BufferedImage canvas = new BufferedImage((int) size[0], (int) size[1], BufferedImage.TYPE_INT_RGB); @@ -187,9 +189,13 @@ public class Whistles2Image extends FreqTransform { x = ((points.get(j)[i][0]-xlims[0])/(xlims[1]-xlims[0]))*size[0]; y = ((points.get(j)[i][1]-ylims[0])/(ylims[1]-ylims[0]))*size[1]; -// System.out.println("Fill oval: x" + x + " y: " + y + " time: " + points.get(j)[i][0]); + //System.out.println("Fill oval: x" + x + " y: " + y + " time: " + points.get(j)[i][0]); + + Graphics2D g2 = (Graphics2D) canvas.getGraphics(); - canvas.getGraphics().fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2.fillOval((int) (x+markerSize/2),(int) (y-markerSize/2), (int) markerSize,(int) markerSize); } } @@ -206,6 +212,7 @@ public class Whistles2Image extends FreqTransform { public double[] size; } + diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java index 134204ab..407d4d03 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java @@ -60,7 +60,17 @@ public class SegmenterProcess extends PamProcess { /** * Holds groups of data units which are within a defined segment. */ - private SegmenterGroupDataBlock segmenterGroupDataBlock; + private SegmenterGroupDataBlock segmenterGroupDataBlock; + + /** + * The first clock update - segments for detection groups (not raw sound data) are referenced from this. + */ + private long firstClockUpdate; + + /** + * The current segmenter detection group. + */ + private SegmenterDetectionGroup segmenterDetectionGroup = null; public SegmenterProcess(DLControl pamControlledUnit, PamDataBlock parentDataBlock) { @@ -111,7 +121,7 @@ public class SegmenterProcess extends PamProcess { */ @Override public ArrayList getCompatibleDataUnits(){ - return new ArrayList>(Arrays.asList(RawDataUnit.class, ClickDetection.class, ClipDataUnit.class)); + return new ArrayList>(Arrays.asList(RawDataUnit.class, ClickDetection.class, ClipDataUnit.class, ConnectedRegionDataUnit.class)); } @@ -177,6 +187,8 @@ public class SegmenterProcess extends PamProcess { if (rawDataBlock==null) return; setParentDataBlock(rawDataBlock); + + this.firstClockUpdate = -1; } @@ -232,10 +244,27 @@ public class SegmenterProcess extends PamProcess { //TODO //this contains no raw data so we are branching off on a completely different processing path here. - //Whislte data units are saved to a buffer and then fed to the deep learning algorohtm + //Whislte data units are saved to a buffer and then fed to the deep learning algorithms + + if (segmenterDetectionGroup==null) { + //iterate until we find the correct time + long segmentStart = firstClockUpdate; + while() { + + } + } } + + + public void masterClockUpdate(long milliSeconds, long sampleNumber) { + super.masterClockUpdate(milliSeconds, sampleNumber); + if (firstClockUpdate<0) { + firstClockUpdate = milliSeconds; + } + } + /** diff --git a/src/test/rawDeepLearningClassifier/DelphinIDTest.java b/src/test/rawDeepLearningClassifier/DelphinIDTest.java new file mode 100644 index 00000000..198c00ed --- /dev/null +++ b/src/test/rawDeepLearningClassifier/DelphinIDTest.java @@ -0,0 +1,91 @@ +package test.rawDeepLearningClassifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.utils.DLMatFile; +import org.junit.jupiter.api.Test; + +import rawDeepLearningClassifier.dlClassification.delphinID.Whistles2Image; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.MatFile; +import us.hebi.matlab.mat.types.Matrix; + +public class DelphinIDTest { + + + @Test + public void whistle2ImageTest() { + + System.out.println("Whislte2Image test started"); + + /** + * Test whether the Whistles2Image transform works properly + */ + String relMatPath = "./src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat"; + + Path path = Paths.get(relMatPath); + + // Create MAT file with a scalar in a nested struct + try { + MatFile matFile = Mat5.readFromFile(path.toString()); + Matrix array = matFile.getArray("tfvalues"); + + //the values for the whistle detector. + double[][] whistleValues = DLMatFile.matrix2array(array); + + //the image after compression + array = matFile.getArray("image1compressedgrayscale"); + double[][] compressedWhistleImage = DLMatFile.matrix2array(array); + + //the whistle2Image transform image + array = matFile.getArray("image1originalgrayscalenorm"); + double[][] whislteImage = DLMatFile.matrix2array(array); + + //now perform the image transform in Java + double[] freqLimits = new double[] {0., 20000.}; + double[] size = new double[] {680., 480.}; + + ArrayList whistleImageArr = new ArrayList(); + whistleImageArr.add(whistleValues); + + BufferedImage canvas = Whistles2Image.makeScatterImage(whistleImageArr, size, new double[]{48, 48. + 4.}, freqLimits, 5.); + + double[][] imaged = new double[(int) size[0]][(int) size[1]]; + + float[] color = new float[3]; + Raster raster = canvas.getData(); + for (int i=0; iR##d5jt~z-*3IgPhm|G7#aR^c$kr30_QV$g5rhcviwcW~3J5?1 z`2~d_|BoBN-;GdN?E>)k9Ps0l_RGS|jO^zVGc)1=CMs$M61}W|vyeLk<_eHRlG(1w z=Iuw{=ME-A-Ve>r4kc{AZ|<0#YfgLg;r?}MLLn5Mlb$r&j4qOh%m)*}wyxn&^)M%B z%IQ@F-uQqqf3nRxY$vGs*S?5ImRCSa(Za6Dq??O~%+lkM7dCsGu$`C?O@4~!Gz0`5E1O&3T4iLxwXStFk$>l> z3ZF@mk@0UQx@QyopzojB5Uy7GHIg)K7nVwP%die7ou&25e*1mW4A&%93Y+Q;Qi}H7 zOD-=@DI-#N1nPb!6*W`qCds&#;Fe*g)iAhs^_?|jNwgT-|G6C^!FlocwHASM1O#Nn zpYMH#Gjj0!fY@bTx4BW}H1T)Gu9Kc#cZ!SCDGyHV1M~`~JUHdSDGyHL!aRM8u^FR;WLKcWTo?kB&fA1HF(Tg z|Iz-MP1NQ5jp)n!Ne{H6A8%`<>kKMO7u;PW8txX1xbahI~J~!_B zL|rU-yx{pN-W!F--C$$8zeRM^XzVQ|50;{m>W_q6suEFA7~x|2O72V|@N?MUG+C;r=(on?*mH=IPKhz?0q zVdw3Yp(2gH$d3W30|23u>tC1VAJWPbGW;D{m0kFjg5j;zL~=$ZwFW z>PX2QZ_OMF(^&(Ik`t9WLw$ywb*bYi3BKhNF2jxb`8QE2hm6Mx!tb2GlyybwAwhV* zNa#C;`6z3$n+0TL_5{RcL;)yI)~Kh4ZY$j)VM8p$%IM388eRaW$j8n;6Ir%*^Jm>^b$Klm(Na9F2_wCn{s zlt@X6rAm7-C#bh}U1kFt9``Cmqyei}5pSM=*v8Zd{&$X=whVzi9rPH za%y5QhDnGG47+lp{c+0Lqe=dipGv-8iT*Y}SKPE+a)>fLWyN_Ft~9JW8R`a2kjs&s zxqY(PnbVx|ee5(DXo@@0IA;4_+(JQXWhBgfXO+OnYZ2!3&Vu;N4}z<-1fr@&YTg(9 z;J#&(U%XUugVY^oOOv2AF$i&}ifVdE;HucMZk4Ao%uYMKB68Z5uNMwpHFl$}DZ}P& z|FvOI;eeUheg1Lc6<*yNhh z2zW7fYwv=_F$f57J}QEy$Y8x-O>xl&gc7v;MBlUq`V>^+unU$ouFG1D8K8l$t$13O zT48%@SpxwUr77ac0jbA&h3hQZqjz;jYBm&+?KJ@>E=dCL8&py=DiC@pmE0MUoM57h z^k!#>A-AhCt|t5GM?#xcVVtLQR)<_a zCkpNUGFip|j@DeF^?(K+(6%w3vPVL*Y1;T-S_C;PZS+-BuvXo5SN$iM`Oo6dv|-!< zaNjVYkJ=qmS3n)Qdo?FGm0CJvPC&QQLCk?2SvOLM$7NLEmH;~A(I+~T22Jbv|7BWd z{5TUZ#w;^%j#*SU1iW3{S4V?oI9+$RbqX^abTpo_n}+0HxsHI9?u`u>XE2oMB?4hx z;(8BpMW(Td+$xfrOuxc3tHnI_Nus*aZ#wY4zsB(hFw_oS5yk;8_MbO3`BzC&52=G^ zL_+kwa=Hkm@>9@qF7tjRL*L5kQ|6#RA_HoS+~;#2jk|+(#Es}rDX{HQ6EfPW!SAaB z+%m;+1B;z=pRnx=)J-Xt+}{vX%tUqu`TaXQzyXr4M;s9R%K?}jwI_9ri+ZB*mjkV0 zrpj23Ixj!gBtZ3EfI9#P-zVFDsDL{piF~#RU+%O4gJO98AUflQ>g;2o$ zHDiJ~2LxX!TF?rryoFdT=|ip%?x}iFR4dV?O)%A9=i{|+;s7U*F}wQ`P1hirsk)rL zxa7YF_yKeyLPAi%e?nl#sU#LyMbPSfs2mQDq%LyOFb(8WMrW{`tfG!Q>>)L2pKgew znLSrY1+Q8`UjU=$ldy9uFVtco6%*wFEy8i}@v_F!7=PM^m)D^R< zX5=5*0S$K~J)kj=3;W`b0jOB(ZrIQhLRMGfgi8DR<*fmKFk4t~Yx~LQWnZ18;iOZL z9P|R9duFL-|4m_I@7M4^6F&_g2WU3hcpKz%%*C-%@^F4Y^1~@Uz2Artc2<%dR5*`#M1dC=FpgV8+>Di;+uQh02Xl zyD<*<3_o>nhJs0p-^!{EZ;FRoC-6|uT-XZst)J|8vNXG z{sj0Zfch^sMM5|$6l(grz7j-+kaE!ZT{0u7QB~i(7_|@=V7lc{@@1Aa#7jj=u31+H zaW%YB#croMSy;(=y&WYFOcA-0ei21K;vJ337{bg}??~!fJH_WATHEj|22Lta5fG;M zH7hx}gZTdn4q%7@a8DFfQE;bamcKz}OXFZHr5MY^5kbwvqdtdU_~+_V`BzU{!_GTU z^HmKvbGJ_@Fu=lv#1$ZPpo_>{HOJc zV(~T17tCs1tSY8rQ4tUz`vh}9@qiCi05GShiiY*4Nnh4_de$GDp0asXiX`>NrpZ>N zZqA<>IjwvOXj%1nr=rJV4z)#v`VI~kf{qlQ+GvP2{?hYsnI`ohX>S@MJx9_r z8`Q1%Wnn;~rzWAZPNfR=9Ru4bPv`g}?WQwv7Fbqkm1~lve^gHq1{hk7$CE)DEupxG z@P8lWS8ceE@Gt;BSeLaUNex_z9GV_bs><(ON;L@$+C7f!qXHD6QUL4{G7+%}_d1xm z4L?*XL&7UVcKrx`k(AO2JVbQYIdIDF94D>o8HoJGC;u}e;i)*PV+~-1n&AWT-G;$3IcCB9Pxom z7$8v=?>LQ(jR0ri6NZtzEH>mNs_4X zuW#Y1`dGQE%6GIN<5Ar!cZpz|#Ze?Y7^Kkpy7l~1?VwDA_+ z!!`im)8K~}lZWbmxzqS(xG$sjzBp(W>-eFP#)$xDptuVB($a&ijm@q$RZ^jmV%uNz6y&yg7=i%KL@24*O!f*Be!b@U73Ufz@n|c;N6_@YD{O zg72lT4>aVBV0iJ%n&62O6X5hpJ z?NR?@?&iCcwo3=Z0H3c_{q>fJ_3s74s?KWdBUse)z+&$8M&Db8iQ$O$_7%ix1LoTn z^z)psg9RD~R*I4HV}6D1y;vr?jeHZJ0%tRK{GL{h35xJt5@C^u88zX_JiyXy`k(kS zL;;YF&?(IH1QWD#dJ9z%oXv)~{+PW&@RmTyMXNIvj!ob@Zpw1rv+kK)z@cyHh+AkKB z3V}aUz}4OxC&5yH;v1m08Ho^1y%o&`Ta%^_wpQY=o&~29@|rVWpq6;nLFLFm{xXE; z7D|{kj5kqjODun?hMG zMOzTNrCcyt2S^&UMERHOKu_SsCeU!O1VSwbeG3T?Y0i8l`QhKLOn13|5)3um9O#aq z9(ud-GcK3L?Qa*A*C2exrOOD85^0Py4uwx{4(}PCV2Wnv%-gcj&`cVpivS^c4`C zD}j8=_Qr?UnyAVb$QYkC+LJYd9$#U`P@2+Ev8I1gPLbv^b;aix$|W*b=qRat7=1v= z9z`nqa12|cF)1+2v)oF1ryWghhyK=a7&1L|YtFqNa}KGE1U>2>Pt?Z9H-?_{mS~U? za0ai3Bm1qL%>V4_tkk*3;~H>i3)cmDYHE>u@qBZpBh?jM~pxs@(CvV#9d774^)*Vh0Bpe%ro z|8`slb8_bK2`?0I#!M?;zSe?kIx`(Z4AP3K^yk~%xU1ms5bLr&$649*;GXhJQPKVZXcO5 zFzUG7o(J>I_Ok&sm@p7Fhx;2pj^_h)Xts<~k8cZO^Gu9Q+e zp+nJG>S(iFk#f>=SFg$d*<85!Ge_ZMYOPRGxn^?8ovQdxm)!I1Ckk56f2thi5-ZR+ zJiPtsAV3qhf&tYTAsKU1z318e-Xa{_upjk(@z-X3S0Di1?7+TBQGK3?ucU}Kd~tE3 zdWan!)4&Xzz~he7JcPh`siQ0lNUEa+72PQxF@>w=dk~!cN*;6(Rh89i6Mo@VH#IhXWQ-r+pyF$f(HXN_dA*=(LM|3y3+)w4Wx>>Qv&NMVvEDH@ zWQfEoiUe+GT*3IPWEj{#aq5*J+5g3bbXut5i1e;4}LRK?{YafurK2Zu5D z#|OZ2q#57NB3syd5_Eu^56l>tvK}psXV+)@UTjba?_s`4ZzB=BOHw)4w0hmrmWm4x z4^T=J&sR*NY-k36WcjI|I{#_v{Z8oh39$;xO!ha~BVPE>^u{(F%}4`>g6-ZujyGO0(6beR!WgEH6`1~bMBrkTi&$@Mnbmv!=F4H|lq|CQb`dP+jOhl&2g_bKP(nq7OeM7vG==r#-@v>>9PrOSJEd><3f?)Drhu2qy8CQ4LMz7 zd&=1T#_c*#OjlF;9;P0ye$n3bMSPHG3D!Q-qPp(Jgd#TgD5$O1+~Vq=KH+V{xlkY< zexeh6otb4`p_3w(sMeBBi8wvil30&GA!37LuqJ|9Qghm&@mqI|*jSHU)R%~4VHNXk zkfIT(47B6egMs3)CoC-`4+Hg81avWz2iByeZ}ZB3`TFo~sBje# zue7xpyN1wR{RDjcWh$QU3cu2-UdaM<7?}V{qvYXrs;$wZt_0hmVClh(gg;Oa-Ph7J z;QJ{2_|%ItwXkf4*GNXK=ZXhU09KI*(k)Ov3^@35OnzElZn}ro6iH zJa3WlNXsi5xCRbviO4mJ>^C|UZrtW}=o@G7%#fK_FW$xGqxP}dc%UwqJIHKgrznWQ ze(PPM0xmviIG%O%gb*>owwz$UmK3@Ys~KXebxn)&-E~S1b~$etmsCVYSp>j{4s`o) zdyk!|YebOalR!%!yYh<5>sQr^c3_6pW_bwz+as038Fh<-tBqZ(SY`l=P4Ru)j4kr5 z0{4FX5r3}k;N4#ilj1KFs;vKEftR#FSpm_+Gof&O)aAd8N5Fvj>0Y=-OO2|^XK3 zVD<*2uuo!QKT9d>DRxr4W&3FMhMq!{V)Uy%Br|N49Z?)@%Mr9%Wi4Q}^?e-Ec=AYR z&cDqu=BnWa+_bwA1vb_+f!Xr(|e6bej#4?UV-!;4f_N{{~ zO028wdz~I(0@mUG88q_{dXirEG=X`&ytFFWomj1^iiEt%Q7J8%r%fIZwyrv=PPn6k zw?vgA*iZ%bgE)UL`SzXPx1N#soUIb#~i__*Bd=pehvbz9o=o0}pPF zo{peMxW16wTO?vug2X;FRwHY#w|?bx#~k5)Bv89ekrl$tAW#wm#U4Mg5??&AwiCP~ zA-8^Vilk&O(~Z{!M?=nR_%U9X!!6JZPT&4Ief#Tw$p_#E*^a@>1(e)}rD_U#~TYhk}tz=>i+te`V zmqVq@;&C}MAB{n)TxxSJe%be7cKpRf`>S)e{NBxwTMv^r!CES5NJ?Ya4_tSaKC`~{ zv6rjd@uYUMe{k?o+c?bc9hrnUuQqV%;><-l8RHI_rigU>8_OA2Odh=5PVlwTlDQQ^NsgaWW4jCUI;O5o#X+Q zCm)ug%BPdNHCk;fy`=ajBCDr=6=9+g{x??BYuH|2-iUn4y1FsY;%B3x@9iOP%f|?9 z@k3c-!@0b>eTMx|(S0e^z6o6PyD2*v!Thi!v6h1xTE0KEI~leOHw;iNdMIVGvh~gf z&UsOk(-qS=vI}k)ouy~|*TTKWzx$zH_6e}NOAS|3CzkeDJkWQR<0F|iQjf9>ldLI1 z#}{Fy$?^iR5GCIWyQR}uwu9?V+f_R~;sq21RD zN)l>pv4Uw9(QnQLDXrKE+679orE}+P3Wb~9bt#{HMAT%EX9uSu(k~uwBZ>JD@?h}H zAem1sWndk(H@K8e zyDM!uaxbHP=&S}Z+GRN1=q_JJV9!tRiIMMa8uP7HOoL)yw#!&An-0zKO;k*=@kRJI z?wFW0T-HN{=lJT#rP#C}#0DdGoQd|L&fS@Ip|3S2m&IJk)73o#b4iv@#!Nr`_)aUwHpRVZtnE6i*3H{Ea|$7_eOc^ z&llf%C|qw+3@t7$o-RHt<}-rV>_w#UiM)v0Vz}4q+{3=?_O8(5-bLl4+au<-_G9#u zCi_^%{`u*sB{#HA|C9*2X6i6qQung1q^_y1%gEW06zq>ZZ=5H4?W&9L6wVl~C@7f3 i#J Date: Fri, 3 May 2024 16:29:37 +0100 Subject: [PATCH 04/17] More work on delphinID classifier but not quite there yet. --- src/rawDeepLearningClassifier/DLControl.java | 1 + .../dlClassification/DLClassifyProcess.java | 184 +++++++++++------- .../StandardClassifierModel.java | 2 +- .../delphinID/DelphinIDClassifier.java | 2 +- .../delphinID/DelphinIDWorker.java | 5 +- .../genericModel/GenericDLClassifier.java | 2 +- .../genericModel/GenericModelWorker.java | 6 +- .../segmenter/SegmenterDetectionGroup.java | 6 +- .../segmenter/SegmenterGroupDataBlock.java | 2 + .../segmenter/SegmenterProcess.java | 118 +++++++++-- 10 files changed, 240 insertions(+), 88 deletions(-) diff --git a/src/rawDeepLearningClassifier/DLControl.java b/src/rawDeepLearningClassifier/DLControl.java index fda39d72..3b2ad27f 100644 --- a/src/rawDeepLearningClassifier/DLControl.java +++ b/src/rawDeepLearningClassifier/DLControl.java @@ -228,6 +228,7 @@ public class DLControl extends PamControlledUnit implements PamSettings { // classify the raw data segments. addPamProcess(dlClassifyProcess = new DLClassifyProcess(this, segmenterProcess.getSegmenterDataBlock())); + dlClassifyProcess.addMultiPlexDataBlock(segmenterProcess.getSegmenteGrouprDataBlock()); //manages the names assigned to different output classes. dlClassNameManager = new DLClassNameManager(this); diff --git a/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java b/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java index fe515cb1..bb0f8945 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java @@ -20,6 +20,7 @@ import rawDeepLearningClassifier.logging.DLAnnotation; import rawDeepLearningClassifier.logging.DLAnnotationType; import rawDeepLearningClassifier.segmenter.GroupedRawData; import rawDeepLearningClassifier.segmenter.SegmenterDataBlock; +import rawDeepLearningClassifier.segmenter.SegmenterDetectionGroup; /** * The deep learning classification process. This takes a segment of raw data from the segmenter. @@ -35,7 +36,7 @@ import rawDeepLearningClassifier.segmenter.SegmenterDataBlock; */ public class DLClassifyProcess extends PamInstantProcess { - + /** * Holds all model results but no other information */ @@ -64,15 +65,13 @@ public class DLClassifyProcess extends PamInstantProcess { /** * The DL buffer */ - private ArrayList classificationBuffer; - + private ArrayList classificationBuffer; /** * The DL annotation type. */ private DLAnnotationType dlAnnotationType; - /** * The last parent data for grouped data. This is used to ensure that DLDetections * correspond to the raw chunk of data from a parent detection e.g. a click detection. @@ -82,8 +81,8 @@ public class DLClassifyProcess extends PamInstantProcess { public DLClassifyProcess(DLControl dlControl, SegmenterDataBlock parentDataBlock) { super(dlControl); - - + + this.setParentDataBlock(parentDataBlock); // this.setParentDataBlock(parentDataBlock); @@ -117,7 +116,7 @@ public class DLClassifyProcess extends PamInstantProcess { overlayGraphics.setDetectionData(true); dlDetectionDataBlock.setOverlayDraw(overlayGraphics); - classificationBuffer = new ArrayList(); + classificationBuffer = new ArrayList(); //the process name. setProcessName("Deep Learning Classifier"); @@ -140,7 +139,7 @@ public class DLClassifyProcess extends PamInstantProcess { System.err.println("Raw Deep Learning Classifier: The grouped source parameters were null." + " A new instance has been created: Possible de-serialization error."); } - + //important for downstream processes such as the bearing localiser. dlModelResultDataBlock.setChannelMap(dlControl.getDLParams().groupedSourceParams.getChannelBitmap()); @@ -166,7 +165,7 @@ public class DLClassifyProcess extends PamInstantProcess { public void prepareProcess() { setupClassifierProcess(); } - + /** * called for every process once the system model has been created. @@ -194,36 +193,71 @@ public class DLClassifyProcess extends PamInstantProcess { */ @Override public void newData(PamObservable obs, PamDataUnit pamRawData) { +// System.out.println("NEW SEGMENTER DATA"); - //the raw data units should appear in sequential channel order - GroupedRawData rawDataUnit = (GroupedRawData) pamRawData; - - if (checkGroupData(rawDataUnit)) { - //check whether the classification buffer is full. If it is then run - if (isClassificationBufferFull(classificationBuffer, rawDataUnit)) { - - //first call run model to clear out the classification buffer if needs be - runModel(); + if (pamRawData instanceof SegmenterDetectionGroup) { + if (classificationBuffer.size()>=1) { + System.out.println("RUN THE MODEL FOR WHISTLES: "); + runDetectionGroupModel(); classificationBuffer.clear(); } - - classificationBuffer.add(rawDataUnit); + else { + classificationBuffer.add(pamRawData); + } } -// System.out.println("New raw data in: chan: " + PamUtils.getSingleChannel(pamRawData.getChannelBitmap()) + -// " Size: " + pamRawData.getSampleDuration() + " first sample: " + rawDataUnit.getRawData()[0][0] -// + "Parent UID: " + rawDataUnit.getParentDataUnit().getUID()); + + if (pamRawData instanceof GroupedRawData) { + //the raw data units should appear in sequential channel order + GroupedRawData rawDataUnit = (GroupedRawData) pamRawData; + + if (checkGroupData(rawDataUnit)) { + //check whether the classification buffer is full. If it is then run + if (isRawClassificationBufferFull(classificationBuffer, rawDataUnit)) { + + //first call run model to clear out the classification buffer if needs be + runRawModel(); + classificationBuffer.clear(); + } + + classificationBuffer.add(rawDataUnit); + + } + } + // System.out.println("New raw data in: chan: " + PamUtils.getSingleChannel(pamRawData.getChannelBitmap()) + + // " Size: " + pamRawData.getSampleDuration() + " first sample: " + rawDataUnit.getRawData()[0][0] + // + "Parent UID: " + rawDataUnit.getParentDataUnit().getUID()); } - + + + /** + * Run a model for which the input is a detection group. + */ + private void runDetectionGroupModel() { + if (classificationBuffer.size()<=0) return; + ArrayList classificationBufferTemp = (ArrayList) classificationBuffer.clone(); + + ArrayList modelResults = this.dlControl.getDLModel().runModel(classificationBufferTemp); + + for (int i=0; i classificationBufferTemp = (ArrayList) classificationBuffer.clone(); - ArrayList modelResults = this.dlControl.getDLModel().runModel(classificationBuffer); + ArrayList modelResults = this.dlControl.getDLModel().runModel(classificationBufferTemp); if (modelResults==null) { return; //there has been a problem @@ -235,18 +269,18 @@ public class DLClassifyProcess extends PamInstantProcess { // System.out.println("Compare Times: " + PamCalendar.formatDBDateTime(modelResults.get(i).getTimeMillis(), true) + // " " + PamCalendar.formatDBDateTime(classificationBufferTemp.get(i).getTimeMilliseconds(), true) + " " + // modelResults.get(i).getPrediction()[1]); - newModelResult(modelResults.get(i), classificationBufferTemp.get(i)); + newRawModelResult(modelResults.get(i), classificationBufferTemp.get(i)); } } } /** - * Check whether the buffer is full and the results should be passed to the classification model. + * Check whether the buffer is full and the results should be passed to the classification model if we are using GrpoupDataUnits * @param classificationBuffer2 - the classification buffer. * @param rawDataUnit - the next raw data unit to add to the buffer. * @return true if the buffer is full. */ - private boolean isClassificationBufferFull(ArrayList classificationBuffer2, GroupedRawData rawDataUnit) { + private boolean isRawClassificationBufferFull(ArrayList classificationBuffer2, GroupedRawData rawDataUnit) { if (classificationBuffer2.size()==0) return false; @@ -254,16 +288,16 @@ public class DLClassifyProcess extends PamInstantProcess { //1) It's over a max time //2) Contains different parent data units (if not from raw data). - GroupedRawData lastUnit = classificationBuffer2.get(classificationBuffer2.size()-1); + GroupedRawData lastUnit = (GroupedRawData) classificationBuffer2.get(classificationBuffer2.size()-1); if (!(lastUnit.getParentDataUnit() instanceof RawDataUnit) && lastUnit.getParentDataUnit()!=rawDataUnit.getParentDataUnit()) { //there is a new parent data unit. return true; } - //get the start time. Use min value instead of first data just in case units ar enot in order. + //get the start time. Use min value instead of first data just in case units are not in order. long min = Long.MAX_VALUE; - for (GroupedRawData groupedRawData: classificationBuffer2) { + for (PamDataUnit groupedRawData: classificationBuffer2) { if (groupedRawData.getTimeMilliseconds()0) { - //System.out.println("Save click annotation to " + lastParentDataUnit[i].getUID()); + //System.out.println("Save click annotation to " + lastParentDataUnit[i].getUID()); addDLAnnotation(dataUnit,groupDataBuffer[i],modelResultDataBuffer[i]); lastParentDataUnit[i]=null; clearBuffer(i); @@ -532,15 +573,22 @@ public class DLClassifyProcess extends PamInstantProcess { @Override public void pamStart() { - // TODO Auto-generated method stub - System.out.println("PREP MODEL:"); +// System.out.println("PREP MODEL:"); this.dlControl.getDLModel().prepModel(); } @Override public void pamStop() { - runModel(); //make sure to run the last data in the buffer. - + //make sure to run the last data in the buffer. + if (this.classificationBuffer.size()>0) { + if (classificationBuffer.get(0) instanceof GroupedRawData) { + runRawModel(); //raw data or raw data units + } + if (classificationBuffer.get(0) instanceof SegmenterDetectionGroup) { + runDetectionGroupModel(); //any other data units. + } + } + //21/11/2022 - it seems like this causes a memory leak when models are reopened and closed every file... //this.dlControl.getDLModel().closeModel(); } diff --git a/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java b/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java index 0fbce3de..b6138e44 100644 --- a/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java +++ b/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java @@ -271,7 +271,7 @@ public abstract class StandardClassifierModel implements DLClassiferModel, PamSe */ protected void newResult(StandardPrediction modelResult, PamDataUnit groupedRawData) { if (groupedRawData instanceof GroupedRawData) { - this.dlControl.getDLClassifyProcess().newModelResult(modelResult, (GroupedRawData) groupedRawData); + this.dlControl.getDLClassifyProcess().newRawModelResult(modelResult, (GroupedRawData) groupedRawData); } } // diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java index b55c8655..0a7e2813 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java @@ -91,7 +91,7 @@ public class DelphinIDClassifier extends StandardClassifierModel { @Override public boolean isDecision(StandardPrediction modelResult, StandardModelParams modelParmas) { //TODO - //DelphinID uses a different decision making process to most of the standard classifiers which just pass a binary threhsoild. + //DelphinID uses a different decision making process to most of the standard classifiers which just pass a binary threshold. return false; } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java index b3ef293d..c411cce1 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -49,8 +49,8 @@ public class DelphinIDWorker extends ArchiveModelWorker { this.setModel(null); // set model to null to make sure nothing works and errors are thrown } - dlParams.binaryClassification = new boolean[dlParams.classNames.length]; - for (int i=0; i { @Override public float[] runModel(float[][][] transformedDataStack) { - //System.out.println("RUN GENERIC MODEL: " + transformedDataStack.length + " " + transformedDataStack[0].length + " " + transformedDataStack[0][0].length); + System.out.println("RUN GENERIC MODEL: " + transformedDataStack.length + " " + transformedDataStack[0].length + " " + transformedDataStack[0][0].length); // System.out.println("RUN GENERIC MODEL: " + transformedDataStack[0][0][0]); float[] results; if (freqTransform) @@ -47,7 +48,8 @@ public class GenericModelWorker extends DLModelWorker { //System.out.println("RUN GENERIC MODEL WAVE: " + waveStack.length + " " + waveStack[0].length + " " + waveStack[0][0]); results = getModel().runModel(waveStack); } - //System.out.println("GENERIC MODEL RESULTS: " + results== null ? null : results.length); + System.out.println("GENERIC MODEL RESULTS: " + (results== null ? null : results.length)); + PamArrayUtils.printArray(results); return results; } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java index 93bbb7e3..fe218fbf 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -28,7 +28,7 @@ public class SegmenterDetectionGroup extends GroupDetection { * @param timeMilliseconds - this is the start of the SEGMENT - Note that the * @param channelBitmap - channels of all detections * @param startSample - the stratSample of the SEGMENT. - * @param duration - the duration of the SEGMENT. + * @param duration - the duration of the SEGMENT in milliseconds. */ public SegmenterDetectionGroup(long timeMilliseconds, int channelBitmap, long startSample, double duration) { super(timeMilliseconds, channelBitmap, startSample, (long) duration); @@ -52,5 +52,9 @@ public class SegmenterDetectionGroup extends GroupDetection { return segDuration; } + public long getSegmentEndMillis() { + return (long) (segMillis+segDuration); + } + } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java index 3bda33fd..3a69c23a 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java @@ -12,6 +12,8 @@ public class SegmenterGroupDataBlock extends PamDataBlock=segStart && whistleStart=segStart && whistleEnd Date: Tue, 7 May 2024 16:15:29 +0100 Subject: [PATCH 05/17] DelphinID almost there. --- .../dataPlotFX/DLPredictionPane.java | 2 + .../dataPlotFX/DLPredictionPlotInfoFX.java | 4 +- .../dlClassification/DLClassifyProcess.java | 7 +- .../StandardClassifierModel.java | 5 +- .../archiveModel/ArchiveModelWorker.java | 3 +- .../delphinID/DelphinIDClassifier.java | 7 +- .../delphinID/DelphinIDWorker.java | 59 +++++++++- .../delphinID/Whistles2Image.java | 13 ++- .../genericModel/GenericModelWorker.java | 6 +- .../segmenter/SegmenterDetectionGroup.java | 12 +- .../segmenter/SegmenterProcess.java | 110 ++++++++++++------ .../DelphinIDTest.java | 22 ++++ .../DelphinID/whistle_image_example.mat | Bin 19051 -> 19612 bytes 13 files changed, 191 insertions(+), 59 deletions(-) diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java index c603f08f..19147c81 100644 --- a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java +++ b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java @@ -101,6 +101,8 @@ public class DLPredictionPane extends PamBorderPane implements TDSettingsPane { if (dlPredictionPlotInfoFX.getDlControl().getDLModel()!=null) { //populate the prediction pane. DLClassName[] classNames = dlPredictionPlotInfoFX.getDlControl().getDLModel().getClassNames(); + +// System.out.println("MAKE MY CLASS NAMES: " + dlPredictionPlotInfoFX.getDlControl().getDLModel().getClassNames()); layoutColourPanes(classNames); } diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java index 44fdca6f..36aed7ed 100644 --- a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java +++ b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java @@ -94,7 +94,7 @@ public class DLPredictionPlotInfoFX extends GenericLinePlotInfo { if (getDlControl().getDLModel()!=null) { DLClassName[] classNames = getDlControl().getDLModel().getClassNames(); -// System.out.println("Class names are: !!! " + (classNames == null ? "null" : classNames.length)); + System.out.println("Class names are: !!! " + (classNames == null ? "null" : classNames.length)); if (classNames!=null) { @@ -105,8 +105,8 @@ public class DLPredictionPlotInfoFX extends GenericLinePlotInfo { dlPredParams.lineInfos[i] = new LineInfo(true, Color.rgb(0, 0, 255%(i*30 + 50))); } } - } + getGraphSettingsPane().setParams(); } } diff --git a/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java b/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java index bb0f8945..03806aa3 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLClassifyProcess.java @@ -197,7 +197,7 @@ public class DLClassifyProcess extends PamInstantProcess { if (pamRawData instanceof SegmenterDetectionGroup) { if (classificationBuffer.size()>=1) { - System.out.println("RUN THE MODEL FOR WHISTLES: "); +// System.out.println("RUN THE MODEL FOR WHISTLES: "); runDetectionGroupModel(); classificationBuffer.clear(); } @@ -232,14 +232,15 @@ public class DLClassifyProcess extends PamInstantProcess { /** * Run a model for which the input is a detection group. */ - private void runDetectionGroupModel() { + private synchronized void runDetectionGroupModel() { if (classificationBuffer.size()<=0) return; ArrayList classificationBufferTemp = (ArrayList) classificationBuffer.clone(); ArrayList modelResults = this.dlControl.getDLModel().runModel(classificationBufferTemp); for (int i=0; i) delphinIDParams.dlTransfromParams); } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java index c411cce1..a2fcc459 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -2,6 +2,7 @@ package rawDeepLearningClassifier.dlClassification.delphinID; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import org.jamdev.jdl4pam.transforms.DLTransform; @@ -9,18 +10,23 @@ import org.jamdev.jdl4pam.transforms.DLTransfromParams; import org.jamdev.jdl4pam.transforms.FreqTransform; import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; import org.jamdev.jdl4pam.transforms.jsonfile.DLTransformsParser; +import org.jamdev.jdl4pam.utils.DLMatFile; import org.jamdev.jdl4pam.utils.DLUtils; import org.json.JSONArray; import org.json.JSONObject; +import PamUtils.PamArrayUtils; import PamguardMVC.PamDataUnit; import ai.djl.Model; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; import rawDeepLearningClassifier.dlClassification.delphinID.Whistles2Image.Whistle2ImageParams; -import rawDeepLearningClassifier.segmenter.GroupedRawData; import rawDeepLearningClassifier.segmenter.SegmenterDetectionGroup; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.MatFile; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; /** * @@ -86,11 +92,50 @@ public class DelphinIDWorker extends ArchiveModelWorker { return whistle2ImageParmas; } - } + //something has gone wrong if we get here. return null; } + + + + private Struct imageStruct; + int count = 0; + /** + * Tets by exporting results to a .mat file. + * @param data + * @param aSegment + */ + private void addIMage2MatFile(double[][] data, SegmenterDetectionGroup aSegment) { + long dataStartMillis = 1340212413000L; + + if (imageStruct==null) { + imageStruct = Mat5.newStruct(100,1); + } + Matrix image = DLMatFile.array2Matrix(data); + imageStruct.set("image", count, image); + imageStruct.set("startmillis", count, Mat5.newScalar(aSegment.getSegmentStartMillis())); + imageStruct.set("startseconds", count, Mat5.newScalar((aSegment.getSegmentStartMillis()-dataStartMillis)/1000.)); + + count++; + + System.out.println("SAVED " +count + " TO MAT FILE"); + + if (count==10) { + //create MatFile for saving the image data to. + MatFile matFile = Mat5.newMatFile(); + matFile.addArray("whistle_images", imageStruct); + //the path to the model + String matImageSave = "C:/Users/Jamie Macaulay/MATLAB Drive/MATLAB/PAMGUARD/deep_learning/delphinID/whistleimages_pg.mat"; + try { + Mat5.writeToFile(matFile,matImageSave); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } @Override @@ -110,7 +155,7 @@ public class DelphinIDWorker extends ArchiveModelWorker { double[][] transformedData2; //spectrogram data for (int j=0; j { @Override public float[] runModel(float[][][] transformedDataStack) { - System.out.println("RUN GENERIC MODEL: " + transformedDataStack.length + " " + transformedDataStack[0].length + " " + transformedDataStack[0][0].length); +// System.out.println("RUN GENERIC MODEL: " + transformedDataStack.length + " " + transformedDataStack[0].length + " " + transformedDataStack[0][0].length); // System.out.println("RUN GENERIC MODEL: " + transformedDataStack[0][0][0]); float[] results; if (freqTransform) @@ -48,8 +48,8 @@ public class GenericModelWorker extends DLModelWorker { //System.out.println("RUN GENERIC MODEL WAVE: " + waveStack.length + " " + waveStack[0].length + " " + waveStack[0][0]); results = getModel().runModel(waveStack); } - System.out.println("GENERIC MODEL RESULTS: " + (results== null ? null : results.length)); - PamArrayUtils.printArray(results); +// System.out.println("GENERIC MODEL RESULTS: " + (results== null ? null : results.length)); +// PamArrayUtils.printArray(results); return results; } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java index fe218fbf..9636a52b 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -21,6 +21,8 @@ public class SegmenterDetectionGroup extends GroupDetection { */ private long segMillis; + private double timeS; + /** * Constructor for a group of detections within a detection. Note that some * longer detections (e.g. whistles) may have sections outside the segment. @@ -33,7 +35,7 @@ public class SegmenterDetectionGroup extends GroupDetection { public SegmenterDetectionGroup(long timeMilliseconds, int channelBitmap, long startSample, double duration) { super(timeMilliseconds, channelBitmap, startSample, (long) duration); this.setDurationInMilliseconds(duration); - this.segMillis =timeMilliseconds; + this.segMillis = timeMilliseconds; this.segDuration = duration; } @@ -56,5 +58,13 @@ public class SegmenterDetectionGroup extends GroupDetection { return (long) (segMillis+segDuration); } + public void setStartSecond(double timeS) { + this.timeS = timeS; + } + + public double getStartSecond() { + return timeS; + } + } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java index 3ae032a0..7012ac68 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java @@ -71,7 +71,11 @@ public class SegmenterProcess extends PamProcess { /** * The current segmenter detection group. */ - private SegmenterDetectionGroup[] segmenterDetectionGroup = null; + private SegmenterDetectionGroup[] segmenterDetectionGroup = null; + + private long segmentStart=-1; + + private long segmenterEnd=-1; public SegmenterProcess(DLControl pamControlledUnit, PamDataBlock parentDataBlock) { @@ -219,9 +223,10 @@ public class SegmenterProcess extends PamProcess { */ public void newData(PamDataUnit pamRawData) { +// System.out.println("New data for segmenter: " + pamRawData); + if (!dlControl.getDLParams().useDataSelector || dlControl.getDataSelector().scoreData(pamRawData)>0) { - //System.out.println("New data for segmenter: " + pamRawData); if (pamRawData instanceof RawDataUnit) { newRawDataUnit(pamRawData); } @@ -243,7 +248,7 @@ public class SegmenterProcess extends PamProcess { * A new detection data unit i.e. this is only if we have detection data which is being grouped into segments. * @param dataUnit - the whistle data unit. */ - private void newWhistleData(PamDataUnit dataUnit) { + private synchronized void newWhistleData(PamDataUnit dataUnit) { ConnectedRegionDataUnit whistle = (ConnectedRegionDataUnit) dataUnit; @@ -256,11 +261,16 @@ public class SegmenterProcess extends PamProcess { int index = -1; for (int i=0; i0) { + this.segmenterGroupDataBlock.addPamData(segmenterDetectionGroup[index]); + } + } + + segmenterDetectionGroup[index] = aSegment; +// System.out.println("NEW SEGMENT START!: " + (segmentStart-firstClockUpdate)/1000. + "s" + " " + segmenterDetectionGroup[index].getSegmentStartMillis()+ " " +segmenterDetectionGroup[index]); + + } + private boolean detectionInSegment(PamDataUnit dataUnit, SegmenterDetectionGroup segmenterDetectionGroup2) { return detectionInSegment(dataUnit, segmenterDetectionGroup2.getSegmentStartMillis(), (long) (segmenterDetectionGroup2.getSegmentStartMillis()+segmenterDetectionGroup2.getSegmentDuration())); @@ -308,11 +348,11 @@ public class SegmenterProcess extends PamProcess { private boolean detectionInSegment(PamDataUnit dataUnit, long segStart, long segEnd) { //TODO - this is going to fail for very small segments. long whistleStart = dataUnit.getTimeMilliseconds(); - long whistleEnd = dataUnit.getDurationInMilliseconds().longValue(); + long whistleEnd = whistleStart + dataUnit.getDurationInMilliseconds().longValue(); if ((whistleStart>=segStart && whistleStart=segStart && whistleEnd transforms = new ArrayList(); + transforms.add(new FreqTransform(DLTransformType.SPECRESIZE, new Number[] {Integer.valueOf(64), Integer.valueOf(48)})); + +// +// //set the spec transform +// ((FreqTransform) transforms.get(0)).setSpecTransfrom(whistles2Image.getSpecTransfrom()); +// +// //process all the transforms. +// DLTransform transform = modelTransforms.get(0); +// for (int i =0; ilYI#tEk4rKt+OiIoZp<_d-;R>me)1{MlN21X_m1C=Kxuud%bQ?JRyz)&&g z&6$Io4F)_87b^n3JYsvdK==hm--Si4EUPcgyK_s>=c4GIzlWX+Iz@G#$Z+A>I_3J8 zPx*3N!cK2-i{2X>V=nuTN6*b%|6p9ZtW*_x1d~S+hOeYHz*KN|8Ikr>;SOnd06xjsC@Jlv|WqTu$#WpZ==Kf?D|We|7=}#Uw!58+BEhWsr0?? zv$JZabP06W_rF~A_n!g3@5f4??GX&~k43!y|6cN0&{n4HkIpCWx~raXsZ#XTqt7$m z<%u~-C0=}fo8hx&J%gO?hPHd`OXeRBQJ?8wduE~Ov^{@)L+|Eq-~QmYrNMcpTjw2{ z#cpo3lVAG$f9CJV?)swYfaRHC%TGyeIr9AX+f|?K_cG=^*)@IM^^o+)*>ds+E`C>h zx%1IFEB3aTi{I~l?^OHGwp`Kj^}{VZa%(sJ|9)@(x38>BQmJ~p8_)mUP{w%j=ewO} z_VmB6I=OTIj4U%|M&AuDzW-wbhx<8CUIqmohJ#jz_jB+uv#3m1#W2b6=J^_PrQ51@ s8?WX}ySTaZZ@Y@;Bq%y5ZUc_6h($ delta 46 zcmbO;lkxQw#tEk48KnxoiIoZpMhb=oR;K1whK34821X_m1C=Kxuud%bvw0!&Zw~-h CE)Qw| From 6f36e5a47fa8d3c155f0af1256d33c62b1a7134d Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Thu, 9 May 2024 10:21:49 +0100 Subject: [PATCH 06/17] Fix bug in whistle detection plot Also add ability to change contour colours --- .classpath | 2 +- .../connectionNodes/ModuleConnectionNode.java | 2 + .../connectionNodes/ModuleIconFactory.java | 6 +- .../whistlePlotFX/WhistlePlotInfoFX.java | 38 +++++- src/detectionPlotFX/plots/FFTPlot.java | 36 ++++- .../plots/FFTSettingsPane.java | 13 +- src/detectionPlotFX/plots/RawFFTPlot.java | 27 ++-- .../projector/DetectionPlotProjector.java | 2 +- .../whistleDDPlot/WhistleFFTPlot.java | 47 +++---- .../whistleDDPlot/WhistlePlotParams.java | 13 ++ .../whistleDDPlot/WhistleSettingsPane.java | 123 ++++++++++-------- src/metadata/MetaDataContol.java | 15 +++ 12 files changed, 212 insertions(+), 112 deletions(-) create mode 100644 src/detectionPlotFX/whistleDDPlot/WhistlePlotParams.java diff --git a/.classpath b/.classpath index 576b210a..c623317c 100644 --- a/.classpath +++ b/.classpath @@ -6,7 +6,7 @@ - + diff --git a/src/dataModelFX/connectionNodes/ModuleConnectionNode.java b/src/dataModelFX/connectionNodes/ModuleConnectionNode.java index 1e7996b3..a182c6ec 100644 --- a/src/dataModelFX/connectionNodes/ModuleConnectionNode.java +++ b/src/dataModelFX/connectionNodes/ModuleConnectionNode.java @@ -649,6 +649,8 @@ public class ModuleConnectionNode extends StandardConnectionNode implements PAMC } + System.out.println("Add module: " + pamControlledUnit.getUnitName() + " " + pamControlledUnit.getPamModuleInfo()); + if (pamControlledUnit.getPamModuleInfo()!=null) { Node icon = ModuleIconFactory.getInstance(). getModuleNode(pamControlledUnit.getPamModuleInfo().getClassName()); diff --git a/src/dataModelFX/connectionNodes/ModuleIconFactory.java b/src/dataModelFX/connectionNodes/ModuleIconFactory.java index 86e170e4..4056f730 100644 --- a/src/dataModelFX/connectionNodes/ModuleIconFactory.java +++ b/src/dataModelFX/connectionNodes/ModuleIconFactory.java @@ -132,6 +132,7 @@ public class ModuleIconFactory { break; case TETHYS: iconNode = PamGlyphDude.createModuleIcon("file-codemeta"); + System.out.println("Get module Tethys " + iconNode); break; default: break; @@ -215,7 +216,6 @@ public class ModuleIconFactory { * @return the module icon enum */ public ModuleIcon getModuleIcon(String className) { -// System.out.println("Get module icon: " + className); ModuleIcon icon = null; switch (className) { case "Acquisition.AcquisitionControl": @@ -270,10 +270,12 @@ public class ModuleIconFactory { case "cpod.CPODControl2": icon=ModuleIcon.CPOD; break; - case "MetaDataControl": + case "Meta Data": icon = ModuleIcon.TETHYS; break; } + System.out.println("Get module icon: " + className + " icon " + icon); + return icon; } diff --git a/src/dataPlotsFX/whistlePlotFX/WhistlePlotInfoFX.java b/src/dataPlotsFX/whistlePlotFX/WhistlePlotInfoFX.java index ae3f27ba..8868afd6 100644 --- a/src/dataPlotsFX/whistlePlotFX/WhistlePlotInfoFX.java +++ b/src/dataPlotsFX/whistlePlotFX/WhistlePlotInfoFX.java @@ -184,6 +184,29 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { } } } + + /** + * Draw a whistle fragment. + * @param pamDataUnit - the PAM data unit + * @param g - the graphics handle + * @param windowRect - window describing window pixel dimensions to draw on + * @param orientation - orientation + * @param tdprojector - projector which converts pixels to time, frequency and vice versa. + * @param scrollStart - the scroll start + * @param type - type flag for plotting + * @param wmControl + * @param fftLength - the FFT length in samples + * @param fftHop - the FFT hop in samples + * @param sampleRate - the sample rate in samples per second + * @param fillCol - the fill colour + * @param linCol - the line colour. + * @param useKhz - true to pot with kHz instead of Hz + * @return a 2D path in pixels of the fragment. + */ + public static Path2D drawWhistleFragement(PamDataUnit pamDataUnit, WhistleMoanControl wmControl, int fftLength, int fftHop, float sampleRate, + GraphicsContext g, TimeProjectorFX tdprojector, double scrollStart, int type, Color fillCol, Color linCol, Orientation orientation) { + return WhistlePlotInfoFX.drawWhistleFragement(pamDataUnit, wmControl, fftLength, fftHop, sampleRate, g, tdprojector, scrollStart, type, fillCol, linCol, false, orientation); + } /** * Draw a whistle fragment. @@ -200,10 +223,11 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { * @param sampleRate - the sample rate in samples per second * @param fillCol - the fill colour * @param linCol - the line colour. + * @param useKhz - true to pot with kHz instead of Hz * @return a 2D path in pixels of the fragment. */ public static Path2D drawWhistleFragement(PamDataUnit pamDataUnit, WhistleMoanControl wmControl, int fftLength, int fftHop, float sampleRate, - GraphicsContext g, TimeProjectorFX tdprojector, double scrollStart, int type, Color fillCol, Color linCol, Orientation orientation) { + GraphicsContext g, TimeProjectorFX tdprojector, double scrollStart, int type, Color fillCol, Color linCol, boolean useKhz, Orientation orientation) { //get position on time axis long timeMillis=pamDataUnit.getTimeMilliseconds(); @@ -275,7 +299,9 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { sliceMillis += timeMillis; slicePeaks = sliceData.getnPeaks(); + tC=tdprojector.getTimePix((long) (sliceMillis-scrollStart)); + if (tC < 0 || tC > tdprojector.getGraphTimePixels()) { return null; } @@ -305,6 +331,7 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { } lastPeak = prevSlice.getPeakInfo()[lastPeakNum]; f2 = thisPeak[1] * sampleRate / fftLength; + if (useKhz) f2=f2/1000.; pt2 = new Point2D(tC, tdprojector.getCoord3d(0,f2,0).getCoordinate(1)); @@ -315,9 +342,10 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { awtPath.lineTo(pt2.getX(), pt2.getY()); } pathCount++; - // System.out.println("yAxis: "+ tdprojector.getCoord3d(0,f2,0).getCoordinate(1)+ " f2: " +f2 + " max val: "+yAxis.getMaxVal()+" "+frequencyInfo.getUnitDivisor()); +// System.out.println("yAxis: "+ tdprojector.getCoord3d(0,f2,0).getCoordinate(1)+ " f2: " +f2 ); //+yAxis.getMaxVal()+" "+frequencyInfo.getUnitDivisor()); f1=lastPeak[1] * sampleRate / fftLength; + if (useKhz) f1=f1/1000.; pt1 = new Point2D(prevtC,tdprojector.getCoord3d(0,f1,0).getCoordinate(1)); @@ -332,9 +360,10 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { pt2 = new Point2D(tC, tdprojector.getCoord3d(0,f2,0).getCoordinate(1)); f1=lastPeak[0] * sampleRate / fftLength; + if (useKhz) f1=f1/1000.; pt1 = new Point2D(prevtC, tdprojector.getCoord3d(0,f1,0).getCoordinate(1)); - + drawWhistleSegment( g, orientation, prevSliceX, pt1.getY(), sliceX, pt2.getY()); minX = Math.min(minX, pt1.getX()); @@ -347,10 +376,13 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { maxY = Math.max(maxY, pt2.getY()); f2=thisPeak[2] * sampleRate / fftLength; + if (useKhz) f2=f2/1000.; pt2 = new Point2D(tC, tdprojector.getCoord3d(0,f2,0).getCoordinate(1)); f1=lastPeak[2] * sampleRate / fftLength; + if (useKhz) f1=f1/1000.; + pt1 = new Point2D(prevtC, tdprojector.getCoord3d(0,f1,0).getCoordinate(1)); drawWhistleSegment( g, orientation, prevSliceX, pt1.getY(), sliceX, pt2.getY()); diff --git a/src/detectionPlotFX/plots/FFTPlot.java b/src/detectionPlotFX/plots/FFTPlot.java index 2a35ff8b..04116a79 100644 --- a/src/detectionPlotFX/plots/FFTPlot.java +++ b/src/detectionPlotFX/plots/FFTPlot.java @@ -20,6 +20,7 @@ import detectionPlotFX.plots.RawFFTPlot.FreqTimeProjector; import detectionPlotFX.projector.DetectionPlotProjector; import javafx.geometry.Side; import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.TextFormatter.Change; import javafx.scene.image.WritableImage; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; @@ -38,6 +39,12 @@ import javafx.scene.shape.Rectangle; */ public abstract class FFTPlot implements DetectionPlot { + /** + * Plot kHz instead of + */ + private static final double USE_KHZ_FREQ = 2000; + + /** * Reference to the detection plot display. */ @@ -63,7 +70,7 @@ public abstract class FFTPlot implements DetectionPlot /** * The FFT parameters. */ - protected FFTPlotParams fftParams = new FFTPlotParams(); + protected FFTPlotParams fftParams = createPlotParams(); private static DataTypeInfo dataTypeInfo = new DataTypeInfo(ParameterType.FREQUENCY, ParameterUnits.HZ); @@ -79,6 +86,10 @@ public abstract class FFTPlot implements DetectionPlot private FFTWriteableImage writableImage; + /** + * True if plotting as kHz instead of Hz - usually for higher frequency data greater than USE_KHZ_FREQ; + */ + private boolean useKHz = false; public FFTPlot(DetectionPlotDisplay displayPlot, DetectionPlotProjector projector) { this.detectionPlotDisplay=displayPlot; @@ -186,10 +197,11 @@ public abstract class FFTPlot implements DetectionPlot * @param freqAxis */ public void setupFreqAxis(double minFreq, double maxFreq, DetectionPlotProjector projector) { - if (maxFreq>2000) { + if (maxFreq>USE_KHZ_FREQ) { //use kHz //projector.getAxis(Side.LEFT).setLabelScale(0.001); - projector.setAxisMinMax(minFreq, maxFreq/ 1000, Side.LEFT, "Frequency (kHz)"); + this.useKHz = true; + projector.setAxisMinMax(minFreq, maxFreq/1000., Side.LEFT, "Frequency (kHz)"); } else { projector.setAxisMinMax(minFreq, maxFreq, Side.LEFT, "Frequency (Hz)"); @@ -407,7 +419,9 @@ public abstract class FFTPlot implements DetectionPlot //add settings listener to dynamic settings pane. setttingsPane.addSettingsListener(()->{ - settingsChanged(setttingsPane.getParams(new FFTPlotParams())); + //this needs to be a new instance of the the FFTPlotParams or some settings don't + //register a chnage + settingsChanged(setttingsPane.getParams(createPlotParams() )); }); // /////////*****Test Pane*******/////////// @@ -431,6 +445,14 @@ public abstract class FFTPlot implements DetectionPlot } return (Pane) setttingsPane.getContentNode(); } + + /** + * Create plot paramters for the FFT plot params + * @return + */ + public FFTPlotParams createPlotParams() { + return new FFTPlotParams(); + } public class SimpleFFTDataUnit extends DataUnit2D { @@ -537,7 +559,6 @@ public abstract class FFTPlot implements DetectionPlot * The PAM detection used for plotting */ private D pamDetection; - public FFTWriteableImage(int x, int y, FFTPlotParams fftParams, D pamDetection) { @@ -546,9 +567,12 @@ public abstract class FFTPlot implements DetectionPlot this.pamDetection=pamDetection; } - } + public boolean isUseKHz() { + return useKHz; + } + } \ No newline at end of file diff --git a/src/detectionPlotFX/plots/FFTSettingsPane.java b/src/detectionPlotFX/plots/FFTSettingsPane.java index b7038a1b..16459be0 100644 --- a/src/detectionPlotFX/plots/FFTSettingsPane.java +++ b/src/detectionPlotFX/plots/FFTSettingsPane.java @@ -349,12 +349,21 @@ public class FFTSettingsPane extends DynamicSettingsPane< } /** - * Get the VBox holder. This is the main pane whihc holds the spectrogram colour settings. + * This is the main pane which holds the spectrogram colour and FFT settings. * @return the VBox holder. */ - public PamHBox getVBoxHolder() { + public PamHBox getHolderPane() { return holder; } + + + /** + * Get the Pane which holds the FFT Settings e.. length hop etc. + * @return the pane with controls for FFT settings. + */ + public Pane getFFTPane() { + return fftPane; + } /** * Get the colour combo box which allows users to change the gradient colour diff --git a/src/detectionPlotFX/plots/RawFFTPlot.java b/src/detectionPlotFX/plots/RawFFTPlot.java index 210a789f..045e2410 100644 --- a/src/detectionPlotFX/plots/RawFFTPlot.java +++ b/src/detectionPlotFX/plots/RawFFTPlot.java @@ -133,6 +133,7 @@ public abstract class RawFFTPlot extends FFTPlot { reloadImage=true; } lastData=dataUnit; + //three threaded sequences. 1) Load the data //2) generate the image and 3), on the FX thread, paint the image, if (reloadRaw && detectionPlotDisplay.isViewer()){ @@ -142,19 +143,19 @@ public abstract class RawFFTPlot extends FFTPlot { //Otherwise variables get cleared etc and it;s a mess spectrogram.checkConfig(); - loadRawData(dataUnit, fftParams.detPadding, fftParams.plotChannel); + loadRawData(dataUnit, fftParams.detPadding, fftParams.plotChannel); //rawDataOrder.startRawDataLoad(dataUnit, fftParams.detPadding, fftParams.plotChannel); //on a different thread which will call repaint again } else if (reloadImage && detectionPlotDisplay.isViewer()){ -// System.out.println("Load IMAGE data: seconds: " + this.rawDataOrder.getRawDataObserver().getRawData().length -// + " for data unit: " + +dataUnit.getUID()); + System.out.println("Load IMAGE data: seconds: " + this.rawDataOrder.getRawDataObserver().getRawData().length + + " for data unit: " + +dataUnit.getUID()); spectrogram.checkConfig(); startImageLoad(); } else { //repaint the image!! -// System.out.println("PAINT the image for: " +dataUnit.getUID()); + System.out.println("PAINT the image for: " +dataUnit.getUID()); if (detectionPlotDisplay.isViewer()) paintSpecImage(graphicsContext, rectangle, projector); paintDetections(dataUnit, graphicsContext, rectangle, projector) ; } @@ -177,6 +178,8 @@ public abstract class RawFFTPlot extends FFTPlot { // if (pamDetection != null) { // sR = pamDetection.getParentDataBlock().getSampleRate(); // } + + //TODO - need to get this working projector.setEnableScrollBar(false); setupFreqAxis(0, sR/2, projector); @@ -305,8 +308,8 @@ public abstract class RawFFTPlot extends FFTPlot { */ protected synchronized void loadDataUnitImage(double[] rawData, float sR, int channel, long dataStart, Task task ){ - System.out.println("RawFFTPlot: Raw data to process: " + rawData.length + " bins: FFTPlot: hop: " + fftParams.fftLength + " length: " + fftParams.fftHop - + " sampleRate: " + sR); +// System.out.println("RawFFTPlot: Raw data to process: " + rawData.length + " bins: FFTPlot: hop: " + fftParams.fftLength + " length: " + fftParams.fftHop +// + " sampleRate: " + sR); this.simpleFFTBlock.setFftHop(fftParams.fftHop); this.simpleFFTBlock.setFftLength(fftParams.fftLength); @@ -480,11 +483,11 @@ public abstract class RawFFTPlot extends FFTPlot { fftPlotParams.windowFunction!=this.fftParams.windowFunction || fftPlotParams.normalise!=this.fftParams.normalise) { -// Debug.out.println("CheckSettings: Image needs relaoded: FFTLength: " -// + (fftPlotParams.fftLength!=this.fftParams.fftLength) + " FFTHop: " -// + (fftPlotParams.fftHop!=this.fftParams.fftHop) + " WindowFunction: " -// + (fftPlotParams.windowFunction!=this.fftParams.windowFunction) + " Normalise: " -// + (fftPlotParams.normalise!=this.fftParams.normalise) + " "); + System.out.println("CheckSettings: Image needs relaoded: FFTLength: " + + (fftPlotParams.fftLength!=this.fftParams.fftLength) + " FFTHop: " + + (fftPlotParams.fftHop!=this.fftParams.fftHop) + " WindowFunction: " + + (fftPlotParams.windowFunction!=this.fftParams.windowFunction) + " Normalise: " + + (fftPlotParams.normalise!=this.fftParams.normalise) + " "); this.reloadImage=true; } @@ -682,6 +685,8 @@ public abstract class RawFFTPlot extends FFTPlot { @Override public Coordinate3d getCoord3d(double d1, double d2, double d3) { +// PamAxisFX axis = projector.getAxis(Side.LEFT); +// System.out.println("d2 : " + d2 + " minmax: " + axis.getMinVal() + " " + axis.getMaxVal()); return projector.getCoord3d(d1,d2,d3); } diff --git a/src/detectionPlotFX/projector/DetectionPlotProjector.java b/src/detectionPlotFX/projector/DetectionPlotProjector.java index 40a5c6ff..960f61d1 100644 --- a/src/detectionPlotFX/projector/DetectionPlotProjector.java +++ b/src/detectionPlotFX/projector/DetectionPlotProjector.java @@ -35,7 +35,7 @@ public class DetectionPlotProjector extends GeneralProjector { /** * True to enable the scroll bar. */ - public boolean enableScrollBar = false; + public boolean enableScrollBar = true; /** * Projector for the ddPlotPane. diff --git a/src/detectionPlotFX/whistleDDPlot/WhistleFFTPlot.java b/src/detectionPlotFX/whistleDDPlot/WhistleFFTPlot.java index c58c1de2..c7a1895f 100644 --- a/src/detectionPlotFX/whistleDDPlot/WhistleFFTPlot.java +++ b/src/detectionPlotFX/whistleDDPlot/WhistleFFTPlot.java @@ -1,22 +1,21 @@ package detectionPlotFX.whistleDDPlot; -import PamguardMVC.debug.Debug; import dataPlotsFX.whistlePlotFX.WhistlePlotInfoFX; import detectionPlotFX.layout.DetectionPlotDisplay; -import detectionPlotFX.plots.RawFFTPlot; +import detectionPlotFX.plots.FFTPlotParams; import detectionPlotFX.plots.FFTSettingsPane; +import detectionPlotFX.plots.RawFFTPlot; import detectionPlotFX.projector.DetectionPlotProjector; import javafx.geometry.Orientation; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; -import pamViewFX.fxNodes.pamAxis.PamAxisFX; import whistlesAndMoans.ConnectedRegionDataUnit; import whistlesAndMoans.WhistleMoanControl; /** - * Plots a whistle contour over. + * Plots a whistle contour over a spectrgram if one is available. * @author Jamie Macaulay * */ @@ -27,21 +26,6 @@ public class WhistleFFTPlot extends RawFFTPlot { */ private WhistleMoanControl whistleMoanControl; - /** - * Line colour - */ - private Color lineColor=Color.BLACK; - - /** - * The fill colour - */ - private Color fillColor=Color.BLACK; - - /** - * The whislte settings pane. - */ - private WhistleSettingsPane setttingsPane; - /** * The whistle FFT plot * @param displayPlot - the display plot. @@ -56,7 +40,9 @@ public class WhistleFFTPlot extends RawFFTPlot { public void paintDetections(ConnectedRegionDataUnit whistleDataUnit, GraphicsContext graphicsContext, Rectangle windowRect, DetectionPlotProjector projector) { -// Debug.out.println("Draw whistle fragment: " + whistleDataUnit + " sR: "+ whistleDataUnit.getParentDataBlock().getSampleRate() + " Scroll start: " + getScrollStart()); + +// System.out.println("Draw whistle fragment: " + whistleDataUnit + " sR: "+ whistleDataUnit.getParentDataBlock().getSampleRate() + " Scroll start: " + getScrollStart()); + WhistlePlotInfoFX.drawWhistleFragement(whistleDataUnit, whistleMoanControl, //need to have fft which was used in making the detections @@ -64,20 +50,23 @@ public class WhistleFFTPlot extends RawFFTPlot { whistleMoanControl.getWhistleToneProcess().getOutputData().getFftHop(), whistleDataUnit.getParentDataBlock().getSampleRate(), //need to use this because FFT sample rate can be unreliable graphicsContext, - super.getProjector(), getScrollStart(), 0, fillColor, lineColor, Orientation.HORIZONTAL); + super.getProjector(), getScrollStart(), 0, getContourColor(), getContourColor(), this.isUseKHz(), Orientation.HORIZONTAL); } - @Override - public Pane getSettingsPane() { - return super.getSettingsPane(); -// if (setttingsPane==null){ -// setttingsPane= new WhistleSettingsPane(whistleMoanControl, this); -// setttingsPane.setParams(super.getFFTParams()) ; -// } -// return (Pane) setttingsPane.getContentNode(); + private Color getContourColor() { + return ((WhistlePlotParams) this.getFFTParams()).contourColor; } + @Override + protected FFTSettingsPane createSettingsPane(){ + return new WhistleSettingsPane(null, this); + } + + @Override + public FFTPlotParams createPlotParams() { + return new WhistlePlotParams(); + } } diff --git a/src/detectionPlotFX/whistleDDPlot/WhistlePlotParams.java b/src/detectionPlotFX/whistleDDPlot/WhistlePlotParams.java new file mode 100644 index 00000000..a9593ee7 --- /dev/null +++ b/src/detectionPlotFX/whistleDDPlot/WhistlePlotParams.java @@ -0,0 +1,13 @@ +package detectionPlotFX.whistleDDPlot; + + +import detectionPlotFX.plots.FFTPlotParams; +import javafx.scene.paint.Color; + +public class WhistlePlotParams extends FFTPlotParams { + + private static final long serialVersionUID = 1L; + + Color contourColor = Color.GRAY; + +} diff --git a/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java b/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java index bf8abd54..0340865e 100644 --- a/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java +++ b/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java @@ -1,20 +1,11 @@ package detectionPlotFX.whistleDDPlot; import detectionPlotFX.plots.RawFFTPlot; +import detectionPlotFX.plots.FFTPlotParams; import detectionPlotFX.plots.FFTSettingsPane; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.geometry.Pos; import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; -import javafx.scene.control.Slider; -import javafx.scene.control.Spinner; -import javafx.scene.control.Tooltip; import javafx.scene.layout.Pane; -import pamViewFX.PamGuiManagerFX; -import pamViewFX.fxGlyphs.PamGlyphDude; -import pamViewFX.fxNodes.PamButton; -import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamSpinner; import pamViewFX.fxNodes.PamVBox; import whistlesAndMoans.ConnectedRegionDataUnit; @@ -27,24 +18,18 @@ import whistlesAndMoans.ConnectedRegionDataUnit; */ public class WhistleSettingsPane extends FFTSettingsPane { - //TODO - neeed to complete this class; + //TODO - need to complete this class; + /** * Allows fragments colours. */ private ColorPicker colorPicker; - /** - * The time buffer control. Controls the time shown before and after the whislte. - */ - private PamSpinner timeBuffer; - - private PamSpinner fftSpinnerLength; - public WhistleSettingsPane(Object owner, RawFFTPlot fftPlot) { super(owner, fftPlot); //add whistle fragment to bottom; - //super.getVBoxHolder().getChildren().add(createWhistlePane()); + super.getFFTPane().getChildren().add(createWhistlePane()); } @@ -54,55 +39,79 @@ public class WhistleSettingsPane extends FFTSettingsPane(0, 500, 1, 0.2); - timeBuffer.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + colorPicker.valueProperty().addListener((obsval, oldVal, newVal)->{ + newSettings(); + }); - //slider for changing the FFT length. - Label fftSpinnerLabel = new Label("FFT Length"); - ObservableList stepSizeListLength=FXCollections.observableArrayList(); - for (int i=2; i<15; i++){ - stepSizeListLength.add((int) Math.pow(2,i)); - } - fftSpinnerLength=new PamSpinner(stepSizeListLength); - fftSpinnerLength.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); - fftSpinnerLength.setEditable(true); - - //Slider for changing hop size - Label windowLengthLabel = new Label("Window Length"); - PamButton pamButton = new PamButton(); -// pamButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.ADJUST, PamGuiManagerFX.iconSize)); - pamButton.setGraphic(PamGlyphDude.createPamIcon("mdi2a-adjust", PamGuiManagerFX.iconSize)); - pamButton.setTooltip(new Tooltip("Optimise the window length based on the average frequency slope of the signal")); - - PamHBox windowLengthHBox= new PamHBox(windowLengthLabel, pamButton); - windowLengthHBox.setAlignment(Pos.CENTER_LEFT); - windowLengthHBox.setSpacing(5); - - Slider windowSizeSlider= new Slider(); - windowSizeSlider.setMax(8192); - windowSizeSlider.setMin(8); - - windowSizeSlider.setShowTickLabels(true); - windowSizeSlider.setShowTickMarks(true); - windowSizeSlider.setMajorTickUnit(1024); - windowSizeSlider.setMinorTickCount(0); //disable minor tick marks +// //buffer +// Label timeBufferLabel = new Label("Time Buffer"); +// timeBuffer = new PamSpinner(0, 500, 1, 0.2); +// timeBuffer.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); +// +// //slider for changing the FFT length. +// Label fftSpinnerLabel = new Label("FFT Length"); +// ObservableList stepSizeListLength=FXCollections.observableArrayList(); +// for (int i=2; i<15; i++){ +// stepSizeListLength.add((int) Math.pow(2,i)); +// } +// fftSpinnerLength=new PamSpinner(stepSizeListLength); +// fftSpinnerLength.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); +// fftSpinnerLength.setEditable(true); +// +// //Slider for changing hop size +// Label windowLengthLabel = new Label("Window Length"); +// PamButton pamButton = new PamButton(); +//// pamButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.ADJUST, PamGuiManagerFX.iconSize)); +// pamButton.setGraphic(PamGlyphDude.createPamIcon("mdi2a-adjust", PamGuiManagerFX.iconSize)); +// pamButton.setTooltip(new Tooltip("Optimise the window length based on the average frequency slope of the signal")); +// +// PamHBox windowLengthHBox= new PamHBox(windowLengthLabel, pamButton); +// windowLengthHBox.setAlignment(Pos.CENTER_LEFT); +// windowLengthHBox.setSpacing(5); +// +// Slider windowSizeSlider= new Slider(); +// windowSizeSlider.setMax(8192); +// windowSizeSlider.setMin(8); +// +// windowSizeSlider.setShowTickLabels(true); +// windowSizeSlider.setShowTickMarks(true); +// windowSizeSlider.setMajorTickUnit(1024); +// windowSizeSlider.setMinorTickCount(0); //disable minor tick marks - PamVBox pamVBox = new PamVBox(contourColourLabel, colorPicker, timeBufferLabel, timeBuffer, - fftSpinnerLabel, fftSpinnerLength, windowLengthHBox, windowSizeSlider); + PamVBox pamVBox = new PamVBox(contourColourLabel, colorPicker); pamVBox.setSpacing(7); return pamVBox; } + + + /** + * Get the params from the current settings of the controls. + * @param wignerParams - the params to set. + * @return the new FFT parameters + */ + @Override + public FFTPlotParams getParams(FFTPlotParams fftPlotParams) { + ((WhistlePlotParams) fftPlotParams).contourColor = this.colorPicker.getValue(); + return super.getParams(fftPlotParams); + + } + + + @Override + public void setParams(FFTPlotParams input) { + this.colorPicker.setValue(((WhistlePlotParams) input).contourColor); + super.setParams(input); + + } } diff --git a/src/metadata/MetaDataContol.java b/src/metadata/MetaDataContol.java index 98093b08..8fc66ee5 100644 --- a/src/metadata/MetaDataContol.java +++ b/src/metadata/MetaDataContol.java @@ -7,11 +7,14 @@ import java.io.Serializable; import javax.swing.JFrame; import javax.swing.JMenuItem; +import Array.ArrayManager; import PamController.PamControlledUnit; import PamController.PamControlledUnitSettings; import PamController.PamController; +import PamController.PamGUIManager; import PamController.PamSettingManager; import PamController.PamSettings; +import PamModel.PamModuleInfo; import metadata.swing.MetaDataDialog; /** @@ -44,6 +47,7 @@ public class MetaDataContol extends PamControlledUnit implements PamSettings { public static MetaDataContol getMetaDataControl() { if (singleInstance == null) { singleInstance = new MetaDataContol(unitType); + singleInstance.addModuleInfo(); //needed for FX // add this line to add it to the main modules list. Then it will get menu's, etc. PamController.getInstance().addControlledUnit(singleInstance); } @@ -108,6 +112,17 @@ public class MetaDataContol extends PamControlledUnit implements PamSettings { // send around a notification ? } } + + /** + * Add module info to the array manager. Need to do this to add icon which is used in data model. + */ + private void addModuleInfo(){ + //need to add module info due to fact array manager is a special case + PamModuleInfo metaModuleInfo=new PamModuleInfo(unitType, "Handles project metadata", MetaDataContol.class); + metaModuleInfo.setCoreModule(true); + metaModuleInfo.addGUICompatabilityFlag(PamGUIManager.FX); + this.setPamModuleInfo(metaModuleInfo); + } From 822832f1794fbe4e41994684234c972c36f47877 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Fri, 10 May 2024 08:50:12 +0100 Subject: [PATCH 07/17] Updates to DelphinID --- .classpath | 2 +- pom.xml | 2 +- .../delphinID/DelphinIDTest.java | 8 +- .../delphinID/DelphinIDWorker.java | 13 +- .../delphinID/Whistles2Image.java | 4 +- .../DelphinIDTest.java | 201 +++++++++++++++--- .../DelphinID/whistle_image_example.mat | Bin 19612 -> 38377 bytes 7 files changed, 195 insertions(+), 35 deletions(-) diff --git a/.classpath b/.classpath index c623317c..576b210a 100644 --- a/.classpath +++ b/.classpath @@ -6,7 +6,7 @@ - + diff --git a/pom.xml b/pom.xml index ca91aa43..00c3451e 100644 --- a/pom.xml +++ b/pom.xml @@ -310,7 +310,7 @@ io.github.macster110 jdl4pam - 0.0.98 + 0.0.99 diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java index c2450f91..64d51211 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -228,7 +228,7 @@ public class DelphinIDTest { String whistleContourPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_contours.mat"; //the path to the model - String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_4s_encounter415.zip"; + String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_2/whistle_4s_415.zip"; //the path to the model String matImageSave = "C:/Users/Jamie Macaulay/MATLAB Drive/MATLAB/PAMGUARD/deep_learning/delphinID/whistleimages.mat"; @@ -240,7 +240,7 @@ public class DelphinIDTest { ArrayList whistleContours = getWhistleContoursMAT(whistleContourPath); //segment the whistle detections - ArrayList segments = segmentWhsitleData(whistleContours, dataStartMillis, + ArrayList segments = segmentWhsitleData(whistleContours, (long) (dataStartMillis+(9.565*1000.)), segLen, segHop); for (int i=0; i points = whistContours2Points(whistleGroup); //does not work becaue it has to be on the AWT thread. - BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getSegmentDuration()/1000.}, freqLimits, 5.); + BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getSegmentDuration()/1000.}, freqLimits, 10.); double[][] imaged = new double[(int) size[0]][(int) size[1]]; diff --git a/src/test/rawDeepLearningClassifier/DelphinIDTest.java b/src/test/rawDeepLearningClassifier/DelphinIDTest.java index 158e5fc6..f0a9cbc4 100644 --- a/src/test/rawDeepLearningClassifier/DelphinIDTest.java +++ b/src/test/rawDeepLearningClassifier/DelphinIDTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.awt.image.BufferedImage; import java.awt.image.Raster; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -14,8 +15,15 @@ import org.jamdev.jdl4pam.transforms.FreqTransform; import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; import org.jamdev.jdl4pam.utils.DLMatFile; import org.jamdev.jdl4pam.utils.DLUtils; +import org.jamdev.jpamutils.JamArr; +import org.jamdev.jpamutils.interpolation.Bicubic; +import org.jamdev.jpamutils.interpolation.Bilinear; +import org.jamdev.jpamutils.interpolation.NearestNeighbor; +import org.jamdev.jpamutils.spectrogram.SpecTransform; import org.junit.jupiter.api.Test; +import ai.djl.MalformedModelException; +import rawDeepLearningClassifier.dlClassification.archiveModel.SimpleArchiveModel; import rawDeepLearningClassifier.dlClassification.delphinID.Whistles2Image; import us.hebi.matlab.mat.format.Mat5; import us.hebi.matlab.mat.types.MatFile; @@ -24,6 +32,55 @@ import us.hebi.matlab.mat.types.Matrix; public class DelphinIDTest { + private static double[][] whistleScatter2Image(double[][] whistleValues) { + + //now perform the image transform in Java + double[] freqLimits = new double[] {2000., 20000.}; + double[] size = new double[] {680., 480.}; + + ArrayList whistleImageArr = new ArrayList(); + whistleImageArr.add(whistleValues); + + BufferedImage canvas = Whistles2Image.makeScatterImage(whistleImageArr, size, new double[]{50, 50. + 4.}, freqLimits, 6.); + + double[][] imaged = new double[(int) size[0]][(int) size[1]]; + + float[] color = new float[3]; + Raster raster = canvas.getData(); + for (int i=0; i modelTransforms = new ArrayList(); + modelTransforms.add(new FreqTransform(DLTransformType.SPECFLIP)); +// modelTransforms.add(new FreqTransform(DLTransformType.SPECNORMALISE_MINIMAX)); + modelTransforms.add(new FreqTransform(DLTransformType.SPECRESIZE, new Number[] {Integer.valueOf(60), Integer.valueOf(80), SpecTransform.RESIZE_BICUBIC})); + modelTransforms.add(new FreqTransform(DLTransformType.GAUSSIAN_FILTER, new Number[] {Double.valueOf(0.5)})); + + + SpecTransform specTransform = new SpecTransform(); + specTransform.setSpecData(imaged); + specTransform.setSampleRate((float) (freqLimits[1]*2)); + + + //set the spec transform + ((FreqTransform) modelTransforms.get(0)).setSpecTransfrom(specTransform); + + //process all the transforms. + DLTransform transform = modelTransforms.get(0); + for (int i =0; i whistleImageArr = new ArrayList(); whistleImageArr.add(whistleValues); - BufferedImage canvas = Whistles2Image.makeScatterImage(whistleImageArr, size, new double[]{48, 48. + 4.}, freqLimits, 5.); + BufferedImage canvas = Whistles2Image.makeScatterImage(whistleImageArr, size, new double[]{50, 50. + 4.}, freqLimits, 10.); double[][] imaged = new double[(int) size[0]][(int) size[1]]; @@ -67,33 +124,53 @@ public class DelphinIDTest { Raster raster = canvas.getData(); for (int i=0; i transforms = new ArrayList(); - transforms.add(new FreqTransform(DLTransformType.SPECRESIZE, new Number[] {Integer.valueOf(64), Integer.valueOf(48)})); + //create the model transforms + ArrayList modelTransforms = new ArrayList(); + modelTransforms.add(new FreqTransform(DLTransformType.SPECFLIP)); +// modelTransforms.add(new FreqTransform(DLTransformType.SPECNORMALISE_MINIMAX)); + modelTransforms.add(new FreqTransform(DLTransformType.SPECRESIZE, new Number[] {Integer.valueOf(60), Integer.valueOf(80), SpecTransform.RESIZE_BICUBIC})); + modelTransforms.add(new FreqTransform(DLTransformType.GAUSSIAN_FILTER, new Number[] {Double.valueOf(0.5)})); + -// -// //set the spec transform -// ((FreqTransform) transforms.get(0)).setSpecTransfrom(whistles2Image.getSpecTransfrom()); + SpecTransform specTransform = new SpecTransform(); + specTransform.setSpecData(imaged); + specTransform.setSampleRate((float) (freqLimits[1]*2)); + + + //set the spec transform + ((FreqTransform) modelTransforms.get(0)).setSpecTransfrom(specTransform); + + //process all the transforms. + DLTransform transform = modelTransforms.get(0); + for (int i =0; i>6SbfM zT)UYEWR0(^jK`j4b@i^Sb)~4~iM%EkA@6EC^uY-SD6=6Cw*&1#Ekdu~0EZOwA&+ zYU%cO&k_u8uilm{=Jerf3<;n3%P+K&?dSFF37mxK%^BJ;_oW;L>n%VGhfq?FlWi8# zdreBe7hgh1r~1~c7dLJLJO^kTd!{_te(cp&eeU6rotA|FkmY>lLWDsr!@pkpdO!LXJSZ7jQT1 zy@2=n+mU?PPy?2~D4njrqc7__z-iLyNS0~L#)R?xqWbYc^Y9#jlx#V=ijrf2GES$@ zJc$GJW`{c_NMz} zVT=iWe=5yX2+Jz87|TlY*eXNH(#`X7LX*-FCvEu3#eqD^k~wEZyWWOye8&UXXXIRU4VK)(#o8Y>*v$X-2jTOP5-mbLb^SE9@T#L#(3Foi&~VL2 zMaV>%D_VpCrBU#svG#P{R9+DO2>YF2mA_&L%OdcvG^HXp^-Hz4uNBbr43rS8JK8^m zUj5#jLbop$V=fBz_&Agoae?LR51KwrnQg7s$wYtMrC;WqlqM&rQyUOQql`GIKIUlI zX~`s81J;xKy*Uy%<{9bTIyZzddb?leBwe=%*6p$kOv;Ph?w(@>1Hf&kO|uYZhs940 zX*sn0$i&1Q$nG9*G#nPshN^Qnry?hvM=%!s6-LDBsl$hUq19fqTbZbsz5|WTU$@TmUmY|8DPED(Rp0{-~(lp6A4gjl}_>H9dvY|!X9Zy z6BEOc8rhb7d0vV=5~OI+X@L+j6ZKcCECHe`W^=HRvM*hbhFDc-xchFbVvt2?ybi|s}L>~rmzgryZ*)v4g4DL!FVyZnGqh}XUDXG&nwsZHd+qwxq zQqa9Idk{Vvb2*Z)7^dWivRb%&aVLw-YLqJapd|b7-|tL4icT0s?OUP*fk-aIGPmk^ z6(!91F~@VRuT$$k=1gLn@m|`^f6(h({?)y|yOgDw7P&VB9pe8f-e8!A0dIef2K}!i z<1XJGoj2p@u&?W*HvB*8;1%Qv9;Dmp2;j1fa8Th6&lVb4A@H+U*}fV}Qx#1qG@|gc zxY@p;OG7P4FVF7+%Ye+TW5W*G$7V+QT*!3r-!Sa>uWxUU2L1pcd?_F8(j2x>v@R42 z06*$HMsUo8y%f8q9Oz;y< z)bqRB=YUp-4~wG0Uh)>@YsOVeRvKEoDclkUs%-2RA)}kZ?xP$6sqf*Z^RsdMT|D9vfOVO{m<`3XrB2>E3iZv@oY*>jE&jJP2NPVn$mqQtgh7|agF>lIXQQ52k= zotZp4)M{|rdYHR!Ld9uT-C$vOt!uKoetk|_O&g!$gq=XmXc(LU_}Rp4-&cp5eRG!-M6xO>qa0>L z9LLAU99Asw459EZl%Gi#Hl<|c1jZH>75xy`eXk=4 z>T4BkxxGQ4_6|3j&qNfl-A#8~2DTs2y`&)|!l?#M z0=A}Od~__VbV*TBR-}1%JP3BbMp=yiLWowFsY?UyhvoXZ+9~e3Z&g!Lrv5~LpUX5| z229;NnQ>N0K^8eJN`LK&RY?W*_wPyNjHDu8ZcQ>$d64Ozm4aFI>l_A-RL^pczmIZo zw9TUwx!M-g{?wVvE#kX1%Sh!#rh`qNd^@Y7Q}dl`Xr`O|Oic=3c9Cl4}s*g`3Fx zH?Lyd)=$5)VpN$(Kc8q{*W>?xFLPq$QDVOQAG|HowxeiRaZWM zy|ByYwyn4X4YCNJF`@x$ReW4@f@YLbxHr$R9U+_=0thb=kcB_pfINL+nnA*eoQH0f z_|9L>Jn^s?c0X2G%=ysOJeZo!-LyN~<(AmuPdJQ~|BztExR`>u<;2!PI>Ib_w(ri8 zcmbde(A1O?dv;I?yC1JC=H)OWMye(tS4e$JWbrE;hRF9swj!e@F0b=O^x?){hk2Ul zIIW|LC%_1vl0dRLpgk)BQ`$N;Z8OQ^1yYI`mQ-&1iTben-<8EshAj|xhWB>4$yb`*l||A*ygjdvTr>3fk^jO$TD)%#k1o{=O_2yc z!D3j_%SfTn7=0=(DzZtjEdH6dQ2#;Vt||z2zgJld>jJ#j0k9wjhIowaH&<4uN&0bD zgvX|oXT?^9*OmrEc7LT3%q{l1@fzySNkEu|LW9tu9T1>^;Z3IBba#{*C%4ODtd!8wv<%H@jG`ug36_V_1;{4#&@DZcg^If|?pLGKu=c={)MswU4My zG(Wq;Q>b&Oe@1);i}>8yXDskRy_^r^4#G!8;kLNRo%pFa#yZn zA#{6w53iP>a=E@Nas?R~ez#$*VB5u0XEKC)UuU({6M7y1Zm};oABm2J>n{5l9D6%C z^e~9F4h`c)?^y$8jCiqVEKkmJgML|p^0F@0u!<9u_+9EVONC-C$|1E)Q_AiWM8K9^x&#XIjsuf{Y zJll6RV?m2Z523G*UaLy<1_B*CsJlZEQRMs%TU)XA5%<({7mQKmA^kkAeGE5WCyI%Q zkv=>5%hsHjpaqT+ev{2)%!``L96vN>e)gQ$G5;)@d*=7(k(H2y!Zl*zV^pV4y*{mEAjWI*$Y~urXDo0b=OK6lOPn418&56k)54k9 zymL&|^y6EIEZ#=IFaY{?HPY_RAjaRytGL(z>(n_n8?R=4+_+w1a2{qZifc=w>!Bpq zyW_p`Jafx{Qm;G|9)veLMr>@shG*s(B+x&QTkIpoVxwM|8T0e=YrD~aaiPj1b&pE> zJ*Jjs94k;-(#97b1)e#yd${|` zu=`2MVzy9X6C^rkyC%l3)m!C}q0b4`9ub?*vDooZcuqe=@0Ee;xgmZn^~|<*jUJgr z5Y%1!>5lc~F1)gM8xE7?=i>vpAxV`Xf`gJwCE`aF+xveMSN^}>+W$|M{S(l-IaMSA zG0)IvN49w3t6q~6bE_MWui(a};+ zQBi%EKl2FxJ-IrGzVllQ5Rf^SN)G1N~f<=rRzS(yR-!3JP+Q9F}~-qJO6R zoVXEPfR`8@9qoXxp2+l8m`ZE8;TpDk1&8BXdcq;m(aDH@3u&Evt7pJ*zBT2&fu-)pDbbP;+&tRrlvXT3Lj~HcEu^!yo%8PdI}Fm62pzdv=+#($N7rQDeL1+ z2f#>I$Lb8HfLo=L$2(tm>c<>xucwXA{$xeoqk{EIY5JaKxXH~x@V{1$D)hx;e>^`% zlXStZEE+m4@zHS!3DGKS3Kr=?ZtD|%E^~Alq;*Y2(w+U|&7bG| zU}?#q9w7nh5qzh@3d0-$Xt8U)Zd!`cIyw>>z;5oQ>$_{HSA%j@+(YnCIt0q;^W^$^ z-uM9kO$AOSqxls>2j z9-m4=)RsL=VVLP!xG*f$+`-*7=kNg$-1u`*ktVkjxBB|nSkydCCe~jWMOj(v8ud{0 z;6~0vT(ps|;^ozdkAkVG`Dps_=f^XEZtj4Vr%hD~>FcGr%mB_@o~!M@z7_0j-)U&n z75YiWrVKqfhgpc>)L?yT@dPak^5}Z}txS}7;zvXEZ_me|YB)ugfqiiTl6~_fsxh;O z$#d-utPskeKR}+A=6*31E!tog_UD$M7PKmV>F3q$ zC6pe{QvAbEw+@)g_j4Jb%iy^a+WBpBW0Z=@* zzQ^w{xJZtSWhVNWGGeGZfJP2iOY2P5hvEKTUAobbf1yHi+)sdtDspI=mi<)-^DjfZ z0f|NJeFOo5gUa(FE@lE*{~>zedgfRhU@Fe9r7~&15B78Er^^tin>EUyLkl&g=La+T zAk1>#OUEvuwg!d{L5?v&pY%S0DexCHTx2@MLy#(KQn`EV!t;w!I4hH$z7XOeg8T48 z$CnxXdj^A+Vue|T{yjaitKRzSUs2{TSnKl;;J_YM$r)mgca1V$K^fyc{bAMX5`(Ks z5ufSwbLpkacz&UoNrO!ED|SSf^Zo>`XApGrJbx=A?fij<27X_Ra2tc=<#=&Q=+&)yGRuq$^S};?PAe_lAe{7b&Y!% z9a;xW;!XCEV2XIjGdAx4FZfx)Y+vbTuUfMr@qN&$-bg=J5KAM6G7TqW)^O>=vKIlA zPf3h?E{!JIYDZdCt_vu)cY#kDHXm!3?IJvlZ*1G1MKTQYCM3qN0 zu)0k>JCE0w%(_C(PAM;3Vg$Wvo7D;M+d^TY52X>@-(ZPQ>;y4&s5V~a7XOBzAtR9 zlDzogepa<^`ZMtT=q!2RWYLcDqKt3OX7<2YJqHao#Xeg^z`MPzIp6C;$mdoyOnl-( zqoN3QtSHBo;eCA~_Z2Uo6gjUGa_9KNU0%mIPByQhXGP$I&(W zbJVjqI71BLY5VH>Q)tG9E3v=eZbuw5^xe*$1MbU>2HuGFnGzyB)YOn2v)|h zl$3qjOg6%jMjfrwd(qj~k2rnGV@EgtJx*5Cc}%=5&P%3bCia@>iS3fa6SC_=+Q)&M z`ZtgtdgrJ3!3L$#6lU*u!n5kP7*zh6^hS11m%TJwE?05Ei={68@bOJ35h0ScRx}eE zofX@ntNic!8>B(L9N;E2nQ-Mi2ul0%>L&5;fqZh`vQsRN8xDHkyYF90a-|%tnmO;f z+h}%$4n3|-Zeedc|M!+9}6Erx`{dkB8HsbNrU3puk?`;Yl? zA(y)9-_gS+vCae;RCwH|Chdjzc^eGzrc&y-x|TqST^6yC+nFQpFspLeHN<2LTJB#B z_GfX^wyuJUv$mE$i$Cz`h#j2}@AX$~0;AIET<9ob(A^XoEkm+$Y6zjIZSJo#Ms zve5znmk%Jqu0F)cH0p0J!atDQ z^v(ygx!E^=5KI8%RlQ|9%hD0O@=oA^V$m=xD$my!3CNnnjAoWem_B@8znK=&u?BL*8BUdY)>y5 zT`lFz%mBO$eV5L_?+!q~Tv=7QQbkO+!v-m-y}}R(`Rw;niF;#9Z(lU z)9t0Y%TxMdqt@PqPG17CvEuUyk#IZ<#n(i05`BG{1k4UKn(M2G2qz>8`OtaZ0X#n! zu;$d{D9u+)=kMSdd%GMt#AQdjWWe$9ZHyC=coR#Ia_wSR+9<`>&kL;IFt!mXq2((1 zF@y7eKj`@X(Utq{?DvR$Klxz}?~?)j`b-4&S=dt2{S*wF!_yQDS_d0uz%u?%$>EQF zn8L`X(xnk{Nw8Jm*P|2|5B#`>UGJFa8n>VJbcT>*(Qrxr56?>LST<5*X zTmA@*ac7-vaO%@SF&duwpSIP2z{TT&&j$L|a1;D@tdt+}pJ@BAuoa9lPWO>E0E=A$ zxiJ< z3x8u?lA+IuRp}L-EMNja244s3zA?`pd`+h{(z@w`sujGBPrQ5(e->5NFo;qZv?5HL zdEC(v1AHrcf0Zo#`lkV1PCeb{q-4_s#jmfsTsk+VJu5MHaZ#cwe;+hM-s@u76+O$L z-N?S_UreFzd;1$%skrR2-2*b1NNIhBRVnaJr?jqWr_pQH?i-`E`*EWj^v0WO_dFj; zpQid)-Y7$7-dkWouGdKj*vPeq#3a68z`g`!=P9Ogw*Mo2{sO5#d=ZK0YM{^dG${~m z0V1li%SiG53OgTBkl{6_w?kfTW>D}UgI4rdxVLUo^G&SBJb|a>`HhZ#0%luCV@?6| z3>4{{r10Am^TdvJtZ3(xoeqW~ZG7vUZku+cd%vh$OUe^H5y%#5@>(?KG`KbZEWQD zQW93ASflVZ2VE5|K}NZ4w!k6E8F6*ZXqFZEM+UZZe(*=Yu!M5eD3-u>S4whMM^kWS za?QwlkF6a2Dpgd+%9ZIBkXK0p*LtfPU=zls0k}5@cbI(kpk3X3AE=csvN*f0CeGAq)+xp;q#i7yWe2&~EG)QNojc-?RGGRSPE8%zPod2?&hNr6 zr(+|%;O!=n&I@JlB`;$cv|YGvXs1>`JYpeD@=jY!^YXyuZW0QsC~o{?%{PtT;PI~w zvu0OFkCNqA>=eWK{fW$vq{|)qH1)Z%@+TS?rxzWB7E##P9h6^#zp}w&{TU35#sMW*Pvi1`67KOUd2|dlXr#8*c<4TU6!kN zk=9Yv(D6f{Ev1)cRuqhh!axQhR+c(RiD=A2o?gU8PtgrHjifV{)&Jo0F zDD1AbWaqD`6hcJ9*2<=C)u+v1@)BJ<+dXqh*Ddhx3Z}lt7rGr+t?79eBeEM-7MLDJ zzQ991&oR6prb~!up!{U#uTa8@*T^-EbBe{OmtJZjWcr74f|<97l+_y?NRv;O=6fMy zPMV|*A+kaSv0?@Z&V<&k>nq2ypl_}tUp28(Gl3{i6;&`@i{3uQ!83gEt`InKn8J6Tr?TkO!{~@q~AuO%UMa_hRYucR8num#3AmsO6o8-07Rr ztSBo2@)cTDF|Q^`sPjw0j9r7)*xIcSFG4aG|A=k)jT>`B4Pa((J5L^+yl!tKEMM{GjV?5e5xPF~+N(#0iQd}FNoym%tL zacaKUgTTn(=Xca?k3f37&fKuC<6fFpkU-3O;~yWj&*lpR3_FDimsq5FFRrN7X9}Mx zfqu+pk(jGj^UD1&`L^{znUD8xz?D7mZ!WpjZ1J>ZEh?mmjKxCX{%pt}p6gcYRFKK_ zmG{ZzuPb3`ZDkNhG6iSk3$5G27IRPM>T9@Wd&rewt$XWZhGOcyER6B1-$(rZDw#hQ z+N%%eGmBlfQKc_2xV}+@B;mvsa&$`pVyNrNMiq^YJ$|^tNIAr@q=7a%Q91h$4@ie9 zy&oX~>fA;7y=(Cf-%j!}nC_xjo|^}HQFeJQpj*A77c6#ipiGMH1yiBS8Ln=(A_-1ePg)Ub+mcoK7Fv0uLAwM|f z54*0wy}59QPj>LrnXP!oX2I!+ZrvggBfHp&vblsVg~0S=HAle{hH;RF&*D@%*u&Ku zzjk;ZGZgM*$$$>Ek;8DTR9({61=gD^yMo zvv@v4L|vx6YoVt5P8DI}t#vSbT#gKvGCQU6_n%AH@BBSd$F&xN95Lv)P>F?Fd(!*( zH%a*_c@g_@{2t`6!z!GCC<@$oNl28T!7Z%Tn;WLEL#Z%C(2V`t{;TbdaR5u1ls=Y%yn}=KZU*7HllOd#iCD+KUQ6Eo_FP_F=;Hf{)UYr8m#sI&@Oc473OGvoGQ!?ey7~W zo50Onq)L3vB?;;NdLTM73RJzn)dXY;x)l3C589y`4`o4aAw^v~{IgC56c!ZG_A7E9 zEv2X~i3HS$=vlsU0Q%WK<4MSOti9BlALAqD-Ot^+;qhtdcExvfCHd38ln3oUq~RK8 zCDR_};tjyV&T~(N>@AI>KUzZv9(O{6%#=)F$#$j+k8P1EEPsRi;<|}r*la+3C#mr3 z+ceG&hGyd%D@xBng|9|7A}YXNPij?Aa3$DK_p+A0>v@EOi&cYGCb}X+uK#GJ9Oc+-;x>P>sp1DBFE%EK+s_-cV^mZeh zfaL>mR7{)By}d38)FMBN=rR;k9r2f_G!j38Be}f47ts7|zDm>*qY?(K^`tJ&lrwF^ zg{dU*-v;1hm%#nCwqC2klAS%ZmP*bpU~Y-8CPYpO%i3r^cqg~#N)(Ae|8-N=_0(e& zH>TW4T%nPIPfmFUl6lYfpyqp(?j6dWE`vohOEpuP{1Kw)eO^4WXISYPu^+$IS)QZ* z*`EE?J6b0adP!A}+&hFvrf7NY`M7d@Mi99}4AXb{Rl6*v@;46JBSjH5z&Gn)JS)tq ziZX8Om{CNcZ{(cXW7=A9D-d2Xral&3^j)`EIiVYr&dPY%snCym)(H99epSU0PDT5g zx|2Fr!pu>Z@`4R#?}#*X=99a0G_J0(9!mLaN&Kur?n~y_WwE(iA&~dRn&*Kq|4Y|{ zg?*2Ao)c*~t;COK7TOwU+<>qXOp|v>D#`qLkd8>*y_$!J|6vz!2 zD25SH%yd`^v&*b^*so5CGias(y|hD}d$xfxMUnn_)O%CNgx zYfEzKm3B|OUX(j_=j=3gAkzst(&D4g;>SOS-tJkAYryk};q()j1#4#mZqg^yCbr$^NBIqS+ zGo{L~Kaihu_`g$PlKSeAtSZmF?x;xI{SbCgF7^jagqf>jYp+|`G`qlSPz*^H(#Tc2 z)cdeNP@d`sZvSddxJ@R5ij4HUSh^*9QpN%;0pcIL$^Wl%bTK7NXytH{RM>Kf%-YG4 z{*u$d>BIDNTc)oC#wibM6>obtQ))ur+;3L8$+*r{z}5-)sNun0O@vgW__mTh?80OJ8mw_4Vq&S76v(DkXpi^lXyZzaP;dpEDLgcX_H}<7cYLPCJ z5oqcq069q}@jG$eVHli)<~0*-t!W39P*T}opQZkV3C9SHzOAAXL8~PfqQ;#*@a+du z5~pT91^pNxF6XP<4 z;L+4esYwN8X&ZOixTbomMM-u0SBt>-=1k;8`Tcm@!exEP9qXfTy51YU-_2{{RqrOw zIaut<;#C^d-TG9EzT?7l&iXi0WY*c>s?ln`-`NbFx>Go4Da^rl0h!tASIz0{0l9!r z3S5rVp#)W6UVnY^AIgL1*bdB`euGgg8ADi3a5Pa8Fjj>D^C)hJ6ge-JA;}nauD!$n zXU#`V?MlD$*5(x^o0MuSb{-uK>*)}D9ptgWt>dQI?LRTp^3P=FMy=Itiz@L9gs)tG z-2vz4toNNOLE<;GqEBt0z!o#Y3|xjF13o&y@lN~m;li1-{}BE02|-GgJ8K3FS)9H1 zI^LYe?j~i7=hK6jFX|Jk->F*1NNO4GX@QeEEpcjYv?8mRxRqfhCSbmLrc3e{O7sI~&?wQHQ}~3|HJHsW*we zhHmlUfHEb<^XO}g%Bh+JRPbHtd@h;_dbt6N)lL(C{0h09>&*`G;u_BTAmp9DIT5M zC*iY04fnxahT%Ma6Mu~NU4L7L2}U+Jm3Ua<=EKWT#Q52saC!+qjwjS#kIA3(BDK5I zkyEa`7a}QEtalwAohvwWVQ4nn*ld~Xuy&3xI2L}aJlJHly8#2TtB_cGc?D9`FD+_j z02NJ*?ZklYWhr^{_MZ+yE#=dzz#|~D(s!r2Sa6meTCI30b&nDEbYy@)%-T@o8(^(G z2chDQTl~;Xq`mBjIue$TOKTWQ0qKZL%qO*iZJwRF{42X7lCY>V5eubyRrTzLRp7UY?0M{n^+te@m`0cv@&Sz+|&$~b|`+JGp`7sTx| zLHZ+C+VDt6h9MTX*xBPhLeMbl$?6j^NfI}_l7BVy2(R}b!AwoS+=M9L>Kg19kGU`x z7roX{J;5ln+Sqs2*DWlR+i`pZH_F$|K)q#Rc-P^qu3`~6?LgmtRz>Y(6287maYeA2j_bjbI!vgzEjJ5Uqc6$&c3oe4}RVF zHa&td;qtV9#Xa8(qtzdJkh+H%eAlY)5=L?Tszr2Yo$S$6%Apzx6$u27NWy7br3K~a z_bCTa7td4!ys`rWfUF=N{>`A@YXocT6|V$yVe=b_GCSl)1rVKe)lk#cdP`$aoI2G% z|HVW2V6yD$_DMT{7c;H-t~&Frx}_=y)$YQ@Vd&M)8XNj&q{VetKEW5FiccMdj>#v? zz$0Neqz;)c@0S@4mb1%{BQwdJB&cR2sE(rFIYHK8B+UTQH6u1%CtR&o9EqQ^{x&c? zDp|+vATm+cq^y*EQ4%ktY()VVT%03d%jl+iTw~>CKGB>Jn6UGujHMnJ@z^lP`mGsU zMBt>3seM)fZ{Ll7rRM-{Q$W(9baRQb`+Qn}y>>|obE?~LQNVXC z_f&g+;c?dfKUx|PEvyXY+!WkTqVbJxP@rMGHwD4DPoJw&$23U3^ZFX5E zYP0fjMQ>J4AMvuV>8AH5razX4P*t17+M1{`?n4r)J#^0Q0gEjrkV=w)mt>cVY90(8I76d3U!5!AdtCCdAX1A`71iCFDy(6Vts zmn9K@yx{9?9=%CQTC?y%w`w116PPID%;M@mAenu(=drr%gMUt5Miy2Xim^80<@Qq< zirtln<}6ZrU$MriKUBhVzjg`1!hs`xC&n&4&XGxtGXgjLsT3sA?v~ec`C3#x^BCF6 z2^;As@g0{&^eoQvTy;1U8D$CR69nFoWN5C>-*P$? zaKKN9z>kw&O|X1FS@|pM73=AisI^oCILA z_L8&zi-XhIm$WZ_e<~muu6`EF1xz~xO6vUuM8DNv$`wS&>u{B26l^txzV&PhgU9}) z{8s3-EP*oZhCq;b9OveNiTu8?+WrR>k{`ROlJVV{{Ip|dH#!n)<=D8|sQIxTES~AM z{dtr;lil1O3Rgxwzj5NQ;#`gUdz5~bF_uU*(YbtQ7y8VeM459_#F!CkdUvh=lMSu# znd@z~Xw=`rm+apA^UQJQ_x_xNz1f0ywTF|z!UtL0`CD)2k1hx7Sy;;^TI-E2RSPrc zEep%b&a?Y#4hf*TX3~QLz}&+5e0qB2&>UFymn4Z{dRBOjEXkCyRcZ{B9+jo}nMeLPub)eSnc-K4#tdc4kmFrc zvWv{ANHW;xTyucq;XveDim1waQ%4*|@9=+B#|trvs4M~Yq8egX)TuD4WwX=&rp5~v{%Xizu;b0{4eyP_{|f7Rr(;cawN z2olZ zv8Ajb1{Cg@4#_H$iUS?>bl8&yi;}IgoQ?J6rQgaoUT#i`0J&Yy73~%WfUYiWd)6Lr zd0lR3R@kHed}XM<@hJP3pRVvJOst|a(Gk{eJi&V2`bGNoP1Dk?0?u&ehYiS*3vTy( z(O+YE+yMhSE`Y&0ukB)|;XlAqgn3Tz)wsk+3|neOPD`*hW0jmM?M>&g+6*RsQJCC$ z?2WtHhXJ&>7-0_BU(=v#tVf zZbdzMFcdvXg$9NHbqgAD5`0}2hKYOkRc>n&Pj*o9t+Bpwbg<-&o>+bcHg;y2fb3=N zux-}Mp6mO*P|KVu0Ukj0V$(&hq^5DV?3oXU>vcgcr6-t65gA z6CL_hF0{7uZfm3aRUlRHipNvb|I4#gMOi<&!Ezm46k^8%S?MLIcK&vM_()XrNgm}$ z{tuB8U{ai99U3^{XogT}SyF`5`J!XKv!stC2fZ-JK9og)v|c97r5p=v&h_6j!No3> z+r3QpzKK7`+^_n_1ut6Di>$r+*8f#KJ-* z$iDZCT^nG;7F-vr9qjQ*0nomMEKkO4R#^Aq5+gao2s)+Nmw&SIqZ^$U+Fdg3f(gzE zx8mQ)0YptIF|3X@CmQ=buKyyXnLIFrSM%2Noj31&?Bsm{-By2}&@RV%c#a5jJ0@~$ zMS#Vqk}oMDcjk-LZ}LVwvu*V~GKN78k{mO72lN3Y%i1uw-D*zst`rLh)|t!ZXyfDLN)}85UjP(k zFF1Htk*BdzOj@A(jZi4gRK#xH2;x^eAXFc4Id1>a77R(=> zL2T!hs|Bqa1d8VsuJw16A0&6*sdFfMfp%&J$|jAWSgZ|$jrF2XDcy7$9}dnK4I2&+ zqLGs%okgLlf+<^%A9B$XtNI1FVcD_~%WH_K^%l;@s?^#x7A#%h?l{rZ9fWJ2o5ia! zqnm%QuL|^kWR4dD;=oyk)qoH%sFB7#3xJEC~K=IZ1f(%u~?lR2wznuQ4n*b1?UFdUN=iyq$Wrrd znq_{`1eUKWEV3AN0P~$TRd7soo9{*7&EU-{cHJiN)1FeJy{$FvluR2M!QwyxV~!g1 zzvaTcHW5T~G)(MdZg@+d%`7S~na{{^g0i z5bdAqP@4c4-3AX>*SpY}arex=#jW!9I}q~9b?Dsqy|O^_s7la;6l%L-7f`Z`6Y=dY zRV>le7e)Qgux)I*x@6_*5xkrmd}kW97d+;VKWdNDJw#_+i`O<;vdPY|)_XNcjc=^Y z?MnRwtyTuNZvz^C`4n;#2)2?vreCg?M}^h&vI?bH-g}&z&)o_-H&*!v9sG=AY-y|= zzs6ko^o}}tFO?~E6pVy+P{ky2Z?LaHs=ygj5 zbsx1}Pe#BW3iBMgURb0f!ynlWA8S$g1&Dphfmq>`KwwFrM*6jt5e8ry;O94oq_#7X z6q3o&BzpNB0O;-gRP^%IedfCt!E73RF@>UEuS*++ezIAYL5D?T29ttEEa{9i$;g&k z8$r9K4S#Osa}9d?4c1++K+BEDKc5c?;;;GR^x^V?MVCra3zq%dA7k=uvl%mcvW~SH zV|);uwaBf%J>K!8{h|3ce_vzz)k8hiw^8dn$P(lV3GYqb>n1-?W z=Nco+cWusy0oIr{&k{1>MA1VcGW{_v>B2v~ULohi!DJqA zn}okt*vp^Qvo+?Q`a(=orGAKNQ$%V9qnxK5tadev8k21sc#HqCjaX>8J*p+~ z?DBk7MvAUYHpOfme-+AE(>3+$Y~zmqxR~#x@p18q%?ymB zJa&#P-M$}M8A=3Zs-*vy%&1|^h(FkCX+k-{0L=@?2BTTQ_Uo_H`p>bB-fHLeCYn`Ru~)+X6I0fIa<_c!(WSA{1_dCPsIVBWsdv3bbnUA*#iA~QirSb63x-YJw38WW@cl}95~9^ z@x7_Vx!dbKTT$L+1@5jY@>B3Xi@1GHT;lXqHv#>d&+PT(IuqQ6=kauV`kntvow>bj ztmE0&r(o|s#>W8|D!)%zbss^fqWtDplm{7q2!$XRH--j9N#SHm)n_Y5w>zlHGhL;& zTZ}1qwf33w_sP&V&_6=Kis9_1oU4PZG{$ikIqrEq=31o;C3*N2pmyf6%xqXJ-2Eo&W*7E&orlvR3DQCw;n!X+F;}T)1ROZ{OU78F6R+V zwLl{ZtJsETob_#jD}NRZSI^6j8Y9G6-S|-bihZ%;$j}=&d*bQ~q5LIv7Cd0cQ-!~6 z)qDPk(|`oe=E1F}ijrNWO^@CEZ{7HR>&EX<{y#nVPY?dnga7sq{I`GLzx@OM?H~Am z&;Eh`f8qWRJ32rN8i6&#;IEKKhWuKhLr$H45xI1(gH83igjvqS44HwL0s7G6mX8Iz z6k3MR>|U2BdUIT?`EE|%43WCWsynDeW7g|wMZ^l;dxO#M#WHbVr6xCqro3!d%{i_2 zBu%xlpvr5q2NJyTn9dmfmnH%zN|Yf)92ZGYl%vylkW)~2yHK$A%i{>-0AQ4(kP-=L z;^tq*S<+BK<5?WTgXW%1J`EV?b_CI)N@8@6K*c#7%D&vc<%GxmQl-|#N+!B$({O(F z-TAl7c%9%PLE_37ypW-$rzbp{*M z#nK4Ut0#1hK5qlT_2YX-oHaMeKrQaczYgR@}y}NYULD$42}_o!i`BL zNdUn{z$O|n&^Vq!D^sq2gx+k6l=$>u=nohzrv92cF=oztk)zr(HH)Waizcy@3b>hN zb@f+;jGrjWfRjuWg7JTv=DT`I_q~;Uqm1+Wh^akP-*x%jWeqvN^ryAMlF~fDHcNVy zXzzo4#`G|O9E=QCH#t2(KyA$vw8%*J907p1$$NQX@cmj=I%xMKg!UrT$@lY*^p{94 za=Yn!EfBJ)Rw~!4b|VY7e{-!)l8|D58XwNwMkccCB;g%KGDwqx;e4(vMz)aXjM0DF zpB1UmI?-6l556@B{Xg1!%cv;7FKpPLOF&AIZb3Tb2S}qxOLs|kx1=-(A`Oz#4MR7g zG%C%^2qNJCLk~UAjSN2T*Z0%A-nIUpX3d&eXP^7TK4)M1y7o;+#^kPwWnMrZDHsi? z2NHCQ@9Q?G6NtU%I)wmLoUdgDm0D*KZ7p`P`=Ad&dIQvDkeQu`_c+IYH$^=R-nfVNx<F-4J>naicV-1 zRGTy1Z&3i761Lh4g|-*%Tnet1hTa}h)f0H2SL$=J>-W!|=G5sdug`4y_j8_MWt_SJ8%% z^7rpMh0yAB-IuyC07bq&EeKhR+~&dCxE*?pVw{3LG>W!TZM^YR%wVV%QLs)Q{b8ysJnz511R?~)R(XSWAW>|ov7X5YsgI{5LS1la~x!Z4$ zZO-v|>ftL^#RY3o7YrzOcLfUr30qTu6yFjS(%somZoTx!K;FbqX1G1>WeF}3RTmk(Iw8et zb&F7)^^y`V+Hsdv!{&RE<3aoSBce1T+uk{iZ&?0z2{B|lkg){&j^i@$d5oX(*Ty8y zJ5vpdIW$a;XVD$>Umq*f+)>uWwi+1}VQ+IIuUL((9z;~;8Ye)IvF6i#uBPbCmUq{f z&ccoBdMlB}H~3WB5*6+)q&A}YHB{JQ%euNLF{mcv3s@f?WT&Dtxdj;&Yj64V%T2z5 zXzLF-C;>W;CsdJ;P<2QiF!<#G{Pb|Y7%Q;iWEu$|PD4nKYpe?(!S|^G1aJ7$d?r3&LW-@f-t`Y*3OpAd6R;zW`fP7K-f z%{jcSa^=?dQk!%l6p!Ll-_xb@X4P2rn@5-*g-K7#CyRaT@f9`?u74_FiTK?vYQ+Pe z2E#oo23-Tg+{jFc`3!2bE3~H{K`Gg#)r`*kuFnXzqH}0OaLTXM`P15`D)-s@)_Q&W zh$suT^rb=LT4&qa)a})n9PV1s3wArzOX!y^*f5u!)|u$vuD};ji0^3GZ5M#n*n%w! z;r#7I)WjJYH`;rF$`fO|gOM-QTJdRVqfVT~%|B}Z{-N1@HH{bmBeYjEU6 zNP5aVD_J2m@jPKn%j%+Wfm*7dg6-SXTm}@(_zmIS_&&bbsE|s1{&RYeRf%Siu?|xy zyy9Ns_;V^J>Og*$w*9IgWzRKJq~EsJ@CW{9WA87s0xGzvpX*yRh>16RA0B`AcMn}6 zP~h*?ECR^(rX(bF%0TKQ{wtNm zN@0mJ%!|g@$x>mrPsQw_G@6{n-77_^<{JJ( zgfM4{z+kB(gAe@VLX~!e>jaBx{nikdi*sfo%lGUO1la>Gj8V@Ckb5u#*!#ZEzHlC+ zU#!P6?RM^&b{wl5?~-WD2rc)ExN8?q*);G3*JaSt+pkgmr9ZoThK}rs(P-bbgP`ua zDzALCF;T$1Zb&dijrQ=x*<-g{`NieDYRC&o_Z}a`%sY<2$bR6kerJ5SDfVmX@i;!o{l7VqnV3e|4^R36<`WPOA^7IG2)Jm0;iD}ZkZD{mmH5DkMWu6>ZqGQ9 zw&*e)hE(bhTlo?$qIVocf=z8+gE+<_iB#VV>OWfqU*EPmsVbvvy}+kGo-tf4M@(N-%Xw!dY`&+*r5J|fnB4o zt!#_wA%10}EyUmn*Xf)jRByw3=g0n75%TU8fr6f1Yhmuk&_T0)>bR)Z_XRR92e7_> zhu{85Of(~j z@i&dE8n4dY@)4AYcv5@x;PGbU!#3u?F4T@2a$hj9I`Eq&1IIT?1uF$^|0K>F5IwGs zf!T)+f^k$W=`uF=AYeKp*(lV&4+ZU^WXGZelD7%vmfFzOSlf^CP7oheSu4IS2u4mH z#_N$V;yC6rNcG;aU>16ao*n6y0f@4SGiht8a0qt?Os%sukDC58REG0Hp7}T()Haq0 z#2c2aRHp0+{_&_(uz?>O{v8k^fM*kXH2wPo6Y4ZADeF{JIHNs{3%%?TaViNDQY&y& z`X$2Gm@#cY>hvkZ>-_XOKS~q?M$vNTYLx*w%D}Gpon$i^|M&ZZ1k2s}gt?(jFCPbk zo-vpdU}GmwVqloL-ww~~Wk`e1Lh_=bu%+MTAonsI=NGqDd~+H@S&){=OxCqb%i0m# zHPmXBAYn7Fl6oHHxzPQvI8}4$H9E?|wNK5Rb!5#Jx$B4uhb06p$}iSEy4;>Y+fQ%o z9Tau*JAR|Icq#T8;fPp8I795^t=7hMVlnhTLC-kPsc-zphwpAj;oEVCA3A5m3-0Tw z>?~-lcy1ZP`_kvyoWo_Y{9)+i_Vozl<;{8qgd4#9L^LENoY+tjO>}AV=N-o-#@1+) z?p*w^{@XIDgyF&=i_$O2Lo^AYAfIN?+0cEd%_7uWe~!bigb6r@U}**~PGmxn-=TqC zN9Ja8=-!CUl4IpRHuRR&Jr%IV2D9hPtRcT!daGk)@WRwKwBMnes$K81az?Wq46*EV zg1R(aF;^&Lm)Lr`pZROigV1|IDx~;81iOuy6gbSqnbyoPA83)5+`4!JD_JXwuQ8b0 z*<`tx{u00XX+&J(TDPmx+pO1*9yVt)SJ}^bEu4=@mRq`yBB=G6*PuvLqLM<{u%M-_ zR7c**!=R{EG!Wb)2Ksu2ct1XqOJ$HkBuYWF(23$ou@m%~5XvqE-Dkw2R&3GlepSaZ zd&@rbnKh3$u^uK4N&W`Tn-QfK+>InI@hAPU4@B5xU_Q5BCR&7$`4!P zimTS$H;?o<^Fvp+%fN!Z2Z1zK{{sF8sT79f(=?=dD zgjDDEpf@+JYvz#?HgR0837b_+*ie`f9x16clch5i!c^LonG zo29WmTTZ(C)y#0$Wgiun26W+%_s;v5m3o$@;QT9^T|e~XhQ9HS(;3M zY^+M3RjZeb^I4SHef;G$tUaZPs25txfZ`#?HHop64G_AV(^>hrs}H8=RcFcdS(j$$ zm;7FJa%eRO4SdB)B@g6}Yxl0@S&_$3<+}AUjyTQ)sIS(-mT^$?QZY@?&i7XK2C%RP zIB&1x3U@m;gri0YkPbcroQs%LH@%K^{9bO)Fk7)gJdrvD%)LdDu!naA%T%sOqrw8a zea4^rCExEblK@geB4j^o;Iw29BLyPOQf(}-P1TV(R(bepHSX)R)6!s3GISMpSG64$1-9I3>-k`r$})mBt?&NhCe1gV?4j?$Pkr;r=+>=%-k@Jq#8xU zk_RpZqsU_9>Gq55(8y&@iZ7BLZ-VwV9u`!wE?^ zLo{t%XvvW#jcKhKGY~U;yUz=3Tx|2`X_HLy`mPk|h+9kca>GwO&tx!#-}@<$Lmz zs6jz$qTyLc;a$A<&(eN!zkS++T%Dt8A@cfk2Uul6=4*naj;dR$sYR4DGn-zhvXOy2 zhsess!s#cri&YS+xxc*6VqcVl1g7umWpQ&-&(y6>esv3p*GY%I#uh1j@b zn^G%VCVAq(9SF|1QzXKE$OEJ|xGhekHQQ|+_MDVr|av#!yp?76Y$U#~iR0*xmtJ>5rk zWR`q}V+UNR+yM8SDvC09brJxQzaZS73f>%rW`f_kGiXiLnQzU6y_KXhz02P-N%G;@ zHX7zS&KOT_tJ#VYoA2W#6`VStkj;=G@ohsCvePcDHq6; zOCj4|FX>zWa6P}OAHO#tUP4NX1&23VX$ki_?iRthBMD?QL_(}rLX3J}K}ZicS0R#W z&h6ppPdR_0XWhPgUx`_5F`o7?&C&Vm;LnS%=N)A3d0Y}?hV_QK{ue*9?fPgCUc&LBrzY!+yU&;Lu>91dXr@4)`SI{9zZ4w zM1{(1zq$HqJ)WhHo&#VdvbAkyl-)jF68fSJv9^eKccY1$sl|VG#We#($mmElc#(+K zMtypRh0Dr)C*KmyM<}0uvZEI%WE?PL9t$J9rY;@3-FHZ#_fjIZ16$p#{4mr?_O$_g zhLZL6vuHRZyG(2cQQB<5kob+Ob>#Z3ui<`#yNM`qW+gr_Q-G!#?Gfqu$Qu?B>5mU^ z{u>GY$cA^TNthN3Pw7GMqVzl=u{%24F2}&<_j6#T{!*LFV80(tJx!T39F}dE>2?tC z98h`7IrD`|z(u>GToifZh`*QmmB3#dE-I%lG$0U{4xX;{p)=*{AJf}xw#&wl(lY1d z#{bqRAhQO7oX~G>Y*tK7d8#^L^3pxjCEN^Zk6B`KEO5T!<;ib9A)!8$*+Y(M=kB2e zd#L^0?8aN(EqX>}03#T<-fAAL+IpAYD~G5HaQ(2hc5rXg!))wG&M?wCHoA6nGH$s# zrAgU{(?$=j)00pr(s@9?WTw$>ZS!5nYP z_5$5|U>`$oP0De%XMr{`v7xmr1kIWDYX_bOX-U!!a`k@lOWt3~B)VR*SI2E5L-i}( zM&8Db@KcDMw39xpdx4c&zs-4EaxMAO2kjD=;lm(Q((Llne!7QdhK?ooy*3Zyt)(1L z8MrHVA2DXA<=6a+D#a>?xmI)iq-Mx@ymB|_ipqHIaWj0QIFgcpT*nPtaF5in_p$}Y z^7o7PezcCVU*uoRJ@qh?#+DUf<$SiECW32e$%6~vzIMg0$>7`gcfL~v1CVNNMLoOi zW|*_hd>)uLT%nfDwIYEx)4%bH+?N{aJ^MEt`X(J_B{iWJ2z%%NWQ`7W``+u?^n58_ zsB4j3(Es^s4s2{}JYW_~KFd8<06jaz(*I(8}T9eR-GRB*jxRIIYwl`S{@WT6KL1?NOd zHXKNmD})#7J)Cwu5XJCZT|qci`*&{(szZOBwO;tOlp+npb-gy{B(LkufWn^P z_f-^S3Gm;STnv}GJ7Z!}i0eXY`&8ALRp(Rz2W>alROCw@J#_NyPWB6_EaVC%IfQa? zZV3o+!dQ?$IL9X9v7CgztJ*LhS^Jo{f;z)OX|YagUBK>V^i(e# ze1Z=96|bZkSa-)_c6k%Qcr)HX`X_(X5&DqpQtUU4nn6Sp@AAMRjiw37f@^yW=~%V~J+sk1dDg!|(~MIxgBA&#V#D ztv3YHYvmVb9;*yT=9%^WqO^XVUx}smyWEA=utKHh8N`j)j0*UVRFe%D`I|~D2hQAA z-@I@L_rJ`f;lnhFsP{_;g3y{#TZLMj>A6NfXg`KsULuH^(DQ*fq>@OOIqf5~o*wD% zYOhq&3%Lo(h9oXd8|XJ{zibule~6yO$`@ zl5`cBf9N5y-DLo;R=)ZRRT`SS?0%Ss?eWE9b$8O%7eZa0;Z@S9>%0~BaEn)G5?HA+ zpXw>~avol^^ofgDtWy~?>_@ESxRiWeHQz>-|7-Db>x1IwyhI_c+5D()j~6S$_eF)N3TkE0huJUU7|2oCRfLvJ5iryBOA*n?nM!K5TV%w6gsrlboYJ~ z%qTYH(P%A8Nm_fkW>~YXEIa13KfV&cmwc5^+UWjUt_LXC6SVj_*~jf-GhKV7t~i`u z%q+^0NAmWb$bFOW?HUE4v^@INb-aCd@fxq_g_xdE(}5` z;nNJU6X@quZW~A294U*+$~nnr=}$D|1kE2i%Co?u#xpPA?acvQ%J`3{)5rwb9-bn# zfwDW5sJh=X0JeQqV@Nkd=Qy%jGjevtp8ls)MFsFiY!kFlT`3O#rTY#^kL8jc5Du8| zS~HeVusf{exH!_0ZJ4JT)6f`ug?6{osb0g;XSN@VxK*h5{}jjJfj>4ol+BK0 z@_sH|cH4?33=cbxhyvcXM^SM1<#!+_kEh`t^8D!$^BT=*kVu(%FulRd7x=reuVV0v zOVhr7fE}6G$dd@-4g+qCyRkL{rvViqKR-O{ee21Z0 zKijyG&2#}78vp}t4@r??o9}!+F1o(FYIndBn@JT5|EZ;*hc20FrjASSTDVZ{2=gP=)hH9$I; zT`GQ{Sr`=YrJj7VhVw}0?it6+PUs$Wtzi5d@Ul>vn$<2htCM+Onc$9sFLJ_`lS6RL z8yD{B^V#eGTA@-nd&}l1V-w1l^5-V%ugtvtoCx0%ZlZUq)lJa#E?jVt?RSVOe<}la%7Fz zQwO`4M+;{4uN7)U9$VUlxPE;ou^pFa)&=3G^acB59bR~q+l*2pacl7&JoK;&WflGT z_JVHwQq3e4)OC}o7#g40G6Au`#q_In<-20c?SFf@lv>9nT7-VOIC}xg1qXZ*t=6hw z>qYMi989!a19jzIBd&XH?Wl-L)K=>1^_^{);m47!c%tLH1~F}s$C24{q2W$SdCj*- zMXL!FND2yHltuavrt9m>;<#RpR;Q<%wE|a+dw1BJyiY*{-mjqw^wK?2y5M^0u`K1jve|P`?8!FZo-Rn?j2?bcyPY6GG^u8GP#K`$j4E4 zkiVchP*3qL@w?+@>`>VpMHj_C1s*gQc(U|7x{>S4=4u=Ry^&ecqKR0~?}x5+oxAQS zTm9#QFa5MPa`v~>p6L|Ry%<2w%@${>|BbM*j2}y2Se-_&oqja%L}MsxY{6y1Y_Tv(N*6f)#Ic+&evXg65n?w6eB^oO*B!o5yLoc!5jy{^&IO>nwh zN}JqWuOI!I8Lip8+Cjpu;h(oZ4vVxN{@*wi%wXH7D<200st1>#u1h!!@d1cyfzn51 zwv@Y}M7u!&|8T(&Kw=6E2XS8Qgg{M58=+TG3V$cFM;=dLM;9 zog{tb*HFf)O}zue+w`2@Dk9*b;7Z^_B#;JIuc z9KMIW$W*S)4{@-prYJy4*bfyYn5UPmIwaPQV+4u2 z{px_zpwM@k(Ycl5f8hiFVN~HTRG=#qe#i8ETcja}svC+5YbMJM z)nAqGA|i64Mon}rP`(9Ec2WY3DManR$9QklC;6lVu^1Q`XkV7FevDErbjxJ$ax`YM z%VlLmPFH#zfam$j>|+Hp2?XbU`Hh2+Dt`;QkRmSHw&wOP^sS^vsyC02SGwE18O|Fc z*l)N2h$Yq}FMFL;sEmNtEU%Ga9qlz5m89$Q$3)-JmpWD%5|`(p6f;a1L6z|LS__3g zR(FhOrsrvoChDLG*O+9Q*oVx? zQ%dZ11XY8lDLkfgx1oYyfXBa^nLi;Ii9gJ1CS?Lx5- z3NYG?x;JzW%i>FhdQ<1s(;!DWR9?+7IO@_k2<9vzTFnEI=YfB%RJU6xw&&=({g@+B z5CrK=|&Hb8aBpR^lMR%>i$Gjn$6Y1$Yj-#~BYt4%fAL+$liMtp#5 zaoetXicb%;@)ud(6}m~owBbLyDk2m_kg6jUuIXa8>T>vMD90P1t}Ae3J{Rb zpK74ImljaoOQN=lpDS-P`&)l za#RJlu-wkw3oh+cHN{sfr|EcdB5}R;BmF)&P7fRoffd7Ho!ihYPI2{6-oZ;Ci*ki@ zcu8EF{3k09iSCDUHdx^cnvmjTXV>5oqfydESg?|Y;7}F$4_Z9T&;I+U`ZQ|0HPtlc4=ig7*I-37Wadt>Ak{c(00YH&h<< z-ve$9-oRbfJ8SBLjr}W4l0lj;%yeHxEI9?+~R~9}<@PH;&m{a`3rITQ1EnCek3PLgr}r#*2)#zl(O> zdU@aIVNZpt$-~~&Z*G&J{#fI!5@@Eczzy9z%+)z!t{75Ia$x>2kLT|ixp=|9U(**t zH|t?7?qQ0byiJ*>O6UEj-(vYq7|_Zz%yuplxMj@k$YV)3a%^1sVo23%HM&xc%IS%WT#y>QKb4Og#GRsoq=Ij#h2u$38(>g zjF^u(@hSuViUa7FgpOgzZwqfMwb zsS86Zqu|YYilwRG_GPofPPC;X62W#7x@mj&bYH7ZYXbKcU*lktz|Wq=ep&29@$qXZ z+NEB_eeYd+#Xr~YK?S~r0(mTVY2pY&Fo`uDUueb9ZI$c>zSe*~Kn{ca6uzHR!O zimY9&gKyMxUx?11bW~deD|?mEm8}FZisg^P6DpwrZ^2?w*@<~zgJT!t3yto6tBWM& zo8vea=`|G}DEY54)d4GWq}EXbzCa|G7GmD&+ocLW!LobmcAF3E5LgQFDnVrb3Ham- z4L2z(xpWLGE&k|Lju6A5I@ZbDl;v*Ym@9`MY&tgOg~y=2UR}NtZ%mrzwrQoz_q!zz zsh@EVdx(Wi%y4)sbdZ-Iqz;WrATfl#J53z}Sj7S5DJ5RTrI7xH^<}+zksjMMy z7Xv4$6xz8Bxu~#TQue#k@AkTPxHD6j85t_HyEEKqEfdR zjnW+T4`~&6&}o!-G1H36>->_uH)NN?Mdly*I}vjQ0PP7mXZ}89+WnT$kNbTJlG{H_ z$!LT@cs`=>2jjZ~R0uopsTEG5MIaBD=ScADqe!}R;}Mz>VV}1q9~XY26=yo}7;}@u ziPP$ws1$j-(j5IdIp7rj_HG?2ua3$SU}Rrfcx5vNzw@^FyJCb-iP907J>Lo z+?GMMCB7=O>BclAUaPu%a&O;_R4<{$G~-p>(}qVe9twvPQp=Yh-ZAdmk$gRWL7#5? z(kn2InOoM#D%RJkb)P-`nX7aMACr)RTPVNK4?i3mM_}AmF#dfcu0~AbYTW!sx|?ruD|ChW>6Nkt5*DK4{KVI1oO1|m<(^dayxskXh^|*#l`Jz|E^Jj~M)Ye6q@dVjj9#GI8{KoCj z4p&9g{71v&o)i|GEQOTLt3Dw*F^DDP4IA??-6<`g!*eG-O_tu!zyAV0SBE`T8zieT zT({AGDHckj9Ut6b3DuTCB9O725rZ^rTZjmxV_OhxJSRp>!msnF-GOTM6sroal2TD$ zrK$6Ae!!*L_rR`7g!6ph&ZoD$!25G)!oT~dK9XAS`cT8sz!mV$^7~Qs_v4pXeC4e9S&ig8NJWOp8EtQO>EE|@RRj!-QQ~ol`<}#pkcvlXT}p}cIQ-} z@)5+MlU?7^YI$1U5byxf)r9O@3(KZy;_Q1M9%K*J^Ysj$t}V*8T59kxqTVuSXt+?N zcF29zza#!Q0jZh$elYs<(|-{NP46Jx4>Q^+TPzFm!R$#fJ^$r>$|cOe=|C__IK*QR zhxIf`jSHdgos;DWX|BVV@ZMA7nASX7=OFZ54xeCt56Q6nj_~-Xez11lr2~O;!7jRP J)m_l8{vWSFzApd( literal 19612 zcmeHvbyQT{yFaOv2!e{_5Q-=*h$1bGq)L}`^U@s>f=YvQh?Gc4OO7ZZ-9s~ibd3x# z!Z6J59KiQ}@9(es=UsPk*0^-doU`|{KhNjMy^qS0&z?w1K?J#ZA=p&C00MiPhA_vNIs%W#6BW*~scKuJmK!i* z|E3CSa70%y-Qf|k7ts3aP)I1--M6h|Y2R?#*-=Pp<@xeIMH2y+YNxY8A<(ArNzCMku-A7Su5>J6JIp6CiG}OqS1=ar=B+yZUcKOLJfD~~7WFG(cAply zkHtdE?CiW>J$2u?EOm&*Vv!WJ(+vS3d(lDayd z4D^K))Xg?L&Fohs6%qJO#><(cmIv3Nv>|Mv@v2Ij+JH^%$9@ztq2w#9U z!c77Eh1AneyI`l+edea(m#QwstYXpjB3$&6VHYN@+UK`WWp&@rOjP}vch#L8{6&;o zQeJ7}6-?*0SW=-%xkQ%j(9TiyCpdc{+Rx&mFRf)0!q&zw;SEK&c=YNg%rWm}MCjHD2v}ziIspyVa1_)yest@g`h{h5Dq-v1gV#X^c!?2T{zcb@Qdd7hk31AQ})-e#fF7%n#S+s>m2iDzK8rd9<4d|rSg8vXLsDrgRQ!r#%P3hXB{-z<~}~I^5+%Q@r4u6`xGwWK*(@fgug?H z$ngo-Vm{%-P4xQ;u}s|*D<8E>9ISN1gs~~aoqam5i<~IS)EMXG<$bbe`M6bM>@px> zq#pkz%y(Y0(77ZjDW6Wfhn9g)2CmizakSHH5TrkKWUOX23+b}gT-m(teK;UVdsB3+ zxTR4ywdByH?E31A0xXr2uiNh^g`l9W>!W;M>JHV*1hdw&C77WIhbzhB32a9)5o zZG4w-aES0fKl~1*W8?e*vCg_>$zEeW_4k9`Bs{=m1yyXXJHZB0lMZuL$be|I8XxUnnysc)d(rdj&aa`C3Ya- z`#Gxn`nUg%JVb`2)cXS1Q$=d^jmAE$HZOwl@r;V?J2oMp$ML7F)_xJ(ld;CjFG)z? zgwCZ&W(#&jT3jHp%Q3|lCFKp8ey8}F=-_y%$OtVZ&%pQlDXdPkM%gCPFDcdJ9UYU) z%F~^7^l9U^Q(SMMrUTntWbWYQCk}jv@H!QAyagDXT4oTAcP&xzfJ=kYBpZ)JE$|~w zN>T&~FAK_{&lzcX&?A*b2ew}QhfFV*7=9YOJ38x-@69vvb0@bH^%RT$w=Fx*6yc*c z4Nnyw5v@Tl+9>!7HUA<$1*8E0gj#xVQ<8T?BVR!8cW_NkaWV;Qvfj%?2!gQv!na$f zTVcfP1nZg-(x+D|+tO_I5S{o`wdTlxUUx&pC&$07buTaPcBfbLiI;@Thz$xOngJ)Gw zZx(|66eBOkSUp3mKY-r6S1zm{70-q}Ya93RT?&+-kRGfI#LF!f5D3JHk>U-!NF5l` zZ7BKpGBrY|EY(c0v-CEow@xhvT}w`v8acQwlY0qQKHsCQnKA6?3&qNn^9?b=E)OQ6e(7!^@>8!?PAV zf=kKX@fg!?)$QWy4p#!E4DiDLmnmvAAOupj2|}q{AGO;Ebn&$VDHkxW71>C#9~!w# z>cJBfeN+HRk4ElKA|E6yO-m2A3G4cU1A$T^>z-H59{U&gX(6%O3}^lIK1onsoR;B0 z+}w15^uPpR3F%AJ#JQ+t1)|Ok%Vs*h27dXI=MUcK2_nZWElZ#KxE3BR!%rajSH5h@ zKwfI;mXW}(8j6ZJRJTLPV_JrB5@I@0H^}fUvUGe7HEd=c#8*wN43ApdqaySL8@XyAUDR zDFYl&?9BO-<=%q&jMsDfX+I;(nZ{AO|Kb)5S}PrH-h1-|Ixf=?`}d~!7k=O{Q{f0J z=_`9&@rHU;On-4#%nMMln=eoDuZxC>_$w-9l=-bcI@PVhEQ-N?uU|;|Y4zKsqc_dH zh?@#=3-|uoFsN|A%xu2mXxzEP4#^{@dC`F*55W+#u1crIxFl6JALKo2aApCXDIgz= z?fBSO@2HMyI#Ed++my;+N>W|TeJpgDeWNK2yL+~Eb3!AD!Qp|I)fy_aL;tAb3gTR$ zb+BY*W<&5eVkkYdP8J`>W>o(Co{yVI{c~2-+R;%OIP8Jr^)oVpzKTMorRbZQ(!|h) zRChW4)mtf@dU%PuDl>w9fy67TRp#1e<1OP*VjMkgh) zqnpM)O#*!mzt$u3*M<|R{cnNIMfez|6=T|~=!b^xrJrmzIu3{))}tzCNrx%{zp>3R zqv{*Bz1Xq-b7w?-KICPUg6$j;Y8JWZ|B5b+s+{qvv~=+htf6Y#VzZHB%*pUD<3wnq8kEC~=K6@! z=R|@1UxuqFz|oqBwjWVqeLJ=mQx6Czw~d0tDnhF#)sF|l zy29}R;sj4;5#lb9p3b;IKd-?s@kz9{+I!Yd%t!TfggC?=RxXYWTI#cCWcaU=B-AJZ z=Y)c^zutBfNaH1;y1mNvl?ch5J)pow0*Cw7>ANiEJ)3j^?dUFSFty04M_E9Brw)6l z0&vR+a~D|bg3FXux1UyOsrca*zg!l)JHY$j;Q6 zj{ukiDhpg=kB#YW?9s%rc|EG^5Qu%T&hv2jcvH-SyS!3giH-`di5Xo~K-#C?^i(1ym0vRE`OBz0B0U@1j2Z zelw;c9Ekss>f>}v-skc-?W2Gl$|pa&jHL$d6Tj{$(Gk-OC_<2s1ZiN0xMJf5l|J7m> za2A3>;zWq3IAu5L3<)(}m~h~Om*O5V)VSqw?;FQ_Lu^asDNQ1(bb`afJyUGo_Ey0X zb*cLNGrd)h@*nf`&KZj8SaBcBf(Bk5KkNYK1AhXA(Vpe|lkaxZEgI|ZFtSmuWQ%N~ z#4eoXPk?^{s2{d16m+{vwr;TJD^5fZAse;#HDiK0C6(E0Olk$QE`LmlGioo&{)P>vclOo6{Rm5+tG_s_CoU{YS7PKbbzzt1rfs2xy|8zk09oh5W7=xmuF zyhPA9AJ8lJWobyXuP&jxL9r(8J1TCkGK1}pq_gJKMPOOwHBL!pK9PMTC}3!5PB+@8 zNKv_En9qku?^?a31T|mma6|T0X`Xt=rrjUuZKBp zHL)LhR<$o2cnVc+4c(uF)b0I)y$ndwYQ(K-uZw^_FC1O>NcTa!2A=vdS}`z$2ml{C z9`l5X>bW6(>5J2LGuoPHoGzZl7l43gIg)y=IdydVt7b>V74A=1B33FMzOtK~g<^}A0heVrn(>|0QDb`8^z)$M0Atrf7G50m96@n3AC{R3jX{rT|7xpKC% zu7j)iA-V|wpAtK|oHEk*%Z1V>(`6NL@WocWRMQ)gG>OlD8Sn#3sEdCy>u-P=sFzXj zO919K{!%`dG)J0Plp)(Pkc|qbK0cir$szUSCtwN=eQ|H&9}iq3UO^g*=2g@y^OU&9 z=LlM6f%S@0S@fXdmbjB@mne_F9AY+&zd36fB?%~Ix9b>8T(J2lrjHM8k9LkGQA%lM zjd~hcRgxq{NkjFe%S@-}Oz@EhNl?hXh?d}7371RBaU!#7&9RaTeSJ;48+U~2i^Z*t zYcv%!q`b{zzS+2Vy-fe8Zr-}PzHQ+!W3FW_H=&J=RxS=ME^*lP1||$w8b&0QXT7~uJ}X0Xu=w)wqiRLMcPa}14m*|`Q= zZ$f?B@&CLaXlshZfR$pz;)HjxOFx>PdaJ+?sK8lF?7pX0q5{IbR)iQuqsI+7vyRY| z+dgOh3|0gt;<9snfjFJFJp^iSF6Ka-e$3y%Nyd?P)M$Gukt(p9tg7Zj)#DgpO9{B} zf@_nU%A0F9$0mb=M}y#?j{a0TqGGUH@UZ4b_iIN574qGZuyZ^`Lv6Ewk8SnbZ4eyY z_{FGL#rJ0hxZ7jvELif9d;`=rCmM8Hdrf`G%CP05mAS~9mw_3$TqX>cDMVj(k=fA_ zzYgN$MhG&6a3w15JSv!JWA>jLsP7t?tX=C5ZFc1BD^APR+Z;bQHZ9r^K@}cpfe`dI zqurBiWGcI*;2_DiwGNwpp;V{D>Jv17quWj32KBl&bs|i- zLv{u9(O6eraJ)8YbFZYb4(6#`_3#i}a(Fhqw>5H$6(zc#5iqtnR3De=Vf`s`5Fral zdsM$Aim*0%o;PCHz`1{V`vQh1!qH>3TO$Rx-_VWU@VWi~ zaQwL<1eC#l0KY%?DNCu)RbC1lwlL$JIjz#$Dww|8iW2QwZVM|*Zqovj)4__A|)lB9Zab%WE=a+KF8sy;`Pt`Nlqj}yv- z(D;@ekfgDWMzb^<5CX%z%&WGs-&4P9JJ>!M2hT{`S#TLdU4lP_gC6yd8)9qh8*N`k zTNFqMD6PBN*kOA&!_|GwwFZ}XY(V&*M8L6}oO%3fDG-DAIgNw**){Lk-Kg7qNH<$D z-EbKsXM+dgpHWAA!y1Xnb1SGsKPZTtr=V1Iu)Boxvdy5b+fV2)uw|>TQE-@4h5A>2 z(&?7+A%ud4Ft6UIje$SBqf~wWk5~UvZM6k_6cHwN@{=KVhWOYT_UdCe5QClG0vLd@ z06zZPX&rp~_6xwrlMztf`F2}^Z6%yQ=J2xw4erEGTZ*9hT>Mq+5=TT zU$Nn*YWKLpYCk8t>WO8HkR0s!1sM0%FccH8^$@_E5;}_LFe!DbxKo`&R?@^tQj+Lg z$dHLr!R+@vS!}hL_pL*PfUwz$ZPg_D+=*>2=+;>`dupvb0-pfo^+%GQ2}u0yGz)ql zLBA;L5-KhEh~Xx)Ojlq~IShA%IAXeGB0NUQOA8<<>vKlzn#K@gT+oBs(u&V(yoU|W z2*9AjX+{e@=siXS{0dS=8&Nxs?f`pQt)c)Y`Moo4Pti8-PW95S(u3M#qcM2pj8z7; zCQnDOdu*Ote>0dFnmof8P9my~*BRUyi#HIFrHI{i2BUv%90=QhjRRqOh~7ME>1v!O zM8-#wd8z}S?IB>`oZ-eVe!T&*#7*8&MEd@9Fz@{`=rqtg4^Ld+DT!Nte0R~|>R^+u z-);aiFj?m4=UUkQcrP+e2fBh{?ERheyI>K^t=U-$@@mFlioN_vv93l@Z0V@j#*@~R zGerU|!;^3oO`)Fq1c+D3%+%Q=MKN!Cpo+^KpdR!4*#3>GcLBw%4PzBPm8c;XmU+a%SuK1TwE*{LVzREjWZ`{*WA}lC zd?T)H$wi7N<9(q@!b{A|Qb0BrYVy)fFoi-Rm{7Wvm~^iu{?j#=0-LF#cJfcv;~bBQ zRF9AEeLC`0$E=}1bpl6n@2K>>6zea+K=lR@-&5Ra#eEzgfBT6G6UNV-wE?gP3Q#bxgp+{6VfBcZ}0IqH6A9 zP}x{~5E){o4|BaFYu#&cxm{7!y9=$hcmMd)H1x$?(g>bYI#!~@&x zni$c8W95YWwp4GRJl8UHZC=>B`Jx{Q{UtRekbx5pTD^ZSqnJB~x9~oQNCM|QPO{mn z)FPsu&HH7&275A%pjV_Us(gKI^FyI_8ms7KN9BNv03IyAbG)uJDTekj+@Y})4csTFUT9grWoAXj zfra|YCyErvrIR+b0zfkU)Ja?XH1lCE_}0{;DzhxscR6G3*x-!iW>6K0>oQYtp`IFI z(PMus7$jQZy0%5T{R3?8+@t+3R{05{=nJb*gYhuGFF{eG&_oMUG#Pk9CH56%B26Ld;e|#NmHD@?mU` zisGA|`vV88{a5Qi0`NI7d2HT!J?cO67~XrYBrfuK_x5{1jfF{?ndY?}@}~P;2f1!M z7?B##)(eskY7+Uaz*JE$)Ri9uEbYQL20|E`j`?dWE-Y6#yK_smzlT%xxQ8qMCgPo{fQ zZqLMl0{XCdw*eH>$;hUUzK>&2xPNmQ8z5YUw#hQBZD600L+70Ywaq$ij=|Y8-Zotd z2J+z-nt`_%7#C%`Nn-Ho&8X$^GxE&vwQ*#_x7dd3!YRbnXKkCm_0~O_=(CRe5}qQc zXwnN(G(3%Vzkl5Su%vJ=VH%0_w+_}XS^bTE7&cvkG<|zORg!d=W$7tQZs^;vTz_! zGinVGx>6JRJgWA&#qF(Emh7j5 zl$EvB7%S> zR+6G<^tb*EGT`Q;rqfx+&j=A6XmuOx*An`##i$2aY24Jf{r(my8>_TO9EU`BS4B9$ zh%RL3XlI|bkyChp-3vc6PwUF6>zmBVC3|stwZ{1{pL=7~qnQoM{Oip;|YJ5_pmOnM+vc)ixoHjFha{Z{FwmJM6&#$I*99k8;^hi^HPU7MM;b+ zMgloN=n)vwSKV&mTRq>6F%$feP0Z!?{B`ZMu1g8_@B!v=s&(g zLHc(#`Or{!+5Vr?+7Yar!P*fB#sd}!bW}Ajx#`SsE`;;rLi8%Gf2OA1_1|%=U+Plg zKt)Q&u{kcQP0p+9{?gH%)JJK-!3OJb@LoV^fLqzI6VP8=FJBhpOPu9XtCp z$9<y>)exD)$Cj8mxaO+KA{XGBMWzAK{fR#pBu?4KxSn!$ee4j)$ zpKa+Z_;lDsGwW?Zje4sQU_;oRXvrSL%UIEZtbt1HBty z-#Wmw%%aAo-~JiOcN6+wgJvE3pQYD*;Ok2iGLlL}_hU3_suJ?6$0anR{%LWAu=F&L zbwllJJw$8lz=kUDIg0ghm+9>LI8nZ3i9PA<#vva4UE!rb2(V+976elt*JyVlT{0%X zCw^D{IX0xR%&Sc>z>l&iF_=xk!StbzAXCozTw~en$(TUrw!wP$7@LL~1yqFq&RYVB zHn5?1vO9tx;P^uDa2bz59ulKwpiI=+Xz|AWz6s3bM4(Q~5_6cduAex{AAP!GC9-^G zZD)8#K)8S27D~-orJigEjDlR)@}|4IfLWp(%2jC{ejc=dps&m!o6ya-vv!&J56qW0 z>;jtox&eMQA%JDW&$bWG-=aBxYY1fLISO#l6#H0Ubxc)|8_32?O4Gri=UPfVm6O$Ts>)}n*Pz;%!#giSrR zYR@=nxka}&*M5pzr!~LVd`5@uwX(BLakmpRB(b%=q*AL`Nj^umov%H)G>%PXc;_e+ zj)-=#FmOlqrDGbGH+*K{XtYTGj;MO(a>*NHBT3JjOpT~V8|ccTI~`?H8|ap%Veecr z1xDBFSp`TGQt?`cL+R^*kMomZS8SLUxV_)c5nGHBx5TwoQxcR%vmQC^t$b!m_Oy|% z-gBdHws~??_|zc8`}HuAhhGkQq(4;SO$3dak7uWdt5IKc-lM$F{h+oYS!d_ZA|EDG zI@u}Uz(M^IMc;Ad;Sr?;$|7w)S2Xmqkg}$%=Ywpmo%kNqhr0}VhXO&IWBHU)mO3#y z|CBq-SG5!MC*!{uYJxK~wjLFAuP*BJv+QOZlZxYhi)v}qpbEKw#OVn=qS;JkIr3*K zwS2cs>8&~UF2$&$^M(`y9~z9`70}<^euJ>nUIZ_zLrZl&Q~Ih6TOJD_5J+u zmLQe9-?rS9*=unKj@QM%>p$mYg6Pj}wpUAg%H8i1Q7`)TMW>_~MH6zfJdH;`vV7K* z(z|<*c5ZxVWJS@w*kkEP%4lCg`XeVPRS>o(yCdq}gvr*28X~U6V0YM~r8xPDRwt{whI5!F<@)GwN90Uf+uNhiQFlsLk6_QOCno#)^l|zs(zA zfez*H==B`+Mnnyy)_Nsy(Cnw~Wd`!bB|U08s-xohQ@@vK)x@rgaMVU95LIk;KXJ&9 zB%Q6A#SmR~Mrp3Rc{G5Osa@p^!d)kR{omLjpd&-96ogESAptiDR5S%`RD2{OI} zHA|H5hla>|UEVLBMY9~;vfr)Q>k~Ojrjhu_^#RS=d|#osr5z;rfi+`f_(70I-dyq!IevS*cdEqNYwJXxtdU`9yuw3k3q9+vm*qv3 zSz`FpO{3mj3Xos3=C}3}XUVvmzbz1I{J^nt{uy42ZoV~?3{R(YvV$P{N6?eu3&TX7 z^`w4T6t3^6EMgN(g(cly$Ey;XO_TFsul3#|68@px&(>R+0z-2SiKHnDYo+A6vGFZi^{BVOA}HP5)mL}ioPmEofa9+=_M%octXO>jWrrhe+1irEfqPd3yL(>k4pnJTHl>RXmXL@o2 zHR1+Cdk=}2ri!4`T}vzMp>~ko%1bb`Ebtv|laV$eO@1fBy5=BnuX(~7ZqnpfKIs1M8fr1@du-l6;^uCdf2^x%xho#mU!@(^qt|S zp)sjdaf4zwJeTarvsY`k{cg&58@%DC$RjhqzJ_VPi@8p^i%scLyO-xtJB0FU_SP+r0={!PVu{*R6`yKjNSDoJ%yFR?4kaTa%#L8xZX4>!& zO*gnW8@b|))Et}CCyLFd7J(`(yDdp3RK^KQH|IUjP6A From 2c8d54b660660b2511a24b7afc68c4afef29c014 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Mon, 13 May 2024 16:52:28 +0100 Subject: [PATCH 08/17] Update pom to more recent SQLite driver version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00c3451e..e0d042fc 100644 --- a/pom.xml +++ b/pom.xml @@ -711,7 +711,7 @@ org.xerial sqlite-jdbc - 3.28.0 + 3.45.3.0 From 83678d3c25a260ccf793105039c915b40e27fda2 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Thu, 16 May 2024 08:29:17 +0100 Subject: [PATCH 09/17] Updates to delphinID and TDisplayFX --- src/PamUtils/PamArrayUtils.java | 21 + src/PamUtils/TxtFileUtils.java | 3 +- src/Resources/css/delphinid_logo01.svg | 114 ----- src/Resources/css/primer-pamguard.css | 8 +- src/dataPlotsFX/TDControlFX.java | 4 +- src/dataPlotsFX/layout/TDDisplayFX.java | 9 +- .../DetectionGroupDisplay.java | 66 ++- .../whistleDDPlot/WhistleSettingsPane.java | 1 - src/pamViewFX/PamGuiFX.java | 3 + src/pamViewFX/fxNodes/PamTabPane.java | 3 +- src/pamViewFX/fxNodes/PamTabPaneSkin.java | 98 ++-- .../fxNodes/flipPane/PamFlipPane.java | 2 +- .../fxNodes/hidingPane/HidingPane.java | 429 ++++++++++-------- .../fxPlotPanes/PamHiddenSidePane.java | 4 +- .../delphinID/DelphinIDTest.java | 171 +------ .../delphinID/DelphinIDTrain.java | 3 + .../delphinID/DelphinIDUtils.java | 330 ++++++++++++++ .../delphinID/DelphinIDWorker.java | 4 +- 18 files changed, 727 insertions(+), 546 deletions(-) delete mode 100644 src/Resources/css/delphinid_logo01.svg create mode 100644 src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTrain.java create mode 100644 src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDUtils.java diff --git a/src/PamUtils/PamArrayUtils.java b/src/PamUtils/PamArrayUtils.java index 672529c3..a4209c74 100644 --- a/src/PamUtils/PamArrayUtils.java +++ b/src/PamUtils/PamArrayUtils.java @@ -411,6 +411,26 @@ public class PamArrayUtils { return new double[] {min, max}; } + + + /** + * Calculate the minimum and maximum value of a 2D array. + * @param arr - the array to find the maximum value of. + * @return the minimum and maximum value in the array + */ + public static float[] minmax(float[][] arr) { + float max = Float.NEGATIVE_INFINITY; + float min = Float.POSITIVE_INFINITY; + + for(int i=0; i - - - - - - - - - - - delphinID - - - - - - diff --git a/src/Resources/css/primer-pamguard.css b/src/Resources/css/primer-pamguard.css index ece247b6..1a2a7415 100644 --- a/src/Resources/css/primer-pamguard.css +++ b/src/Resources/css/primer-pamguard.css @@ -137,7 +137,7 @@ */ /** top-left, top-right, bottom-right, and bottom-left corners, in that order. */ .close-button-right{ - -fx-background-color: -fx-darkbackground-trans; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 0 10 10 0; -fx-border-color: transparent; -fx-border-radius: 0 10 10 0; @@ -145,7 +145,7 @@ .close-button-left{ - -fx-background-color: -fx-darkbackground-trans; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 10 0 0 10; -fx-border-color: transparent; -fx-border-radius: 10 0 0 10; @@ -153,7 +153,7 @@ .close-button-top{ - -fx-background-color: -fx-darkbackground-trans; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 10 10 0 0; -fx-border-color: transparent; -fx-border-radius: 10 10 0 0; @@ -161,7 +161,7 @@ .close-button-bottom{ - -fx-background-color: -fx-darkbackground-trans; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 0 0 10 10; -fx-border-color: transparent; -fx-border-radius: 0 0 10 10; diff --git a/src/dataPlotsFX/TDControlFX.java b/src/dataPlotsFX/TDControlFX.java index 1c46fc4b..493b9fe0 100644 --- a/src/dataPlotsFX/TDControlFX.java +++ b/src/dataPlotsFX/TDControlFX.java @@ -115,10 +115,10 @@ public class TDControlFX extends TDControl implements UserDisplayNodeFX { PamDataBlock dataBlock=this.tdDisplayController.getUserDisplayProcess().getParentDataBlock(); if (TDDataProviderRegisterFX.getInstance().findDataProvider(dataBlock)!=null) dataBlocks.add(dataBlock); if (dataBlock!=null) { - System.out.println("TDControldFX: parent datablock "+dataBlock.getDataName()); +// System.out.println("TDControldFX: parent datablock "+dataBlock.getDataName()); } else{ - System.out.println("TDControldFX: parent datablock null"); +// System.out.println("TDControldFX: parent datablock null"); return dataBlocks; } diff --git a/src/dataPlotsFX/layout/TDDisplayFX.java b/src/dataPlotsFX/layout/TDDisplayFX.java index afbcdb52..6cc2f259 100644 --- a/src/dataPlotsFX/layout/TDDisplayFX.java +++ b/src/dataPlotsFX/layout/TDDisplayFX.java @@ -252,10 +252,11 @@ public class TDDisplayFX extends PamBorderPane { //create the button which shows the hiding panel. Although we get this button from the hiding pane, where to place //it and what colour it is etc has to be set for whatever pane it is to be located in. showButton=hidingControlPane.getShowButton(); - showButton.getStyleClass().add("transparent-button-square"); - showButton.setStyle("-fx-background-radius: 0 0 10 10;"); + hidingControlPane.setShowButtonOpacity(1.0); +// showButton.getStyleClass().add("transparent-button-square"); + showButton.setStyle("-fx-background-radius: 0 0 10 0;"); showButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-down", PamGuiManagerFX.iconSize)); - showButton.setPrefWidth(60); + showButton.setPrefWidth(30); showButton.setMaxHeight(timeAxisSize-20); //create the time axis for the display. @@ -276,7 +277,7 @@ public class TDDisplayFX extends PamBorderPane { StackPane mainGraphPane=new StackPane(); mainGraphPane.getChildren().add(splitPaneHolder); mainGraphPane.getChildren().add(showButton); - StackPane.setAlignment(showButton, Pos.TOP_CENTER); + StackPane.setAlignment(showButton, Pos.TOP_LEFT); this.setCenter(mainGraphPane); diff --git a/src/detectionPlotFX/DetectionGroupDisplay.java b/src/detectionPlotFX/DetectionGroupDisplay.java index 28c416c3..3b9754f2 100644 --- a/src/detectionPlotFX/DetectionGroupDisplay.java +++ b/src/detectionPlotFX/DetectionGroupDisplay.java @@ -18,20 +18,25 @@ import javafx.geometry.Pos; import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TabPane.TabClosingPolicy; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamStackPane; import pamViewFX.fxNodes.PamTabPane; import pamViewFX.fxNodes.hidingPane.HidingPane; +import pamViewFX.fxNodes.utilityPanes.PamToggleSwitch; import pamViewFX.fxStyles.PamStylesManagerFX; /** @@ -201,6 +206,7 @@ public class DetectionGroupDisplay extends PamBorderPane { hidingPane.getShowButton().setGraphic(PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); + hidingPane.setShowButtonOpacity(1.0); //don't want show button to gray out if mouse not over it this.setRight(hidingPane); //bit of a hack but works. hidingPane.showHidePane(false); @@ -219,36 +225,68 @@ public class DetectionGroupDisplay extends PamBorderPane { detectionDisplayHolder = new PamStackPane(); PamTabPane settingsPane = new PamTabPane(); - settingsPane.setTabMinHeight(60); - settingsPane.setMinHeight(60); -// settingsPane.repackTabs(); - + //this has to be before removing the heading button settingsPane.setAddTabButton(false); + settingsPane.setTabMinHeight(60); + settingsPane.setMinHeight(100); + // settingsPane.getStyleClass().add(Styles.TABS_FLOATING); settingsPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); - //settingsPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); - Tab dataTab = new Tab("Data",detectionDisplay.getDataTypePane()); - Tab settingsTab = new Tab("Settings -BLAH",detectionDisplay.getSettingsHolder()); + PamGridPane gridPane = new PamGridPane(); + gridPane.setHgap(5.); + gridPane.setVgap(5.); + gridPane.setPadding(new Insets(0,0,0,5)); + + gridPane.add(new Label("Plot type"), 0, 0); + gridPane.add(detectionDisplay.getDataTypePane(), 1, 0); + + PamToggleSwitch showScrollSwitch = new PamToggleSwitch("Show scroll bar"); + showScrollSwitch.selectedProperty().addListener((obsVal, oldVal, newVal)->{ + //show or hide the scroll bar. + this.setEnableScrollBar(newVal); + }); + showScrollSwitch.setSelected(true); + gridPane.add(showScrollSwitch, 0, 1); + GridPane.setColumnSpan(showScrollSwitch, GridPane.REMAINING); + + Tab dataTab = new Tab("Plot",gridPane); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER); + scrollPane.setContent(detectionDisplay.getSettingsHolder()); + Tab settingsTab = new Tab("Settings", scrollPane); + + //here add the option to show the scroll bar or not. settingsPane.getTabs().add(dataTab); settingsPane.getTabs().add(settingsTab); - hidingPane = new HidingPane(Side.RIGHT, settingsPane, detectionDisplayHolder, layoutType==DISPLAY_COMPACT, 0); + //set the hiding pane + Node icon = PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize); + detectionDisplay.getPlotPane().setHidePane(new PamBorderPane(settingsPane), icon, Side.RIGHT); + + //move the hiding pane button into the top of the tab pane - this makes best + //use of space. + hidingPane = detectionDisplay.getPlotPane().getHidePane(Side.RIGHT); + hidingPane.removeHideButton(); - hidingPane.getHideButton().setMinWidth(40); + settingsPane.setTabStartRegion(hidingPane.getHideButton()); +// hidingPane.getHideButton().getStyleClass().add("close-button-right-trans"); +// hidingPane.getHideButton().setStyle(" -fx-background-radius: 0 0 0 0;"); + hidingPane.setPadding(new Insets(0,0,0,0)); + + hidingPane.getHideButton().prefHeightProperty().bind(settingsPane.tabMinHeightProperty()); + + //set the show button to be slight larger + hidingPane.getShowButton().setPrefHeight(60.); //now everything to pane. detectionDisplayHolder.getChildren().add(detectionDisplay); StackPane.setAlignment(detectionDisplay, Pos.CENTER); //settingsPane.setPadding(new Insets(35,0,0,0)); - - Node icon = PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize); - detectionDisplay.getPlotPane().setHidePane(new PamBorderPane(settingsPane), icon, Side.RIGHT); - hidingPane.getShowButton().setPrefHeight(30); - } //set styles diff --git a/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java b/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java index 0340865e..ebfca3d6 100644 --- a/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java +++ b/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java @@ -6,7 +6,6 @@ import detectionPlotFX.plots.FFTSettingsPane; import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; import javafx.scene.layout.Pane; -import pamViewFX.fxNodes.PamSpinner; import pamViewFX.fxNodes.PamVBox; import whistlesAndMoans.ConnectedRegionDataUnit; diff --git a/src/pamViewFX/PamGuiFX.java b/src/pamViewFX/PamGuiFX.java index 80ee6983..a9d4c678 100644 --- a/src/pamViewFX/PamGuiFX.java +++ b/src/pamViewFX/PamGuiFX.java @@ -166,6 +166,8 @@ public class PamGuiFX extends StackPane implements PamViewInterface { sidePaneContent.setPadding(new Insets(25,5,5,5)); //give quite abit of spacing at the top so that there is room for close button sidePaneContent.setMinWidth(0); hidingSidePane=new HidingPane(Side.RIGHT, sidePaneContent, this, false); + hidingSidePane.setShowButtonOpacity(1.0); + hidingSidePane.showHidePane(false); //create the button which shows the hiding panel. Although we get this button from the hiding pane, where to place @@ -242,6 +244,7 @@ public class PamGuiFX extends StackPane implements PamViewInterface { mainTabPane.setTabEndRegion(showButtonRight); mainTabPane.setTabStartRegion(showButtonLeft); +// mainTabPane.getStyleClass().add(Styles.TABS_FLOATING); mainTabPane.getAddTabButton().setOnAction((value)->{ addPamTab(new TabInfo("Display " + (this.getNumTabs()+1)), null ,true); diff --git a/src/pamViewFX/fxNodes/PamTabPane.java b/src/pamViewFX/fxNodes/PamTabPane.java index 14ab8ab0..654f91e4 100644 --- a/src/pamViewFX/fxNodes/PamTabPane.java +++ b/src/pamViewFX/fxNodes/PamTabPane.java @@ -103,7 +103,8 @@ public class PamTabPane extends TabPane { public void setAddTabButton(boolean addTabButton) { if (this.addTabButton==addTabButton) return; this.addTabButton = addTabButton; - tabPaneSkin = new PamTabPaneSkin(this); +// tabPaneSkin = new PamTabPaneSkin(this); + tabPaneSkin.setAddTabButton(addTabButton); } /** diff --git a/src/pamViewFX/fxNodes/PamTabPaneSkin.java b/src/pamViewFX/fxNodes/PamTabPaneSkin.java index 9b575dc1..c795ac25 100644 --- a/src/pamViewFX/fxNodes/PamTabPaneSkin.java +++ b/src/pamViewFX/fxNodes/PamTabPaneSkin.java @@ -39,13 +39,13 @@ public class PamTabPaneSkin extends TabPaneSkin { * Button which allows a new tab to be added. */ private PamButton addTabButton; - + /** * How many pixels the tab button 'floats' in the header area. */ public double addButtonInsets=3; - - + + public PamTabPaneSkin(PamTabPane tabPane) { super(tabPane); this.pamTabPane=tabPane; @@ -66,7 +66,7 @@ public class PamTabPaneSkin extends TabPaneSkin { //add extra regions to header area headerArea.getChildren().add(addTabButton); - + } /* @@ -82,7 +82,7 @@ public class PamTabPaneSkin extends TabPaneSkin { public void removeTabEndRegion(Region tabRegion){ headerArea.getChildren().remove(tabRegion); } - + /* * Add the tab start region. This sits in the tab header area, either at the left if the tab pane is horizontal or top if the tab pane is vertical. */ @@ -114,23 +114,23 @@ public class PamTabPaneSkin extends TabPaneSkin { * Set the add button in the correct position */ private void layoutAddButton(){ - - //need to careful here. The width and height of the tab pane don'tr swap on rotation i.e. width of pane remains the axis on which tabs are added. - //Weird but guess the java people decided that would make programming easier. - double insetx=0.; - if (pamTabPane.getSide()==Side.TOP || pamTabPane.getSide()==Side.BOTTOM){ - addTabButton.layoutXProperty().setValue(tabContentArea.getWidth()+insetx+headerArea.getPadding().getLeft()+addButtonInsets); - addTabButton.layoutYProperty().setValue(headerArea.getHeight()-tabContentArea.getHeight()+addButtonInsets); - addTabButton.resize(tabContentArea.getHeight()-4, tabContentArea.getHeight()-addButtonInsets*2); - } - else{ - //yep, this makes no sense but works when tabs are vertical. - addTabButton.layoutXProperty().setValue(headerArea.getWidth()-tabContentArea.getWidth()-tabContentArea.getHeight()-headerArea.getPadding().getTop()); - addTabButton.layoutYProperty().setValue(headerArea.getHeight()-tabContentArea.getHeight()); - addTabButton.resize(tabContentArea.getHeight(),tabContentArea.getHeight()); - } - -// System.out.println("tab Content area width: " +tabContentArea.getWidth()+" tab Content area height: "+tabContentArea.getHeight()+ " addTabButton.layoutYProperty()" +addTabButton.layoutYProperty().getValue()); + + //need to careful here. The width and height of the tab pane don'tr swap on rotation i.e. width of pane remains the axis on which tabs are added. + //Weird but guess the java people decided that would make programming easier. + double insetx=0.; + if (pamTabPane.getSide()==Side.TOP || pamTabPane.getSide()==Side.BOTTOM){ + addTabButton.layoutXProperty().setValue(tabContentArea.getWidth()+insetx+headerArea.getPadding().getLeft()+addButtonInsets); + addTabButton.layoutYProperty().setValue(headerArea.getHeight()-tabContentArea.getHeight()+addButtonInsets); + addTabButton.resize(tabContentArea.getHeight()-4, tabContentArea.getHeight()-addButtonInsets*2); + } + else{ + //yep, this makes no sense but works when tabs are vertical. + addTabButton.layoutXProperty().setValue(headerArea.getWidth()-tabContentArea.getWidth()-tabContentArea.getHeight()-headerArea.getPadding().getTop()); + addTabButton.layoutYProperty().setValue(headerArea.getHeight()-tabContentArea.getHeight()); + addTabButton.resize(tabContentArea.getHeight(),tabContentArea.getHeight()); + } + + // System.out.println("tab Content area width: " +tabContentArea.getWidth()+" tab Content area height: "+tabContentArea.getHeight()+ " addTabButton.layoutYProperty()" +addTabButton.layoutYProperty().getValue()); } @@ -153,24 +153,24 @@ public class PamTabPaneSkin extends TabPaneSkin { double startHeaderSize; double endHeadersize; if (pamTabPane.getSide()==Side.TOP || pamTabPane.getSide()==Side.BOTTOM){ - startHeaderSize = (pamTabPane.getTabStartRegion()==null) ? in.getLeft() : getRegionWidth(pamTabPane.getTabStartRegion()); - endHeadersize = (pamTabPane.getTabEndRegion()==null) ? in.getRight(): getRegionWidth(pamTabPane.getTabEndRegion()); - //set padding of tab header to make space for any added buttons - headerArea.setPadding(new Insets( - in.getTop(), - endHeadersize, - in.getBottom(), - startHeaderSize)); + startHeaderSize = (pamTabPane.getTabStartRegion()==null) ? in.getLeft() : getRegionWidth(pamTabPane.getTabStartRegion()); + endHeadersize = (pamTabPane.getTabEndRegion()==null) ? in.getRight(): getRegionWidth(pamTabPane.getTabEndRegion()); + //set padding of tab header to make space for any added buttons + headerArea.setPadding(new Insets( + in.getTop(), + endHeadersize, + in.getBottom(), + startHeaderSize)); } else { - startHeaderSize = (pamTabPane.getTabStartRegion()==null) ? in.getTop() : pamTabPane.getTabStartRegion().getHeight(); - endHeadersize = (pamTabPane.getTabEndRegion()==null) ? in.getBottom(): pamTabPane.getTabEndRegion().getHeight(); - //set padding of tab header to make space for any added buttons - headerArea.setPadding(new Insets( - startHeaderSize, - in.getRight(), - endHeadersize, - in.getLeft())); + startHeaderSize = (pamTabPane.getTabStartRegion()==null) ? in.getTop() : pamTabPane.getTabStartRegion().getHeight(); + endHeadersize = (pamTabPane.getTabEndRegion()==null) ? in.getBottom(): pamTabPane.getTabEndRegion().getHeight(); + //set padding of tab header to make space for any added buttons + headerArea.setPadding(new Insets( + startHeaderSize, + in.getRight(), + endHeadersize, + in.getLeft())); } //layout additional header regions for start @@ -210,14 +210,14 @@ public class PamTabPaneSkin extends TabPaneSkin { //System.out.println("Hello "+hello.getWidth()+" "+headerArea.getWidth()+ " "+tabContentArea.getWidth()); } - + private double getRegionWidth(Region region){ double regionWidth=50; if (region.getMinWidth()>0) regionWidth=region.getMinWidth(); if (region.getPrefWidth()>0) regionWidth=region.getPrefWidth(); return regionWidth; } - + private double getRegionHeight(Region region){ double regionHeight=50; if (region.getMinHeight()>0) regionHeight=region.getMinHeight(); @@ -240,7 +240,7 @@ public class PamTabPaneSkin extends TabPaneSkin { public double getHeaderHeight() { return headerArea.getHeight(); } - + /** * Get the height property of the header. * @return the height property of the header. @@ -248,6 +248,20 @@ public class PamTabPaneSkin extends TabPaneSkin { public ReadOnlyDoubleProperty getHeaderHeightProperty(){ return headerArea.heightProperty(); } - + + public void setAddTabButton(boolean addTabButton2) { + if (addTabButton2){ + if (!headerArea.getChildren().contains(addTabButton)) { + headerArea.getChildren().remove(addTabButton); + layoutAddButton(); + } + } + else { + headerArea.getChildren().remove(addTabButton); + } + + + } + } diff --git a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java index 691248ba..756748bd 100644 --- a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java +++ b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java @@ -164,7 +164,7 @@ public class PamFlipPane extends FlipPane { backButton.setStyle("-fx-background-radius: 0 5 5 0; -fx-border-radius: 0 5 5 0; -fx-background-color: -color-accent-6"); backButton.setOnAction((action)->{ - System.out.println("FLIP BACK TO FRONT"); +// System.out.println("FLIP BACK TO FRONT"); this.backButtonResponse.setValue(OK_BACK_BUTTON); this.flipToFront(); }); diff --git a/src/pamViewFX/fxNodes/hidingPane/HidingPane.java b/src/pamViewFX/fxNodes/hidingPane/HidingPane.java index b60a5429..26a19fc3 100644 --- a/src/pamViewFX/fxNodes/hidingPane/HidingPane.java +++ b/src/pamViewFX/fxNodes/hidingPane/HidingPane.java @@ -13,12 +13,14 @@ import javafx.util.Duration; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; +import javafx.animation.Animation.Status; import javafx.beans.binding.DoubleBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.event.ActionEvent; import javafx.event.EventHandler; +import javafx.geometry.Insets; /** * Hiding pane which can be added to any node. @@ -32,6 +34,12 @@ import javafx.event.EventHandler; */ public class HidingPane extends StackPane { + /** + * The opacity of the button when the mouse is outside the show button + */ + private double showButtonOpacity = 0.25; + + /** * Which side the pane is hiding on. */ @@ -52,7 +60,7 @@ public class HidingPane extends StackPane { * width when shown and when aligned top.button it is the height shown. */ private double expandedSize=300; - + /** * Show/Hide animation time in milliseconds. */ @@ -72,7 +80,7 @@ public class HidingPane extends StackPane { * Button which sits in the hiding pane. Pressed to hide pane. */ private PamButton hideButton; - + /** * The button which shows the pane. This button isn't actually on the pane. */ @@ -93,22 +101,22 @@ public class HidingPane extends StackPane { * The translate property, depends on side and whether the hiding panel is overlaid or moves it's parent node. */ private DoubleProperty paneTanslateProperty; - + /** * The binding property for the show button. Only used when the panel is overlayed on another panel. */ private DoubleBinding buttonTranslateProperty; - + /** * Indicates whether the panel is showing or not; */ private BooleanProperty showing=new SimpleBooleanProperty(false); - + /** * If true then the pane only shows once the hide pane animation has finished */ private boolean visibleImmediatly=true; - + /** * The default size of the icons */ @@ -119,7 +127,7 @@ public class HidingPane extends StackPane { * */ private double offset=0; - + /** * Constructor top create a new hiding pane. * @param side - the side the hiding pane appears from. @@ -134,14 +142,14 @@ public class HidingPane extends StackPane { this.holderPane=holderPane; this.overlay=overlay; this.offset=offset; - -// //CSS styling -// this.getStylesheets().add(getClass().getResource("pamSettingsCSS.css").toExternalForm()); -// this.getStyleClass().add("pane"); - + + // //CSS styling + // this.getStylesheets().add(getClass().getResource("pamSettingsCSS.css").toExternalForm()); + // this.getStyleClass().add("pane"); + //set the size the panel will expand to setDefaultExpandedSize(hidePane); - + //set the translate property and side index. setTranlsateProperty(); @@ -150,36 +158,36 @@ public class HidingPane extends StackPane { styleHideButton(hideButton); hideButton.setOnAction(new HideButtonPressed()); hideButton.setVisible(false); -// hideButton.setStyle("close-button-right"); + // hideButton.setStyle("close-button-right"); //create a show button. The show button is not displayed on the hiding panel but caqn be used in other panels showButton=createShowButton(true); - + //if overlay true bind the show button to the side pane. if (overlay) showButton.translateXProperty().bind(buttonTranslateProperty); - + //set up the animation for panel showing/hiding setAnimation(); - + holderPane.setMinHeight(5); //needed for hiding panes to appear outside holder pane. - - -// this.setMinHeight(500); -// holderPane.heightProperty().addListener((oldVall, newVal, obsVsal)->{ -// if (this.getHeight()<500){ -// this.setLayoutY(0); -// this.setMinHeight(500); -// holderPane.setStyle("-fx-background: red;"); -// } -// System.out.println("HidingPane: LayoutY: "+this.getLayoutY() + " height "+this.getHeight()); -// }); - -// PamBorderPane mainPane=new PamBorderPane(); -// mainPane.setCenter(hidePane); - + + + // this.setMinHeight(500); + // holderPane.heightProperty().addListener((oldVall, newVal, obsVsal)->{ + // if (this.getHeight()<500){ + // this.setLayoutY(0); + // this.setMinHeight(500); + // holderPane.setStyle("-fx-background: red;"); + // } + // System.out.println("HidingPane: LayoutY: "+this.getLayoutY() + " height "+this.getHeight()); + // }); + + // PamBorderPane mainPane=new PamBorderPane(); + // mainPane.setCenter(hidePane); + this.getChildren().add(hidePane); setHideButtonPos(side); - + } @@ -193,36 +201,36 @@ public class HidingPane extends StackPane { public HidingPane(final Side side, Region hidePane, Pane holderPane, boolean overlay) { this(side, hidePane, holderPane, overlay, 0); } - + public void setShowButton(PamButton showButton) { this.showButton = showButton; } - + public boolean isHorizontal(){ if (side==Side.TOP || side==Side.BOTTOM) return true; return false; } - - + + public void resetHideAnimation(){ //set the size the panel will expand to setDefaultExpandedSize(hidePane); - + //set the translate property and side index. setTranlsateProperty(); //set up the animation for panel showing/hiding setAnimation(); } - + /** * Style the button to have an image of arrow. * @param button-button to style */ public void styleHideButton(PamButton button){ - styleHideButton(button, side); + styleHideButton(button, side); } - + /** * Style the button to have an image of arrow. * @param button-button to style @@ -232,34 +240,34 @@ public class HidingPane extends StackPane { switch (side){ case RIGHT: //button.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/Resources/SidePanelShow2.png")))); -// button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); + // button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); button.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-right", PamGuiManagerFX.iconSize)); button.getStyleClass().add("close-button-right"); break; case LEFT: //button.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/Resources/SidePanelHide2.png")))); -// button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_LEFT, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); + // button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_LEFT, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); button.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-left", PamGuiManagerFX.iconSize)); button.getStyleClass().add("close-button-left"); break; case TOP: //button.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/Resources/SidePanelUp2.png")))); - -// button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_UP, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); + + // button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_UP, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); button.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-up", PamGuiManagerFX.iconSize)); button.setPrefWidth(60); //horizontal buttons are slightly wider button.getStyleClass().add("close-button-top"); break; case BOTTOM: //utton.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/Resources/SidePanelDown2.png")))); -// button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); + // button.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); button.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-down", PamGuiManagerFX.iconSize)); button.setPrefWidth(60); //horizontal buttons are slightly wider button.getStyleClass().add("close-button-bottom"); break; } } - + /** * Set the default expanded size for the hiding panel. If the pane has a preferred size then uses this but if not then uses * the field expandedSize to set expanded Size; @@ -267,11 +275,11 @@ public class HidingPane extends StackPane { */ private void setDefaultExpandedSize(Region hidePane){ boolean isHorizontal=isHorizontal(); - + //check if the panel has a preferred width or height depending on side position. If so set the expanded size to that; if (!isHorizontal && hidePane.getPrefWidth()>0) expandedSize=hidePane.getPrefWidth(); else if (isHorizontal && hidePane.getPrefHeight()>0) expandedSize=hidePane.getPrefHeight(); - + //now set the correct preferred width for the panel if (!isHorizontal){ hidePane.setPrefWidth(expandedSize); @@ -285,7 +293,7 @@ public class HidingPane extends StackPane { this.setMinWidth(0); this.setMinHeight(0); } - + /** * If the preferred size is set for the hiding pane then the expanded size, the animation, the show * button binding property and position of the pane must be reset. @@ -313,9 +321,9 @@ public class HidingPane extends StackPane { //set the correct translation for the pane if (showing.get()) paneTanslateProperty.set(overlay ? 0 : expandedSize+offset); else paneTanslateProperty.set(overlay ? (expandedSize+offset)*sideIndex: 0); - + } - + /** * Create a blank button which contains all the functionality to open or close the hiding panel, including the ability @@ -328,75 +336,95 @@ public class HidingPane extends StackPane { private PamButton createShowButton(final boolean show){ + final PamButton pamButton=new PamButton(); + pamButton.setOnMousePressed(new EventHandler() { - @Override public void handle(MouseEvent mouseEvent) { - // record a delta distance for the drag and drop operation. - dragX =mouseEvent.getSceneX(); - dragY =mouseEvent.getSceneY(); - } + @Override public void handle(MouseEvent mouseEvent) { + // record a delta distance for the drag and drop operation. + dragX =mouseEvent.getSceneX(); + dragY =mouseEvent.getSceneY(); + } }); + pamButton.setOnMouseReleased(new EventHandler() { - @Override public void handle(MouseEvent mouseEvent) { - if (distance==0) return; - distance=sideIndex*distance; -// System.out.println("Mouse released: HidePanel distance: "+distance); - if (!overlay) distance=Math.abs(distance); - //need to see where the drag has ended-if greater than 50% then open but if less then close. - if (!isHorizontal()){ - if (distance=expandedSize/2) showHidePane(overlay? false : true); - } - else{ - if (distance=expandedSize/2) showHidePane(overlay? false : true); - } - //reset the distance - distance=0; - } + @Override public void handle(MouseEvent mouseEvent) { + if (distance==0) return; + distance=sideIndex*distance; + // System.out.println("Mouse released: HidePanel distance: "+distance); + if (!overlay) distance=Math.abs(distance); + //need to see where the drag has ended-if greater than 50% then open but if less then close. + if (!isHorizontal()){ + if (distance=expandedSize/2) showHidePane(overlay? false : true); + } + else{ + if (distance=expandedSize/2) showHidePane(overlay? false : true); + } + //reset the distance + distance=0; + } }); + pamButton.setOnMouseDragged(new EventHandler() { - @Override public void handle(MouseEvent mouseEvent) { - if (visibleImmediatly) hidePane.setVisible(true); - else hidePane.setVisible(false); - // hideButton.setVisible(true); - /** - * Work out the distance the panel is to be dragged; - */ - if (!isHorizontal()){ + @Override public void handle(MouseEvent mouseEvent) { + if (visibleImmediatly) hidePane.setVisible(true); + else hidePane.setVisible(false); + // hideButton.setVisible(true); + /** + * Work out the distance the panel is to be dragged; + */ + if (!isHorizontal()){ if (!show) distance=(mouseEvent.getSceneX()-dragX); else distance=(mouseEvent.getSceneX()-dragX)+sideIndex*expandedSize; if (!overlay) distance=expandedSize-sideIndex*distance; - } - else{ + } + else{ if (!show) distance=(mouseEvent.getSceneY()-dragY); else distance=(mouseEvent.getSceneY()-dragY)-sideIndex*expandedSize; if (!overlay) distance=expandedSize+sideIndex*distance; - } -// if (show && Math.abs(distance)>expandedSize) return; - - translatePanel(distance); - } - }); - pamButton.setOnMouseEntered(new EventHandler() { - @Override public void handle(MouseEvent mouseEvent) { - } - }); - pamButton.setOnMouseClicked(new EventHandler() { - @Override public void handle(MouseEvent mouseEvent) { - //System.out.println("Mouse clicked"); - showHidePane(show); - } - }); - pamButton.addEventHandler(ActionEvent.ACTION,new EventHandler() { - @Override - public void handle(ActionEvent e) { - //System.out.println("Button event"); - showHidePane(show); - } + } + // if (show && Math.abs(distance)>expandedSize) return; + + translatePanel(distance); + } }); + pamButton.setOnMouseEntered(new EventHandler() { + @Override public void handle(MouseEvent mouseEvent) { +// System.out.println("HidingPane.showButton - mouse entered"); + pamButton.setOpacity(1.0); + pamButton.setPadding(new Insets(2.,2.,2.,2.)); + } + }); + + pamButton.setOnMouseExited(new EventHandler() { + @Override public void handle(MouseEvent mouseEvent) { +// System.out.println("HidingPane.showButton - mouse exited"); + if (show) pamButton.setOpacity(showButtonOpacity); + pamButton.setPadding(new Insets(0.,0.,0.,0.)); + } + }); + +// pamButton.setOnMouseClicked(new EventHandler() { +// @Override public void handle(MouseEvent mouseEvent) { +// System.out.println("HidingPane.showButton - mouse clicked"); +// showHidePane(show); +// } +// }); + + pamButton.addEventHandler(ActionEvent.ACTION,new EventHandler() { + @Override + public void handle(ActionEvent e) { + System.out.println("HidingPane.showButton - action event clicked"); + showHidePane(show); + } + }); + + if (show) pamButton.setOpacity(showButtonOpacity); + return pamButton; } @@ -425,21 +453,21 @@ public class HidingPane extends StackPane { break; } } - + /** * Remove the hide button from the hiding pane. For example, used if the pane is non user controllable. */ public void removeHideButton(){ this.getChildren().remove(hideButton); } - + /** * The hiding panel will have a different side Index and translateProperty depending on what the side and overlay value combination is. * This function sets the correct values for sideIndex and translateProperty which are used in opening/closing animations and dragging the panel to an open or c * closed position. */ private void setTranlsateProperty(){ - + /** * Figure out the correct index and property. The sideIndex is used to tell the animation what direction to * move the animation in. The property tells the panel either to translate in the x or y direction or @@ -481,7 +509,7 @@ public class HidingPane extends StackPane { break; } } - + /** * Create a time line to show or hide a pane. * @param paneTanslateProperty -the translate property e.g. whether to change the preferred height/width, max height/width etc. @@ -494,18 +522,18 @@ public class HidingPane extends StackPane { // Animation for scroll SHOW. timeLine.setCycleCount(1); //defines the number of cycles for this animation final KeyValue kvDwn = new KeyValue(paneTanslateProperty, newSize); -// final KeyValue kvDwn = new KeyValue(translateProperty, expandedSize); + // final KeyValue kvDwn = new KeyValue(translateProperty, expandedSize); final KeyFrame kfDwn = new KeyFrame(Duration.millis(duration), kvDwn); timeLine.getKeyFrames().add(kfDwn); return timeLine; } - + /** * Set up the animation that moves the hiding panel to show and to hide. translate property and sideIndex must be set * before calling this function. */ private void setAnimation(){ - + //Initially hiding the pane paneTanslateProperty.set((expandedSize)*sideIndex); //+for right @@ -514,23 +542,23 @@ public class HidingPane extends StackPane { timeLineShow = createAnimation(paneTanslateProperty, overlay ? 0-offset : expandedSize +offset , duration); // Animation for scroll HIDE timeLineHide = createAnimation(paneTanslateProperty, overlay ? (expandedSize +offset)*sideIndex: 0-offset , duration); - + timeLineShow.setOnFinished(new EventHandler(){ - @Override - public void handle(ActionEvent arg0) { - showFinished(); - } - }); + @Override + public void handle(ActionEvent arg0) { + showFinished(); + } + }); timeLineHide.setOnFinished(new EventHandler(){ - @Override - public void handle(ActionEvent arg0) { - hideFinished(); - } - }); - + @Override + public void handle(ActionEvent arg0) { + hideFinished(); + } + }); + } - + /** * Called whenever the hid animation has finished. */ @@ -540,12 +568,12 @@ public class HidingPane extends StackPane { hidePane.setVisible(false); hideButton.setVisible(false); showButton.toFront(); -// //TODO-delete -// System.out.println("ShowButton Size:"+showButton.getLayoutX() +" tranlateX: "+showButton.getTranslateX() + -// " tranlateY: "+showButton.getTranslateY()+" show button layoutX "+ showButton.getLayoutX()+ " " -// +showButton.getLayoutY()+ "show button width: "+showButton.getWidth()); + // //TODO-delete + // System.out.println("ShowButton Size:"+showButton.getLayoutX() +" tranlateX: "+showButton.getTranslateX() + + // " tranlateY: "+showButton.getTranslateY()+" show button layoutX "+ showButton.getLayoutX()+ " " + // +showButton.getLayoutY()+ "show button width: "+showButton.getWidth()); } - + /** * Called whenever the show animation has finished. */ @@ -554,7 +582,7 @@ public class HidingPane extends StackPane { hidePane.setVisible(true); hideButton.setVisible(true); } - + /** * Move the hiding panel to show or hide a certain distance. * @param distance. The distance in pixels to move the panel. Positive to open the panel and negative to move the panel towards closed. @@ -562,12 +590,12 @@ public class HidingPane extends StackPane { public void translatePanel(double distance){ paneTanslateProperty.set(distance); } - + /** * Show or hide the pane. * @param showing- */ - public void showHidePane(boolean show){ + public synchronized void showHidePane(boolean show){ if (show) { //the pref size may have been changed...hide pane shoulf follow this size. showing.setValue(true); @@ -575,6 +603,11 @@ public class HidingPane extends StackPane { //hideButton.setVisible(true); //System.out.println("HidingPane: Open Hide Pane"); //open the panel + if (timeLineShow.getStatus()==Status.RUNNING) { + //stops the issue with the hiding pane freezing. + return; + } + timeLineShow.play(); } else{ @@ -585,7 +618,7 @@ public class HidingPane extends StackPane { timeLineHide.play(); } } - + /** * Show the hide pane * @param show - tru to opne the pane. False to close the pnae @@ -604,7 +637,7 @@ public class HidingPane extends StackPane { } } } - + /** * Called whenever the pin button is pressed. * @author Jamie Macaulay @@ -624,7 +657,7 @@ public class HidingPane extends StackPane { public PamButton getHideButton() { return hideButton; } - + /** * Get the button which shows the hide pane. This is generally not on the pane itself but needs to be * added to another pane. @@ -649,7 +682,7 @@ public class HidingPane extends StackPane { public void setDuration(long duration) { this.duration = duration; } - + /** * Get the pane which contains this hiding pane. * @return the pane the hiding pane is located in. @@ -657,7 +690,7 @@ public class HidingPane extends StackPane { public Pane getHolderPane() { return holderPane; } - + /** * Get the timeline which opens hiding tab pane. * @return TimelIne which opens the hiding pane. @@ -673,7 +706,7 @@ public class HidingPane extends StackPane { public Timeline getTimeLineHide() { return timeLineHide; } - + /** * Check whether the hide pane shows content at the start of the hide animation or end. @@ -682,7 +715,7 @@ public class HidingPane extends StackPane { public boolean isVisibleImmediatly() { return visibleImmediatly; } - + /** * Set whether the hide pane shows content at the start of the hide animation or end. * @param true if shows the pane at the start of the animation. @@ -690,7 +723,7 @@ public class HidingPane extends StackPane { public void setVisibleImmediatly(boolean visibleImmediatly) { this.visibleImmediatly = visibleImmediatly; } - + /** * The showing property for the hiding pane. * @return the showing property @@ -698,53 +731,71 @@ public class HidingPane extends StackPane { public BooleanProperty showingProperty(){ return this.showing; } + + /** + * Get the opacity of the show button when the mouse is outside the button. + * @return the opacity of the show button. + */ + public double getShowButtonOpacity() { + return showButtonOpacity; + } -// hideSidebar.onFinishedProperty().set(new EventHandler() { -// @Override public void handle(ActionEvent actionEvent) { -// setVisible(false); -// controlButton.setText("Show"); -// controlButton.getStyleClass().remove("hide-left"); -// controlButton.getStyleClass().add("show-right"); -// } -//}); -// -// -//showSidebar.onFinishedProperty().set(new EventHandler() { -// @Override public void handle(ActionEvent actionEvent) { -// controlButton.setText("Collapse"); -// controlButton.getStyleClass().add("hide-left"); -// controlButton.getStyleClass().remove("show-right"); -// } -//}); - -// // create an animation to hide sidebar. -// final Animation hideSidebar = new Transition() { -// { setCycleDuration(Duration.millis(50)); } -// protected void interpolate(double frac) { -// final double curWidth = expandedWidth * (1.0 - frac); -// setPrefWidth(curWidth); -// setTranslateX(curWidth); -// } -// }; -// -// // create an animation to show a sidebar. -// final Animation showSidebar = new Transition() { -// { setCycleDuration(Duration.millis(duration)); } -// protected void interpolate(double frac) { -// final double curWidth = expandedWidth * frac; -// setPrefWidth(curWidth); -// setTranslateX(expandedWidth-curWidth); -// } -// }; - -// if (showSidebar.statusProperty().get() == Animation.Status.STOPPED && hideSidebar.statusProperty().get() == Animation.Status.STOPPED) { -// if (isVisible()) { -// hideSidebar.play(); -// } else { -// setVisible(true); -// showSidebar.play(); -// } -// } - + /** + * Set the opacity of the show button when the mouse is outside. + * @return the opacity of the show button - set to 1.0 for no chnage when mouse outside the button. + */ + public void setShowButtonOpacity(double showButtonOpacity) { + this.showButtonOpacity = showButtonOpacity; + this.showButton.setOpacity(showButtonOpacity); + } + + + // hideSidebar.onFinishedProperty().set(new EventHandler() { + // @Override public void handle(ActionEvent actionEvent) { + // setVisible(false); + // controlButton.setText("Show"); + // controlButton.getStyleClass().remove("hide-left"); + // controlButton.getStyleClass().add("show-right"); + // } + //}); + // + // + //showSidebar.onFinishedProperty().set(new EventHandler() { + // @Override public void handle(ActionEvent actionEvent) { + // controlButton.setText("Collapse"); + // controlButton.getStyleClass().add("hide-left"); + // controlButton.getStyleClass().remove("show-right"); + // } + //}); + + // // create an animation to hide sidebar. + // final Animation hideSidebar = new Transition() { + // { setCycleDuration(Duration.millis(50)); } + // protected void interpolate(double frac) { + // final double curWidth = expandedWidth * (1.0 - frac); + // setPrefWidth(curWidth); + // setTranslateX(curWidth); + // } + // }; + // + // // create an animation to show a sidebar. + // final Animation showSidebar = new Transition() { + // { setCycleDuration(Duration.millis(duration)); } + // protected void interpolate(double frac) { + // final double curWidth = expandedWidth * frac; + // setPrefWidth(curWidth); + // setTranslateX(expandedWidth-curWidth); + // } + // }; + + // if (showSidebar.statusProperty().get() == Animation.Status.STOPPED && hideSidebar.statusProperty().get() == Animation.Status.STOPPED) { + // if (isVisible()) { + // hideSidebar.play(); + // } else { + // setVisible(true); + // showSidebar.play(); + // } + // } + } \ No newline at end of file diff --git a/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java b/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java index bee30dfa..4aaf312e 100644 --- a/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java +++ b/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java @@ -213,9 +213,7 @@ public class PamHiddenSidePane extends PamStackPane { break; } - //make show button same height as hide button -// showButton.prefHeightProperty().bind(hidingPane.getHideButton().heightProperty()); - //translate it so it sits slightly below the top of the pane. + //set the location of the show button to be in the middle of the pane showButton.translateYProperty().bind(displayPane.heightProperty().divide(2).subtract(showButton.heightProperty().divide(2))); hidingPane.getHideButton().translateYProperty().setValue(yPos); diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java index 64d51211..722d4cb3 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -27,177 +27,14 @@ import whistlesAndMoans.AbstractWhistleDataUnit; */ public class DelphinIDTest { - public static DelphinIDWorkerTest prepDelphinIDModel(String modelPath) { - //create the delphinID worker. - DelphinIDWorkerTest delphinIDWorker = new DelphinIDWorkerTest(); - - StandardModelParams params = new StandardModelParams(); - params.modelPath = modelPath; - - //prepare the model - delphinIDWorker.prepModel(params, null); - - return delphinIDWorker; - } - - - /** - * Load whistle contours from a MAT file. () - * - * @param filePath - the file path. - * - * @return a list of whistle contour objects from the mat file. - */ - public static ArrayList getWhistleContoursMAT(String filePath){ - - ArrayList contours = new ArrayList(); - - // SegmenterDetectionGroup segmenterDetectionGroup = new SegmenterDetectionGroup(0, 0, 0, 0); - - // Read scalar from nested struct - try { - Mat5File matFile = Mat5.readFromFile(filePath); - Struct whistlesStruct = matFile.getStruct("whistles"); - - double fftLen = matFile.getMatrix("fftlen").getDouble(0); - double fftHop = matFile.getMatrix("ffthop").getDouble(0); - double sampleRate = matFile.getMatrix("samplerate").getDouble(0); - - for (int i=0; i< whistlesStruct.getNumElements(); i++) { - DataUnitBaseData basicData = new DataUnitBaseData(); - - long timeMillis = ((Matrix)whistlesStruct.get("millis", i)).getLong(0); - basicData.setTimeMilliseconds(timeMillis); - - long sampleDuration = ((Matrix)whistlesStruct.get("sampleDuration", i)).getLong(0); - basicData.setSampleDuration(sampleDuration); - - basicData.setMillisecondDuration(1000.*(sampleDuration/sampleRate)); - - int channelMap = ((Matrix)whistlesStruct.get("channelMap", i)).getInt(0); - basicData.setChannelBitmap(channelMap); - - long uid = ((Matrix)whistlesStruct.get("UID", i)).getLong(0); - basicData.setUID(uid); - - long startSample = ((Matrix)whistlesStruct.get("startSample", i)).getLong(0); - basicData.setStartSample(startSample); - - int nSlices = ((Matrix)whistlesStruct.get("nSlices", i)).getInt(0); - - double[] freq = new double[nSlices]; - double[] times = new double[nSlices]; - - Matrix contourStruct = whistlesStruct.getMatrix("contour", i); - for (int j=0; j segmentWhsitleData(ArrayList whistles, long dataStartMillis, - double segLen, double segHop){ - - ArrayList group = new ArrayList(); - - //find the maximum whistle time - long maxTime = Long.MIN_VALUE; - long endTime = 0; - for (AbstractWhistleDataUnit whislte: whistles) { - endTime = (long) (whislte.getTimeMilliseconds()+whislte.getDurationInMilliseconds()); - if (endTime>maxTime) maxTime=endTime; - } - - long segStart = dataStartMillis; - long segEnd = (long) (segStart+segLen); - - long whistleStart; - long whistleEnd; - SegmenterDetectionGroup whistleGroup; - while (segStart=segStart && whistleStart=segStart && whistleEnd dataUnits, float sampleRate, int iChan){ + float[][][] data = super.dataUnits2ModelInput(dataUnits, sampleRate, iChan); this.lastModelInput = data; @@ -237,10 +74,10 @@ public class DelphinIDTest { MatFile matFile = Mat5.newMatFile(); //get the whislte contours form a .mat file. - ArrayList whistleContours = getWhistleContoursMAT(whistleContourPath); + ArrayList whistleContours = DelphinIDUtils.getWhistleContoursMAT(whistleContourPath); //segment the whistle detections - ArrayList segments = segmentWhsitleData(whistleContours, (long) (dataStartMillis+(9.565*1000.)), + ArrayList segments = DelphinIDUtils.segmentWhsitleData(whistleContours, (long) (dataStartMillis+(9.565*1000.)), segLen, segHop); for (int i=0; i getWhistleContoursMAT(String filePath){ + + ArrayList contours = new ArrayList(); + + // SegmenterDetectionGroup segmenterDetectionGroup = new SegmenterDetectionGroup(0, 0, 0, 0); + + // Read scalar from nested struct + try { + Mat5File matFile = Mat5.readFromFile(filePath); + + Struct whistlesStruct = matFile.getStruct("whistles"); + + double fftLen = matFile.getMatrix("fftlen").getDouble(0); + double fftHop = matFile.getMatrix("ffthop").getDouble(0); + double sampleRate = matFile.getMatrix("samplerate").getDouble(0); + + return getWhistleContoursMAT(whistlesStruct, fftLen, fftHop, sampleRate); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return contours; + } + + + /** + * Load whistle contours from a MATLAB struct + + * @param whistlesStruct - a struct containing a list of whistle contours + * @param fftLen- the fft length in samples + * @param fftHop - the fft hop in samples. + * @param sampleRate - the sample rate in samples per second. + * @return a list of whistle contour objects from the struct. + */ + public static ArrayList getWhistleContoursMAT(Struct whistlesStruct, double fftLen, double fftHop, double sampleRate){ + + ArrayList contours = new ArrayList(); + + + for (int i=0; i< whistlesStruct.getNumElements(); i++) { + DataUnitBaseData basicData = new DataUnitBaseData(); + + long timeMillis = ((Matrix)whistlesStruct.get("millis", i)).getLong(0); + basicData.setTimeMilliseconds(timeMillis); + + long sampleDuration = ((Matrix)whistlesStruct.get("sampleDuration", i)).getLong(0); + basicData.setSampleDuration(sampleDuration); + + basicData.setMillisecondDuration(1000.*(sampleDuration/sampleRate)); + + int channelMap = ((Matrix)whistlesStruct.get("channelMap", i)).getInt(0); + basicData.setChannelBitmap(channelMap); + + long uid = ((Matrix)whistlesStruct.get("UID", i)).getLong(0); + basicData.setUID(uid); + + long startSample = ((Matrix)whistlesStruct.get("startSample", i)).getLong(0); + basicData.setStartSample(startSample); + + int nSlices = ((Matrix)whistlesStruct.get("nSlices", i)).getInt(0); + + double[] freq = new double[nSlices]; + double[] times = new double[nSlices]; + + Matrix contourStruct = whistlesStruct.getMatrix("contour", i); + for (int j=0; j segmentWhsitleData(ArrayList whistles, long dataStartMillis, + double segLen, double segHop){ + + ArrayList group = new ArrayList(); + + //find the maximum whistle time + long maxTime = Long.MIN_VALUE; + long endTime = 0; + for (AbstractWhistleDataUnit whislte: whistles) { + endTime = (long) (whislte.getTimeMilliseconds()+whislte.getDurationInMilliseconds()); + if (endTime>maxTime) maxTime=endTime; + } + + long segStart = dataStartMillis; + long segEnd = (long) (segStart+segLen); + + long whistleStart; + long whistleEnd; + SegmenterDetectionGroup whistleGroup; + while (segStart=segStart && whistleStart=segStart && whistleEnd> contourData = TxtFileUtils.importCSVData(csvFile); + ArrayList whistles = getWhistleContoursMAT(whistlesStruct, fftLen, fftHop, sampleRate); + + //segment the whistle detections + ArrayList segments = DelphinIDUtils.segmentWhsitleData(whistles, whistles.get(0).getTimeMilliseconds(), segLen, segHop); + + float[][][] images = worker.dataUnits2ModelInput(segments, (float) sampleRate, 0); + + float[][] image; + BufferedImage bfImage; + for (int k=0; k csvFiles = filelist.getFileList(listOfFiles[i].getAbsolutePath(), ".mat" , true); + + System.out.println("Directory " + listOfFiles[i].getName()); + + File outFolder = new File(imageFolder + File.separator + listOfFiles[i].getName()); + outFolder.mkdir();//make the out folder directory + + try { + + File file = new File(listOfFiles[i].getPath() + File.separator + "whistles.mat"); + Mat5File matFile = Mat5.readFromFile(file); + + Struct whistlesStruct = matFile.getStruct("whistles"); + + double fftLen = matFile.getMatrix("fftlen").getDouble(0); + double fftHop = matFile.getMatrix("ffthop").getDouble(0); + double sampleRate = matFile.getMatrix("samplerate").getDouble(0); + + List fieldNames = whistlesStruct.getFieldNames(); + + Struct whistecontours; + for (String name: fieldNames) { + System.out.println("Generating images for recording " + name + " from " + listOfFiles[i].getName()); + if (!name.equals("fftlen") && !name.equals("ffthop") && !name.equals("samplerate")) { + whistecontours = whistlesStruct.get(name); + generateImages( whistecontours, (outFolder + File.separator + name) , model, fftLen, fftHop, sampleRate); + } + } + + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + } + } + } + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java index d607a0a3..4a32e7a9 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -95,9 +95,7 @@ public class DelphinIDWorker extends ArchiveModelWorker { return whistle2ImageParmas; } } - - - + //something has gone wrong if we get here. return null; } From 7df04d58a2a93a2566a3c530bd4080e78184f05b Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Fri, 17 May 2024 16:34:45 +0100 Subject: [PATCH 10/17] DelphinID training script --- .../delphinID/DelphinIDTest.java | 4 +- .../delphinID/DelphinIDUtils.java | 120 ++++++++++++++---- .../delphinID/DelphinIDWorker.java | 11 ++ .../delphinID/Whistles2Image.java | 17 ++- .../defaultModels/DefaultModelPane.java | 8 +- .../segmenter/SegmenterDetectionGroup.java | 4 + 6 files changed, 128 insertions(+), 36 deletions(-) diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java index 722d4cb3..eb8e72d6 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -65,7 +65,9 @@ public class DelphinIDTest { String whistleContourPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_contours.mat"; //the path to the model - String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_2/whistle_4s_415.zip"; +// String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_2/whistle_4s_415.zip"; + String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_3/whistle_4s_415_f5.zip"; + //the path to the model String matImageSave = "C:/Users/Jamie Macaulay/MATLAB Drive/MATLAB/PAMGUARD/deep_learning/delphinID/whistleimages.mat"; diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDUtils.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDUtils.java index 0e88bae1..2a359e6d 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDUtils.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDUtils.java @@ -224,28 +224,34 @@ public class DelphinIDUtils { //segment the whistle detections ArrayList segments = DelphinIDUtils.segmentWhsitleData(whistles, whistles.get(0).getTimeMilliseconds(), segLen, segHop); - + float[][][] images = worker.dataUnits2ModelInput(segments, (float) sampleRate, 0); float[][] image; BufferedImage bfImage; + double density; for (int k=0; k contour = Whistles2Image.whistContours2Points(group); + + //time bin length from the first contour + double[] times = new double[contour.get(0).length-1]; + for (int i=0; i fieldNames = whistlesStruct.getFieldNames(); + File outFolder = new File(imageFolder + File.separator + listOfFiles[i].getName()); + outFolder.mkdir();//make the out folder directory + Struct whistecontours; for (String name: fieldNames) { - System.out.println("Generating images for recording " + name + " from " + listOfFiles[i].getName()); + System.out.println("Generating images for recording " + name + " from " + listOfFiles[i].getName() + " " + lineWidth); if (!name.equals("fftlen") && !name.equals("ffthop") && !name.equals("samplerate")) { whistecontours = whistlesStruct.get(name); + generateImages( whistecontours, (outFolder + File.separator + name) , model, fftLen, fftHop, sampleRate); } } @@ -327,4 +363,32 @@ public class DelphinIDUtils { } } + public static void main(String[] args) { + + // double[] density = new double[] {0.15 - 1.5}; + + //number of whistle bins/number of time bins; either 16 or 21 + //the e contours as csv files. + // String whistlefolder = "/Users/au671271/Library/CloudStorage/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/training/WMD"; +// String whistlefolder = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/training/WMD_examples/contours"; + String whistlefolder = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/training/WMD/contours"; + + //the image folder to save to. + // String imageFolder = "/Users/au671271/Library/CloudStorage/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/training/WMD_Images"; +// String imageFolder = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/training/WMD_examples/images"; + String imageFolder = "C:/Users/Jamie Macaulay/Desktop/Tristan_training_images/contour_images"; + + //the path to the model + // String modelPath = "/Users/au671271/Library/CloudStorage/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_2/whistle_4s_415.zip"; + String modelPath = "D:/Dropbox/PAMGuard_dev/Deep_Learning/delphinID/testencounter415/whistle_model_2/whistle_4s_415.zip"; + + double[] lineWidths = new double[] {6, 7, 10, 15, 20}; + + for (double lineWidth:lineWidths) { + String imageFolderWidth = (imageFolder + "_"+ String.format("%d",(int)lineWidth)); + new File(imageFolderWidth).mkdir(); + generateTrainingData( modelPath, whistlefolder, imageFolderWidth, lineWidth); + } + } + } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java index 4a32e7a9..bb3ef58a 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -42,6 +42,15 @@ public class DelphinIDWorker extends ArchiveModelWorker { */ private Whistle2ImageParams whistleImageParams; + /** + * Get the whislte to image parameters. + * + * @return + */ + public Whistle2ImageParams getWhistleImageParams() { + return whistleImageParams; + } + @Override public void prepModel(StandardModelParams dlParams, DLControl dlControl) { @@ -87,10 +96,12 @@ public class DelphinIDWorker extends ArchiveModelWorker { freqLimits[1] = jsonObjectParams.getFloat("maxfreq"); size[0] = jsonObjectParams.getInt("widthpix"); size[1] = jsonObjectParams.getInt("heightpix"); + double lineWidth = jsonObjectParams.getDouble("linewidthpix"); Whistle2ImageParams whistle2ImageParmas = new Whistle2ImageParams(); whistle2ImageParmas.freqLimits = freqLimits; whistle2ImageParmas.size = size; + whistle2ImageParmas.lineWidth = lineWidth; return whistle2ImageParmas; } diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java index 892043c5..539792da 100644 --- a/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java @@ -36,7 +36,7 @@ public class Whistles2Image extends FreqTransform { // double[] freqLimits = new double[] {params[0].doubleValue(), params[1].doubleValue()}; // double[] size = new double[] {params[2].doubleValue(), params[3].doubleValue()}; - SpecTransform specTransform = whistleGroupToImage( whistleGroup, params.freqLimits, params.size); + SpecTransform specTransform = whistleGroupToImage( whistleGroup, params.freqLimits, params.size, params.lineWidth); this.setSpecTransfrom(specTransform); this.setFreqlims(params.freqLimits); @@ -50,7 +50,7 @@ public class Whistles2Image extends FreqTransform { * @param freqLimits - the frequency limits * @return the spectrogram transform. */ - private SpecTransform whistleGroupToImage(SegmenterDetectionGroup whistleGroup, double[] freqLimits, double[] size) { + private SpecTransform whistleGroupToImage(SegmenterDetectionGroup whistleGroup, double[] freqLimits, double[] size, double lineWidth) { SpecTransform specTransform = new SpecTransform(); @@ -65,7 +65,7 @@ public class Whistles2Image extends FreqTransform { ArrayList points = whistContours2Points(whistleGroup); //does not work becaue it has to be on the AWT thread. - BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getSegmentDuration()/1000.}, freqLimits, 10.); + BufferedImage canvas = makeScatterImage(points, size, new double[]{0, whistleGroup.getSegmentDuration()/1000.}, freqLimits, lineWidth); double[][] imaged = new double[(int) size[0]][(int) size[1]]; @@ -90,9 +90,9 @@ public class Whistles2Image extends FreqTransform { /** * Convert a list of whistle contours to a list of time and frequency points. * @param whistleGroup - list of whistle contours within a detection group. - * @return an array with time (milliseconds from start of group) and frequency (Hz) + * @return an array with time (seconds from start of group) and frequency (Hz) */ - private ArrayList whistContours2Points(SegmenterDetectionGroup whistleGroup) { + public static ArrayList whistContours2Points(SegmenterDetectionGroup whistleGroup) { ArrayList contours = new ArrayList(); @@ -212,7 +212,12 @@ public class Whistles2Image extends FreqTransform { */ public double[] freqLimits; - public double[] size; + public double[] size; + + /** + * The line width to draw in pixels + */ + public double lineWidth = 10; } diff --git a/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java b/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java index 755bd5db..b6b2078d 100644 --- a/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java +++ b/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java @@ -35,6 +35,9 @@ import rawDeepLearningClassifier.defaultModels.DLModel; */ public class DefaultModelPane extends PamBorderPane{ + private static final double PREF_WIDTH = 200; + + /** * Reference to the deafult model manager that contains the default models. */ @@ -74,7 +77,7 @@ public class DefaultModelPane extends PamBorderPane{ // vBox.setPrefWidth(120); hidingPaneContent= new PamBorderPane(); - hidingPaneContent.setPrefWidth(150); + hidingPaneContent.setPrefWidth(PREF_WIDTH); hidingPane = new HidingPane(Side.RIGHT, hidingPaneContent, vBox, true, 0); PamButton button; @@ -101,12 +104,15 @@ public class DefaultModelPane extends PamBorderPane{ } hidingPane.setStyle("-fx-background-color: -fx-base"); + hidingPane.setPrefWidth(PREF_WIDTH); // this.setStyle("-fx-background-color: -fx-base"); PamStackPane mainHolder = new PamStackPane(); mainHolder.getChildren().addAll(vBox, hidingPane); StackPane.setAlignment(hidingPane, Pos.TOP_RIGHT); + mainHolder.setPrefWidth(PREF_WIDTH); + return mainHolder; } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java index 9636a52b..6c152634 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -50,6 +50,10 @@ public class SegmenterDetectionGroup extends GroupDetection { return segMillis; } + /** + * Get the segment duration in milliseconds. + * @return the segment duration in millis. + */ public double getSegmentDuration() { return segDuration; } From bf20889743cdadce837ef35bc0de90188a529e8f Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Fri, 24 May 2024 11:30:01 +0100 Subject: [PATCH 11/17] Updates to exporter Getting export capability working in PAMGuard Updated POM to use more up to date repository --- .classpath | 3 +- .settings/org.eclipse.core.resources.prefs | 1 + .settings/org.eclipse.jdt.core.prefs | 6 +- pom.xml | 14 +-- src/PamController/PamController.java | 15 ++- src/PamView/PamAWTUtils.java | 58 +++++++-- src/PamView/PamGui.java | 21 ++++ .../dataSelector/DataSelectorDialogPanel.java | 8 +- .../layout/localisation/CTMSettingsPanel.java | 2 +- src/export/CSVExport/CSVExportManager.java | 35 +++++- src/export/MLExport/MLDataUnitExport.java | 2 +- src/export/MLExport/MLDetectionsManager.java | 72 +++++++++++- src/export/PamExporterManager.java | 53 ++++++++- src/export/RExport/RExportManager.java | 110 ++++++++++++++---- src/export/layoutFX/ExportParams.java | 7 +- src/export/swing/ExportProcessDialog.java | 105 +++++++++++++---- src/export/swing/ExportTask.java | 41 ++++++- src/loggerForms/FormsTabPanel.java | 28 ++--- src/offlineProcessing/OLProcessDialog.java | 48 +++++++- src/pamguard/PAMGuard_sqlite.java | 39 +++++++ 20 files changed, 555 insertions(+), 113 deletions(-) create mode 100644 src/pamguard/PAMGuard_sqlite.java diff --git a/.classpath b/.classpath index 576b210a..924124d8 100644 --- a/.classpath +++ b/.classpath @@ -6,9 +6,8 @@ - + - diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 51bb81c3..a474f512 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -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/=UTF-8 encoding/src=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 5c924643..43939db6 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -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 diff --git a/pom.xml b/pom.xml index e0d042fc..1f960c52 100644 --- a/pom.xml +++ b/pom.xml @@ -270,14 +270,12 @@ --> - - - false - - bedatadriven - bedatadriven_renjin - https://nexus.bedatadriven.com/content/groups/public/ - + + bedatadriven + bedatadriven public repo + https://nexus.bedatadriven.com/content/groups/public/ + + diff --git a/src/PamController/PamController.java b/src/PamController/PamController.java index 5da0239a..2fe26b3e 100644 --- a/src/PamController/PamController.java +++ b/src/PamController/PamController.java @@ -100,6 +100,7 @@ import PamguardMVC.uid.UIDManager; import PamguardMVC.uid.UIDOnlineManager; import PamguardMVC.uid.UIDViewerManager; import binaryFileStorage.BinaryStore; +import export.ExportOptions; import PamguardMVC.debug.Debug; /** @@ -2692,13 +2693,23 @@ public class PamController implements PamControllerInterface, PamSettings { } /** - * Respond to storage options dialog. Selects whethere data + * Respond to storage options dialog. Selects whether data * are stored in binary, database or both * @param parentFrame */ public void storageOptions(JFrame parentFrame) { StorageOptions.getInstance().showDialog(parentFrame); } + + /** + * Show export options tp export data to other formats + * @param parentFrame + */ + public void exportData(JFrame parentFrame) { + ExportOptions.getInstance().showDialog(parentFrame); + + } + /** * Return a verbose level for debug output @@ -2941,4 +2952,6 @@ public class PamController implements PamControllerInterface, PamSettings { return pamConfiguration; } + + } diff --git a/src/PamView/PamAWTUtils.java b/src/PamView/PamAWTUtils.java index 576fea5c..7280d460 100644 --- a/src/PamView/PamAWTUtils.java +++ b/src/PamView/PamAWTUtils.java @@ -1,13 +1,17 @@ package PamView; import java.awt.Component; +import java.awt.Container; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.util.ArrayList; +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.JComponent; import javax.swing.JPanel; /** @@ -19,21 +23,51 @@ public class PamAWTUtils { /** * Disable an entire swing panel including all child components. - * @param panel - the panel to disable + * @param jComponent - the panel to disable * @param isEnabled true if enabled. */ - public static void setPanelEnabled(JPanel panel, Boolean isEnabled) { - panel.setEnabled(isEnabled); - - Component[] components = panel.getComponents(); - - for (Component component : components) { - if (component instanceof JPanel) { - setPanelEnabled((JPanel) component, isEnabled); - } - component.setEnabled(isEnabled); - } + public static void setPanelEnabled(JComponent jComponent, Boolean isEnabled) { + setPanelEnabled(jComponent, isEnabled? 1 :-1); } + +// private static final Map componentAvailability = new WeakHashMap(); + + public static void setMoreEnabled(Component component) { + setPanelEnabled(component, +1); + } + + public static void setMoreDisabled(Component component) { + setPanelEnabled(component, -1); + } + + // val = 1 for enabling, val = -1 for disabling + private static void setPanelEnabled(Component component, int val) { + if (component != null) { + +// final Integer oldValObj = componentAvailability.get(component); +// +// final int oldVal = (oldValObj == null) +// ? 0 +// : oldValObj; +// +// final int newVal = oldVal + val; +// componentAvailability.put(component, newVal); + + int newVal = val; + + if (newVal >= 0) { + component.setEnabled(true); + } else if (newVal < 0) { + component.setEnabled(false); + } + if (component instanceof Container) { + Container componentAsContainer = (Container) component; + for (Component c : componentAsContainer.getComponents()) { + setPanelEnabled(c,val); + } + } + } + } /** * Find the closest boundary of a shape to a point. diff --git a/src/PamView/PamGui.java b/src/PamView/PamGui.java index ac51f1bc..bdfae2ee 100644 --- a/src/PamView/PamGui.java +++ b/src/PamView/PamGui.java @@ -667,6 +667,13 @@ public class PamGui extends PamView implements WindowListener, PamSettings { menuItem.addActionListener(new StorageOptions(getGuiFrame())); startMenuEnabler.addMenuItem(menuItem); fileMenu.add(menuItem); + + if (isViewer) { + menuItem = new JMenuItem("Export Data ..."); + menuItem.addActionListener(new ExportData(getGuiFrame())); + startMenuEnabler.addMenuItem(menuItem); + fileMenu.add(menuItem); + } for (int i = 0; i < pamControllerInterface.getNumControlledUnits(); i++) { @@ -975,6 +982,19 @@ public class PamGui extends PamView implements WindowListener, PamSettings { } } + class ExportData implements ActionListener { + private JFrame parentFrame; + + public ExportData(JFrame parentFrame) { + super(); + this.parentFrame = parentFrame; + } + + public void actionPerformed(ActionEvent e) { + PamController.getInstance().exportData(parentFrame); + } + } + class MenuGeneralXMLExport implements ActionListener { private JFrame parentFrame; public MenuGeneralXMLExport(JFrame parentFrame) { @@ -1164,6 +1184,7 @@ public class PamGui extends PamView implements WindowListener, PamSettings { pamControllerInterface.orderModules(frame); } } + class menuArray implements ActionListener { public void actionPerformed(ActionEvent ev){ ArrayManager.getArrayManager().showArrayDialog(getGuiFrame()); diff --git a/src/PamguardMVC/dataSelector/DataSelectorDialogPanel.java b/src/PamguardMVC/dataSelector/DataSelectorDialogPanel.java index 3154f807..76f7ecd2 100644 --- a/src/PamguardMVC/dataSelector/DataSelectorDialogPanel.java +++ b/src/PamguardMVC/dataSelector/DataSelectorDialogPanel.java @@ -13,6 +13,8 @@ import javax.swing.JRadioButton; import javax.swing.border.Border; import javax.swing.border.TitledBorder; +import PamView.PamAWTUtils; +import PamView.PamView; import PamView.dialog.PamDialogPanel; import PamView.dialog.PamGridBagContraints; @@ -130,7 +132,11 @@ public class DataSelectorDialogPanel implements PamDialogPanel { public void enableComponent() { boolean enable = !disableButton.isSelected(); - innerPanel.getDialogComponent().setEnabled(enable); +// System.out.println("Disable!!! " + enable); + PamAWTUtils.setPanelEnabled(innerPanel.getDialogComponent(), enable); + + //disable in swing is not recursive +// innerPanel.getDialogComponent().setEnabled(enable); } @Override diff --git a/src/clickTrainDetector/layout/localisation/CTMSettingsPanel.java b/src/clickTrainDetector/layout/localisation/CTMSettingsPanel.java index 8d74cc24..c068e418 100644 --- a/src/clickTrainDetector/layout/localisation/CTMSettingsPanel.java +++ b/src/clickTrainDetector/layout/localisation/CTMSettingsPanel.java @@ -79,7 +79,7 @@ public class CTMSettingsPanel extends TMSettingsPanel { */ private void setPanelEnabled(boolean enable) { PamAWTUtils.setPanelEnabled(getFilterPanel(), enable); - PamAWTUtils.setPanelEnabled(getLocPanel(), enable); + PamAWTUtils.setPanelEnabled(getLocPanel(), enable); } diff --git a/src/export/CSVExport/CSVExportManager.java b/src/export/CSVExport/CSVExportManager.java index a624ebdb..9e508f4d 100644 --- a/src/export/CSVExport/CSVExportManager.java +++ b/src/export/CSVExport/CSVExportManager.java @@ -1,5 +1,38 @@ package export.CSVExport; -public class CSVExportManager { +import java.io.File; +import java.util.List; + +import PamguardMVC.PamDataUnit; +import export.PamDataUnitExporter; + +public class CSVExportManager implements PamDataUnitExporter{ + + @Override + public boolean hasCompatibleUnits(Class dataUnitType) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean exportData(File fileName, List dataUnits, boolean append) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getFileExtension() { + return "csv"; + } + + @Override + public String getIconString() { + return "mdi2f-file-table-outline"; + } + + @Override + public String getName() { + return "CSV Export"; + } } diff --git a/src/export/MLExport/MLDataUnitExport.java b/src/export/MLExport/MLDataUnitExport.java index b6f2825e..8c67bbaa 100644 --- a/src/export/MLExport/MLDataUnitExport.java +++ b/src/export/MLExport/MLDataUnitExport.java @@ -84,7 +84,7 @@ public abstract class MLDataUnitExport> { double datenumMT = PamCalendar.millistoDateNum(dataUnit.getTimeMilliseconds()); Matrix date = Mat5.newScalar(datenumMT); - mlStruct.set("date", date); + mlStruct.set("date", index, date); //add detection specific data mlStruct= addDetectionSpecificFields(mlStruct, index, dataUnit); diff --git a/src/export/MLExport/MLDetectionsManager.java b/src/export/MLExport/MLDetectionsManager.java index 37f20911..b6e65001 100644 --- a/src/export/MLExport/MLDetectionsManager.java +++ b/src/export/MLExport/MLDetectionsManager.java @@ -2,13 +2,20 @@ package export.MLExport; import java.io.File; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; import java.util.List; +import java.util.NoSuchElementException; +import java.util.zip.Deflater; +import PamUtils.PamCalendar; import PamguardMVC.PamDataUnit; import export.PamDataUnitExporter; import us.hebi.matlab.mat.format.Mat5; import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.format.Mat5Writer; import us.hebi.matlab.mat.types.Matrix; import us.hebi.matlab.mat.types.Sink; import us.hebi.matlab.mat.types.Sinks; @@ -24,12 +31,20 @@ import us.hebi.matlab.mat.util.Casts; public class MLDetectionsManager implements PamDataUnitExporter { public static final String extension = "mat"; + + // Creating date format + public static SimpleDateFormat dataFormat = new SimpleDateFormat( + "yyyyMMdd_HHmmss_SSS"); /** * * All the possible MLDataUnitExport export classes. */ - ArrayList mlDataUnitsExport = new ArrayList(); + ArrayList mlDataUnitsExport = new ArrayList(); + + private Sink sink; + + private File currentFile; public MLDetectionsManager(){ @@ -55,12 +70,60 @@ public class MLDetectionsManager implements PamDataUnitExporter { @Override public boolean exportData(File fileName, List dataUnits, boolean append) { + System.out.println("Export: " + dataUnits.size() + " data units " + append); + + if (dataUnits==null || dataUnits.size()<1) { + //nothing to write but no error. + return true; + } try { - Mat5File matFile = Mat5.newMatFile(); - Sink sink = Sinks.newMappedFile(fileName, Casts.sint32(1000000)); + + Struct dataUnitsStruct = dataUnits2MAT(dataUnits); + + // then + PamDataUnit minByTime = dataUnits + .stream() + .min(Comparator.comparing(PamDataUnit::getTimeMilliseconds)) + .orElseThrow(NoSuchElementException::new); + + //matlab struct must start with a letter. + Date date = new Date(minByTime.getTimeMilliseconds()); + String entryName = "det_" + dataFormat.format( date); + + //is there an existing sink? Is that sink writing to the correct file? + if (sink==null || !fileName.equals(currentFile)) { + + System.out.println("Export: " + dataUnitsStruct.getNumDimensions() + entryName); + + currentFile = fileName; - matFile.writeTo(sink);//Streams the data into a MAT file? + //create the sink for the next data so it can be appended to the file. + sink = Sinks.newStreamingFile(fileName); + + //create the Mat File - gets all the headers right etc. + Mat5File matFile = Mat5.newMatFile(); + matFile.addArray(entryName, dataUnitsStruct); +// matFile.addArray("two", Mat5.newScalar(2)); + + matFile.writeTo(sink); + + matFile.close(); + + } + else { + //write to the mat file without loading all contents into memory. + Mat5Writer writer = Mat5.newWriter(sink); + + writer + .writeArray(entryName, dataUnitsStruct) + .setDeflateLevel(Deflater.NO_COMPRESSION); +// .writeArray("three", Mat5.newScalar(2)); + + writer.flush(); + } + + return true; } catch (IOException e) { // TODO Auto-generated catch block @@ -68,7 +131,6 @@ public class MLDetectionsManager implements PamDataUnitExporter { return false; } - return false; } /** diff --git a/src/export/PamExporterManager.java b/src/export/PamExporterManager.java index f81a50f9..e13d636c 100644 --- a/src/export/PamExporterManager.java +++ b/src/export/PamExporterManager.java @@ -1,11 +1,14 @@ package export; import java.io.File; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import PamUtils.PamCalendar; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; +import export.CSVExport.CSVExportManager; import export.MLExport.MLDetectionsManager; import export.RExport.RExportManager; import export.layoutFX.ExportParams; @@ -18,9 +21,9 @@ import export.wavExport.WavFileExportManager; public class PamExporterManager { /** - * The number of data units to save before saving. + * The number of data units to save before saving. This prevents too much being stored in memory */ - private static int BUFFER_SIZE = 1000; + private static int BUFFER_SIZE = 10000; /** * Keep the file size to around 1GB per file @@ -47,6 +50,10 @@ public class PamExporterManager { * Reference to the current export paramters. */ private ExportParams exportParams = new ExportParams(); + + // Creating date format + public static SimpleDateFormat dataFormat = new SimpleDateFormat( + "yyyy_MM_dd_HHmmss"); public PamExporterManager() { pamExporters = new ArrayList(); @@ -55,19 +62,35 @@ public class PamExporterManager { pamExporters.add(new MLDetectionsManager()); pamExporters.add(new RExportManager()); pamExporters.add(new WavFileExportManager()); + pamExporters.add(new CSVExportManager()); } /** * Add a data unit to the export list. + * @param force - true to force saving of data e.g. at the end of processing. */ - public boolean exportDataUnit(PamDataUnit dataUnit) { + public boolean exportDataUnit(PamDataUnit dataUnit, boolean force) { boolean exportOK = true; + + if (dataUnit==null) { + if (force) { + System.out.println("Write data 1!!" + dataUnitBuffer.size()); + exportOK = pamExporters.get(exportParams.exportChoice).exportData(currentFile, dataUnitBuffer, true); + dataUnitBuffer.clear(); + } + return true; + } + //if the data unit is null then save everything to the buffer. if (currentFile == null || isFileSizeMax(currentFile)) { + Date date = new Date(dataUnit.getTimeMilliseconds()); + + String newFileName = "PAM_" + dataFormat.format(date); + //create a new file - note each exporter is responsible for closing the file after writing //so previous files should already be closed - String fileName = (exportParams.folder + File.separator + PamCalendar.formatDate2(dataUnit.getTimeMilliseconds(), false) + String fileName = (exportParams.folder + File.separator + newFileName + "." + pamExporters.get(exportParams.exportChoice).getFileExtension()); currentFile = new File(fileName); @@ -75,7 +98,11 @@ public class PamExporterManager { dataUnitBuffer.add(dataUnit); - if (BUFFER_SIZE>=BUFFER_SIZE) { +// System.out.println("Write data unit " + dataUnitBuffer.size() + " to: "+ currentFile); + + if (dataUnitBuffer.size()>=BUFFER_SIZE || force) { + System.out.println("Write data 2!!" + dataUnitBuffer.size()); + exportOK = pamExporters.get(exportParams.exportChoice).exportData(currentFile, dataUnitBuffer, true); dataUnitBuffer.clear(); } @@ -101,6 +128,8 @@ public class PamExporterManager { private static double getFileSizeMegaBytes(File file) { return (double) file.length() / (1024 * 1024); } + + public boolean canExportDataBlock(PamDataBlock dataBlock) { for (PamDataUnitExporter exporter:pamExporters) { if (exporter.hasCompatibleUnits(dataBlock.getUnitClass())) return true; @@ -127,5 +156,19 @@ public class PamExporterManager { } + public void setCurrentFile(File file) { + this.currentFile=file; + + } + + public ExportParams getExportParams() { + return exportParams; + } + + public void setExportParams(ExportParams currentParams) { + exportParams=currentParams; + + } + } diff --git a/src/export/RExport/RExportManager.java b/src/export/RExport/RExportManager.java index 5ba0381d..a795add5 100644 --- a/src/export/RExport/RExportManager.java +++ b/src/export/RExport/RExportManager.java @@ -1,14 +1,20 @@ package export.RExport; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.zip.GZIPOutputStream; +import org.renjin.eval.Context; +import org.renjin.primitives.io.serialization.RDataWriter; import org.renjin.sexp.ListVector; import org.renjin.sexp.PairList; import PamguardMVC.PamDataUnit; import export.PamDataUnitExporter; +import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; /** * Handles exporting pam data units into an rdata. @@ -22,7 +28,17 @@ public class RExportManager implements PamDataUnitExporter { * * All the possible RDataUnit export classes. */ - ArrayList mlDataUnitsExport = new ArrayList(); + ArrayList mlDataUnitsExport = new ArrayList(); + + private FileOutputStream fos; + + private GZIPOutputStream zos; + + private File currentFileName ; + + private RDataWriter writer; + + private Context context; public RExportManager(){ @@ -32,6 +48,56 @@ public class RExportManager implements PamDataUnitExporter { mlDataUnitsExport.add(new RRawExport()); //should be last in case raw data holders have specific exporters } + + @Override + public boolean exportData(File fileName, List dataUnits, boolean append) { + + //convert the data units to + RData dataUnitsR = dataUnits2R(dataUnits); + +// System.out.println("Export R file!!" + dataUnits.size()); + + //now write the file + try { + + //is there an existing writer? Is that writer writing to the correct file? + if (zos==null || fileName.equals(currentFileName)) { + + if (zos!=null) { + zos.close(); + writer.close(); + } + + currentFileName = fileName; + + // System.out.println("MLDATA size: "+ mlData.size()); + // System.out.println("---MLArray----"); + // for (int i=0; i dataUnits){ - + public RData dataUnits2R(List dataUnits){ + //if there's a mixed bunch of data units then we want separate arrays of structures. So a structure of arrays of structures. //so, need to sort compatible data units. - + PairList.Builder allData = new PairList.Builder(); ArrayList dataUnitTypes = new ArrayList(); - + //iterate through possible export functions. for (int i=0; i1) { allData.add(mlDataUnitsExport.get(i).getName(), dataListArray.build()); dataUnitTypes.add(mlDataUnitsExport.get(i).getName()); } - + } - + RData rData = new RData(); rData.rData=allData; rData.dataUnitTypes=dataUnitTypes; @@ -115,19 +181,19 @@ public class RExportManager implements PamDataUnitExporter { //now ready to save. return rData; } - + /** * Simple class to hold RData and list of the data unit names whihc were saved. * @author jamie * */ public class RData { - + /** * The RData raedy to save */ public PairList.Builder rData; - + /** * List of the names of the types of data units whihc were saved. */ @@ -136,12 +202,6 @@ public class RExportManager implements PamDataUnitExporter { - @Override - public boolean exportData(File fileName, List dataUnits, boolean append) { - // TODO Auto-generated method stub - return false; - } - @Override public String getFileExtension() { return "RData"; @@ -157,6 +217,6 @@ public class RExportManager implements PamDataUnitExporter { return "R data"; } - + } diff --git a/src/export/layoutFX/ExportParams.java b/src/export/layoutFX/ExportParams.java index aa68d5fe..a4c5dbdc 100644 --- a/src/export/layoutFX/ExportParams.java +++ b/src/export/layoutFX/ExportParams.java @@ -13,7 +13,7 @@ public class ExportParams implements Serializable, Cloneable { /** * */ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * The index of the export choice. @@ -25,6 +25,11 @@ public class ExportParams implements Serializable, Cloneable { */ public String folder = System.getProperty("user.home"); + /** + * The maximum file size in Megabytes + */ + public Double maximumFileSize = 1000.0; + @Override public ExportParams clone() { try { diff --git a/src/export/swing/ExportProcessDialog.java b/src/export/swing/ExportProcessDialog.java index c3721a23..fd62ec6e 100644 --- a/src/export/swing/ExportProcessDialog.java +++ b/src/export/swing/ExportProcessDialog.java @@ -1,6 +1,5 @@ package export.swing; -import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; @@ -11,14 +10,10 @@ import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; -import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; -import javax.swing.Icon; -import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JLabel; -import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.JSpinner.DefaultEditor; import javax.swing.JTextField; @@ -29,13 +24,13 @@ import javax.swing.border.TitledBorder; import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.fileicons.FileIcons; -import org.kordamp.ikonli.materialdesign2.MaterialDesignA; import org.kordamp.ikonli.materialdesign2.MaterialDesignF; import org.kordamp.ikonli.swing.FontIcon; import PamController.PamController; import PamUtils.PamFileChooser; import PamView.dialog.PamButton; +import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; import PamView.panel.PamPanel; import PamguardMVC.PamDataBlock; @@ -43,6 +38,7 @@ import export.PamExporterManager; import export.layoutFX.ExportParams; import offlineProcessing.OLProcessDialog; import offlineProcessing.OfflineTaskGroup; +import offlineProcessing.TaskStatus; /** * Handles an offline dialog for processing offline data and exporting to bespoke file types. @@ -150,20 +146,26 @@ public class ExportProcessDialog { public ExportOLDialog(Window parentFrame, OfflineTaskGroup taskGroup, String title) { super(parentFrame, taskGroup, title); - // TODO Auto-generated constructor stub + //remove the notes panel - don't need this for export. + super.removeNotePanel(); + //remove delete database entried - not used. + super.getDeleteOldDataBox().setVisible(false); + + //construc tthe panel. PamPanel mainPanel = new PamPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); mainPanel.setBorder(new TitledBorder("Export Settings")); buttonGroup = new ButtonGroup(); PamPanel buttonPanel = new PamPanel(); - + ActionListener listener = actionEvent -> { - System.out.println(actionEvent.getActionCommand() + " Selected"); +// System.out.println(actionEvent.getActionCommand() + " Selected"); //TODO set the buttons to be disabled or enabled. - + enableTasks(getExportSelection()); }; exportButtons = new JToggleButton[exportManager.getNumExporters()]; @@ -178,7 +180,7 @@ public class ExportProcessDialog { b.setIcon(icon); b.addActionListener(listener); - + exportButtons[i]=b; buttonGroup.add(b); buttonPanel.add(b); @@ -192,8 +194,8 @@ public class ExportProcessDialog { c.gridy = 0; addComponent(p, exportTo = new JTextField(), c); - exportTo.setMinimumSize(new Dimension(170, 25)); - exportTo.setPreferredSize(new Dimension(170, 25)); + exportTo.setMinimumSize(new Dimension(180, 25)); + exportTo.setPreferredSize(new Dimension(180, 25)); c.gridx +=3; c.gridwidth = 1; @@ -216,34 +218,51 @@ public class ExportProcessDialog { c.gridx = 1; c.gridy++; c.gridwidth = 2; - + JLabel label = new JLabel("Maximum file size", SwingConstants.RIGHT); addComponent(p, label, c); c.gridwidth = 1; c.gridx +=2; - + SpinnerListModel list = new SpinnerListModel(new Double[] {10.,30., 60., 100., 200., 300., 600., 1000.}); - + spinner = new JSpinner(list); //don't want the user to to able to set values ((DefaultEditor) spinner.getEditor()).getTextField().setEditable(false); spinner.setBounds(50, 80, 70, 100); addComponent(p, spinner, c); - + c.gridx ++; addComponent(p, new JLabel("MB"), c); - - + + mainPanel.add(p); mainPanel.add(buttonPanel); //add the main panel at a different index. getMainPanel().add(mainPanel, 1); + + pack(); + } + /** + * Enable which task are disables and enabled. + * @param exportSelection + */ + private void enableTasks(int exportSelection) { + this.currentParams = getExportParams(); + exportManager.setExportParams(currentParams); +// ExportTask task; +// for (int i=0; i0) { + + File file = new File(exportTo.getText()); + + if (!(file.exists() && file.isDirectory())) { + currentParams.folder = null; + } + else { + currentParams.folder = file.getAbsolutePath(); + } + } + + currentParams.exportChoice = getExportSelection(); + currentParams.maximumFileSize = (Double) spinner.getValue(); - return currentParams; } - + @Override public boolean getParams() { //make sure we update the current paramters before processing starts. this.currentParams = getExportParams(); + exportManager.setExportParams(currentParams); + + if (this.currentParams.folder==null) { + return PamDialog.showWarning(super.getOwner(), "No folder or file selected", "You must select an output folder"); + } + + return super.getParams(); } @@ -286,8 +342,14 @@ public class ExportProcessDialog { buttonGroup.clearSelection(); exportButtons[params.exportChoice].setSelected(true); + + exportTo.setText(currentParams.folder); + + spinner.setValue(currentParams.maximumFileSize); } + + } @@ -304,6 +366,7 @@ public class ExportProcessDialog { return "Export Data"; } } + diff --git a/src/export/swing/ExportTask.java b/src/export/swing/ExportTask.java index a3582eb2..a68f65c5 100644 --- a/src/export/swing/ExportTask.java +++ b/src/export/swing/ExportTask.java @@ -25,12 +25,15 @@ public class ExportTask extends OfflineTask>{ /** * The data selector for the data block */ - private DataSelector dataSelector; + private DataSelector dataSelector; + + private boolean canExport; public ExportTask(PamDataBlock> parentDataBlock, PamExporterManager exporter) { super(parentDataBlock); this.exporter = exporter; dataSelector=parentDataBlock.getDataSelectCreator().getDataSelector(this.getUnitName() +"_clicks", false, null); + } @@ -41,20 +44,25 @@ public class ExportTask extends OfflineTask>{ @Override public boolean processDataUnit(PamDataUnit dataUnit) { - exporter.exportDataUnit(dataUnit); - return true; + if (dataSelector==null) exporter.exportDataUnit(dataUnit, false); + else if (dataSelector.scoreData(dataUnit)>0) { + exporter.exportDataUnit(dataUnit, false); + } + return false; //we don't need to indicate that anything has changed - we are just exporting. } @Override public void newDataLoad(long startTime, long endTime, OfflineDataMapPoint mapPoint) { // TODO Auto-generated method stub - +// System.out.println("EXPORTER: new data load"); } @Override public void loadedDataComplete() { - //force the exporter so save any remaning data units in the buffer - exporter.exportDataUnit(null); + System.out.println("EXPORTER: loaded data complete"); + //force the exporter so save any renaming data units in the buffer + exporter.exportDataUnit(null, true); + exporter.setCurrentFile(null); } /** @@ -79,4 +87,25 @@ public class ExportTask extends OfflineTask>{ } + /** + * Set whether the task can export based on the current selection + * @param exportSelection - the index of the selected exporter + */ + public boolean canExport(int exportSelection) { + return exporter.getExporter(exportSelection).hasCompatibleUnits(getDataBlock().getUnitClass()); + } + + + @Override + public boolean canRun() { + boolean can = getDataBlock() != null; + + if (can) { + //check whether we can export based on the export selection + can = canExport(exporter.getExportParams().exportChoice); + } + + return can; + } + } diff --git a/src/loggerForms/FormsTabPanel.java b/src/loggerForms/FormsTabPanel.java index 1176afd2..93d2cca4 100644 --- a/src/loggerForms/FormsTabPanel.java +++ b/src/loggerForms/FormsTabPanel.java @@ -47,20 +47,20 @@ public class FormsTabPanel implements PamTabPanel { // keyManager=KeyboardFocusManager.getCurrentKeyboardFocusManager(); // keyManager.addKeyEventDispatcher(new LoggerKeyEventDispatcher()); - /** Global (OS-level) hotkey manager: - * jnativehook supported systems: Windows, X11, MacOS - * - */ - try { - LogManager.getLogManager().reset(); - GlobalScreen.setEventDispatcher(new SwingDispatchService()); - GlobalScreen.registerNativeHook(); - GlobalScreen.addNativeKeyListener(new GlobalKeyListenerExample()); - } - catch (NativeHookException ex) { - System.err.println("There was a problem registering the native hook."); - System.err.println(ex.getMessage()); - } +// /** Global (OS-level) hotkey manager: +// * jnativehook supported systems: Windows, X11, MacOS +// * +// */ +// try { +// LogManager.getLogManager().reset(); +// GlobalScreen.setEventDispatcher(new SwingDispatchService()); +// GlobalScreen.registerNativeHook(); +// GlobalScreen.addNativeKeyListener(new GlobalKeyListenerExample()); +// } +// catch (NativeHookException ex) { +// System.err.println("There was a problem registering the native hook."); +// System.err.println(ex.getMessage()); +// } } @Override diff --git a/src/offlineProcessing/OLProcessDialog.java b/src/offlineProcessing/OLProcessDialog.java index bc94e2e1..fbaace42 100644 --- a/src/offlineProcessing/OLProcessDialog.java +++ b/src/offlineProcessing/OLProcessDialog.java @@ -13,7 +13,6 @@ import java.awt.event.WindowEvent; import java.util.ArrayList; import javax.swing.BoxLayout; -import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -39,7 +38,6 @@ import PamView.panel.PamAlignmentPanel; import PamView.panel.PamPanel; import PamView.panel.PamProgressBar; import PamguardMVC.PamDataBlock; -import export.layoutFX.ExportParams; import offlineProcessing.logging.OldTaskData; import offlineProcessing.logging.TaskLogging; import offlineProcessing.superdet.OfflineSuperDetFilter; @@ -95,7 +93,7 @@ public class OLProcessDialog extends PamDialog { // public static ImageIcon settings = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - public static FontIcon settings = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + public static FontIcon settings = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); TaskStatus currentStatus = TaskStatus.IDLE; @@ -105,6 +103,13 @@ public class OLProcessDialog extends PamDialog { */ private JPanel mainPanel; + private JPanel notePanel; + + /** + * True if a note is required for the + */ + private boolean isNeedaNote = true; + public OLProcessDialog(Window parentFrame, OfflineTaskGroup taskGroup, String title) { super(parentFrame, title, false); @@ -165,7 +170,7 @@ public class OLProcessDialog extends PamDialog { c.gridy++; } - JPanel notePanel = new JPanel(new BorderLayout()); + notePanel = new JPanel(new BorderLayout()); notePanel.setBorder(new TitledBorder("Notes")); noteText = new DBTextArea(2, 40, TaskLogging.TASK_NOTE_LENGTH); noteText.getComponent().setToolTipText("Notes to add to database record of complete tasks"); @@ -185,11 +190,14 @@ public class OLProcessDialog extends PamDialog { c.gridwidth = 1; addComponent(progressPanel, new JLabel("File ", SwingConstants.RIGHT), c); c.gridx++; + c.gridwidth = 2; addComponent(progressPanel, loadedProgress = new PamProgressBar(0, 100), c); c.gridx = 0; c.gridy++; + c.gridwidth = 1; addComponent(progressPanel, new JLabel("All Data ", SwingConstants.RIGHT), c); c.gridx++; + c.gridwidth = 2; addComponent(progressPanel, globalProgress = new PamProgressBar(00, 100), c); mainPanel.add(dataSelectPanel); @@ -220,6 +228,15 @@ public class OLProcessDialog extends PamDialog { } + /** + * Remove the notes panel. + */ + public void removeNotePanel() { + isNeedaNote = false; + mainPanel.remove(notePanel); + pack(); + } + /** * Get the main panel. This can be used to add additional controls if needed. * @return the main panel. @@ -310,7 +327,7 @@ public class OLProcessDialog extends PamDialog { taskCheckBox[i].setSelected(false); } if (settingsButton[i] != null) { - settingsButton[i].setEnabled(nr); + settingsButton[i].setEnabled(aTask.canRun() && nr); } if (taskCheckBox[i].isSelected()) { selectedTasks++; @@ -368,7 +385,7 @@ public class OLProcessDialog extends PamDialog { } String note = noteText.getText(); - if (note == null || note.length() == 0) { + if ((note == null || note.length() == 0) && isNeedaNote) { return PamDialog.showWarning(super.getOwner(), "Task note", "you must enter a note about what you are doing"); } taskGroupParams.taskNote = note; @@ -376,6 +393,7 @@ public class OLProcessDialog extends PamDialog { return true; } + public void setTaskToolTips() { int nTasks = taskGroup.getNTasks(); @@ -842,6 +860,24 @@ public class OLProcessDialog extends PamDialog { public OfflineTaskGroup getTaskGroup() { return this.taskGroup; } + + + /** + * Check whether a note is required. + * @return true if a note is required. + */ + public boolean isNeedaNote() { + return isNeedaNote; + } + + /** + * Set whether a note is required before processing + * @param isNeedaNote - true to require user to input a note. + */ + public void setNeedaNote(boolean isNeedaNote) { + this.isNeedaNote = isNeedaNote; + } + } diff --git a/src/pamguard/PAMGuard_sqlite.java b/src/pamguard/PAMGuard_sqlite.java new file mode 100644 index 00000000..7c57b1a7 --- /dev/null +++ b/src/pamguard/PAMGuard_sqlite.java @@ -0,0 +1,39 @@ +package pamguard; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.sqlite.SQLiteConfig; + +import generalDatabase.sqlite.SqliteSQLTypes; + +public class PAMGuard_sqlite { + + public static void main(String[] args) { + + String dbName = "/Users/jdjm/Desktop/section2_cpod/hyskeir_pamguard.sqlite3"; + /* + * Don't use the driver manager, but open from the built in command in + * SQLiteConfig. This will then correctly set the dateformat of the database. + */ + SQLiteConfig config = new SQLiteConfig(); + config.setSharedCache(true); + config.enableRecursiveTriggers(true); + config.enableLoadExtension(true); + config.setDateClass(SqliteSQLTypes.dateClass.getValue()); + config.setDateStringFormat(SQLiteConfig.DEFAULT_DATE_STRING_FORMAT); + + Connection con = null; + try { + con = config.createConnection("jdbc:sqlite:" + dbName); + con.setAutoCommit(false); + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + System.out.println("Connection: " + con); + + } + +} From d8cd536f268ebbcb450ab9ebc77e1d7e50cff1a2 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Fri, 24 May 2024 16:56:37 +0100 Subject: [PATCH 12/17] Updates to detection display in FX GUI --- .../DetectionGroupDisplay.java | 36 ++- .../DetectionGroupDisplayFX.java | 11 +- src/detectionPlotFX/DetectionPlotParams.java | 6 +- src/detectionPlotFX/layout/DDDataPane2.java | 1 + .../layout/DetectionPlotDisplay.java | 23 +- src/pamViewFX/TabSelectionPane.java | 4 +- src/pamViewFX/fxPlotPanes/PlotPane.java | 222 +++++++++--------- 7 files changed, 173 insertions(+), 130 deletions(-) diff --git a/src/detectionPlotFX/DetectionGroupDisplay.java b/src/detectionPlotFX/DetectionGroupDisplay.java index 3b9754f2..69289edf 100644 --- a/src/detectionPlotFX/DetectionGroupDisplay.java +++ b/src/detectionPlotFX/DetectionGroupDisplay.java @@ -118,8 +118,17 @@ public class DetectionGroupDisplay extends PamBorderPane { /** * Hiding pane for the plot settings. */ - private HidingPane hidingPane; - + private HidingPane hidingPane; + + /** + * Flag for how the deteciton plot is laid out. + */ + private int layoutType = DISPLAY_EXTENDED; + + /** + * Toggle switch for showing the scroll pane. + */ + private PamToggleSwitch showScrollSwitch; /** * Constructor for the detection group display. @@ -127,6 +136,7 @@ public class DetectionGroupDisplay extends PamBorderPane { public DetectionGroupDisplay() { //create hash map to map DDDataInfos to datablocks for quick access. dDataInfoHashMap = new HashMap(); + this.layoutType = DISPLAY_EXTENDED; createDetectionDisplay(DISPLAY_EXTENDED); this.setCenter(detectionDisplayHolder); } @@ -136,9 +146,11 @@ public class DetectionGroupDisplay extends PamBorderPane { * @param layoutType - the layout of the display - e.g. DetectionGroupDisplay.DISPLAY_COMPACT */ public DetectionGroupDisplay(int layoutType) { + this.layoutType = layoutType; + //create hash map to map DDDataInfos to datablocks for quick access. dDataInfoHashMap = new HashMap(); - createDetectionDisplay(DISPLAY_COMPACT); + createDetectionDisplay(layoutType); this.setCenter(detectionDisplayHolder); } @@ -241,7 +253,7 @@ public class DetectionGroupDisplay extends PamBorderPane { gridPane.add(new Label("Plot type"), 0, 0); gridPane.add(detectionDisplay.getDataTypePane(), 1, 0); - PamToggleSwitch showScrollSwitch = new PamToggleSwitch("Show scroll bar"); + showScrollSwitch = new PamToggleSwitch("Show scroll bar"); showScrollSwitch.selectedProperty().addListener((obsVal, oldVal, newVal)->{ //show or hide the scroll bar. this.setEnableScrollBar(newVal); @@ -570,11 +582,23 @@ public class DetectionGroupDisplay extends PamBorderPane { /** - * Show the scroll bar which allows the user to chnage time limits. + * Show the scroll bar which allows the user to change time limits. * @param enableScrollBarPane - true to enable the time scroll bar. */ public void setEnableScrollBar(boolean enableScrollBarPane) { - this.detectionDisplay.setEnableScrollBar(enableScrollBarPane); + if (this.layoutType==DISPLAY_COMPACT) { + showScrollSwitch.setSelected(enableScrollBarPane); + } + detectionDisplay.setEnableScrollBar(enableScrollBarPane); + detectionDisplay.setupScrollBar(); + } + + /** + * Check whether the scroll bar is changing. The scroll bar allows the user to change time limits. + * @return true if the scroll bar pane is showing. + */ + public boolean isEnableScrollBar() { + return this.detectionDisplay.isEnableScrollBar(); } diff --git a/src/detectionPlotFX/DetectionGroupDisplayFX.java b/src/detectionPlotFX/DetectionGroupDisplayFX.java index a06b520d..ea831e73 100644 --- a/src/detectionPlotFX/DetectionGroupDisplayFX.java +++ b/src/detectionPlotFX/DetectionGroupDisplayFX.java @@ -75,8 +75,10 @@ public class DetectionGroupDisplayFX extends DetectionGroupDisplay implements U @Override public void closeNode() {}; + @Override public DetectionPlotParams getDisplayParams() { + return this.detectionPlotParams; } @@ -86,6 +88,8 @@ public class DetectionGroupDisplayFX extends DetectionGroupDisplay implements U } else detectionPlotParams.dataSource = null; + detectionPlotParams.showScrollBar = this.isEnableScrollBar(); + if (this.internalFrame!=null) { //need to use the parent node because inside an internal pane. detectionPlotParams.positionX=internalFrame.getInternalRegion().getLayoutX(); @@ -189,6 +193,10 @@ public class DetectionGroupDisplayFX extends DetectionGroupDisplay implements U // System.out.println("LOAD DETECTION DISPLAY DATA SOURCE: " + settings.tabName); this.detectionPlotParams = settings.clone(); + + + this.setEnableScrollBar(detectionPlotParams.showScrollBar); + return true; } @@ -213,10 +221,9 @@ public class DetectionGroupDisplayFX extends DetectionGroupDisplay implements U /** * The extra stuff here is to make sure that the plot types for a specific detection are saved. So for example * if viewing click spectrum then the spectrum plot is selected whenever 1) PAMGuard is opened again or 2) switching from - * one type of detection ot another e.g. whistle to click, then the click does nto revert to shwoing a waveform instead + * one type of detection to another e.g. whistle to click, then the click does not revert to showing a waveform instead * of spectrum. */ - if (currentDetection!=null) { //save the current selected detection plot for the particular type of data unit. String detectionPlotName = this.getDetectionDisplay().getCurrentDataInfo().getCurrentDetectionPlot().getName(); diff --git a/src/detectionPlotFX/DetectionPlotParams.java b/src/detectionPlotFX/DetectionPlotParams.java index f951e1c6..80840b7d 100644 --- a/src/detectionPlotFX/DetectionPlotParams.java +++ b/src/detectionPlotFX/DetectionPlotParams.java @@ -26,8 +26,12 @@ public class DetectionPlotParams extends UserDisplayNodeParams implements Clonea * The data source for the detection plot. */ public String dataSource = null; - + /** + * True to show the scroll bar. + */ + public boolean showScrollBar = true; + /** * Saves which data axis is used for which data block. The key is the data block long name and the * result is the name of the plot e.g. waveform. In this way users can set how they want the data plots to display diff --git a/src/detectionPlotFX/layout/DDDataPane2.java b/src/detectionPlotFX/layout/DDDataPane2.java index 240a54f3..f73e289c 100644 --- a/src/detectionPlotFX/layout/DDDataPane2.java +++ b/src/detectionPlotFX/layout/DDDataPane2.java @@ -107,6 +107,7 @@ public class DDDataPane2 extends PamBorderPane { detectionPlotDisplay.setMinHidePaneHeight(ddDataInfo.getCurrentDetectionPlot().getSettingsPane().getMinHeight()); } + //don't want the hide button if there's no settings pane. //detectionPlotDisplay.setEnableSettingsButton(ddDataInfo.getCurrentDetectionPlot().getSettingsPane()!=null); diff --git a/src/detectionPlotFX/layout/DetectionPlotDisplay.java b/src/detectionPlotFX/layout/DetectionPlotDisplay.java index 439301e9..1c77fa6e 100644 --- a/src/detectionPlotFX/layout/DetectionPlotDisplay.java +++ b/src/detectionPlotFX/layout/DetectionPlotDisplay.java @@ -355,16 +355,17 @@ public class DetectionPlotDisplay extends PamBorderPane { * the current data unit. */ public void setupScrollBar(PamDataUnit newDataUnit){ + + if (currentDataInfo!=null) { + //important we put this here as it allows the plot to set up the scroll bar pane. + this.currentDataInfo.setupAxis(detectionPlotProjector, newDataUnit); + } + //setup the scroll bar (or not) if (enableScrollBar && this.detectionPlotProjector.enableScrollBar && newDataUnit!=null) { this.setTop(scrollBarPane); - if (currentDataInfo!=null) { - //important we put this here as it allows the plot to set up the scroll bar pane. - this.currentDataInfo.setupAxis(detectionPlotProjector, newDataUnit); - } - //System.out.println("Set min and max limits for scroll bar: " + detectionPlotProjector.getMinScrollLimit() + " " + detectionPlotProjector.getMaxScrollLimit()); scrollBarPane.setMinVal(detectionPlotProjector.getMinScrollLimit()); scrollBarPane.setMaxVal(detectionPlotProjector.getMaxScrollLimit()); @@ -384,6 +385,9 @@ public class DetectionPlotDisplay extends PamBorderPane { } else { + //need this to ensure the axis change when scroll bar is not longer displayed. + detectionPlotProjector.setAxisMinMax(detectionPlotProjector.getMinScrollLimit(), + detectionPlotProjector.getMaxScrollLimit(), detectionPlotProjector.getScrollAxis()); this.setTop(null); } } @@ -409,19 +413,25 @@ public class DetectionPlotDisplay extends PamBorderPane { private void drawDataUnit(PamDataUnit newDataUnit) { //Debug.out.println("DetectionPlotDisplay DrawDataUnit: " +newDataUnit); if (currentDataInfo!=null){ + //sometimes the axis just need a little push to make sure the pane and axis object bindings have been updated for (int i=0; i { tabChoice=new ComboBox(); tabChoice.setMinWidth(100); PamHBox.setHgrow(tabChoice, Priority.ALWAYS); //make sure choice nox is big enough - tabChoice.setEditable(true); + tabChoice.setEditable(false); //listener for adding tabs addButton=new PamButton(); // addButton.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.PLUS, Color.WHITE, PamGuiManagerFX.iconSize)); addButton.setGraphic(PamGlyphDude.createPamIcon("mdi2p-plus", Color.WHITE, PamGuiManagerFX.iconSize)); addButton.setOnAction((action)->{ - pamGuiFX.addPamTab(new TabInfo("New Display " +(pamGuiFX.getTabs().size()+1)), null, true); + pamGuiFX.addPamTab(new TabInfo("Display " +(pamGuiFX.getTabs().size()+1)), null, true); populateChoiceBox(); //select the tab which has just been added. tabChoice.getSelectionModel().selectLast(); diff --git a/src/pamViewFX/fxPlotPanes/PlotPane.java b/src/pamViewFX/fxPlotPanes/PlotPane.java index 6f62eb2f..6f94e068 100644 --- a/src/pamViewFX/fxPlotPanes/PlotPane.java +++ b/src/pamViewFX/fxPlotPanes/PlotPane.java @@ -21,22 +21,22 @@ import pamViewFX.fxNodes.pamAxis.PamAxisPane2; * */ public class PlotPane extends PamBorderPane { - + /** * The x axis which sits at the top of the plot */ private PamAxisFX xAxisTop; - + /** * The x axis which sits at the bottom of the plot */ private PamAxisFX xAxisBottom; - + /** * The y Axis which sits to the left of the plot */ private PamAxisFX yAxisLeft; - + /* *The y axis which sits to right of the plot */ @@ -88,7 +88,7 @@ public class PlotPane extends PamBorderPane { * Convenience variable, an array with all axis in order, top, right, bottom, left. */ private PamAxisFX[] axisArray; - + /** * Convenience variable, an array with all axis in order, top, right, bottom, left. */ @@ -98,7 +98,7 @@ public class PlotPane extends PamBorderPane { * The holder pane for stuff */ private PamBorderPane holderPane; - + /** * Overlaid pane on canvas which can be used to add hiding panes to the plot. */ @@ -111,23 +111,33 @@ public class PlotPane extends PamBorderPane { private double bottomBorder; private double leftBorder; -// -// public static final int BOTTOMAXIS = 0; -// public static final int BOTTOMAXIS = 1; -// public static final int BOTTOMAXIS = 2; -// public static final int BOTTOMAXIS = 3; -// - + // + // public static final int BOTTOMAXIS = 0; + // public static final int BOTTOMAXIS = 1; + // public static final int BOTTOMAXIS = 2; + // public static final int BOTTOMAXIS = 3; + // + /** * Constructs a default plot with an bottom x axis and left y axis. */ public PlotPane(){ this.setCenter(createPlot(false)); } - - + + private PamBorderPane createPlot(boolean sidePanes){ - + + //create the panes to hold the axis; + //create the plot pane. + canvasHolder=new PamBorderPane(); + canvasHolder.setMaxWidth(4000); + canvasHolder.setMaxHeight(4000); + + canvas = new Canvas(50, 50); + canvas.heightProperty().bind(canvasHolder.heightProperty()); + canvas.widthProperty().bind(canvasHolder.widthProperty()); + //create the x axis for the display. xAxisTop = new PamAxisFX(0, 1, 0, 1, 0, 10, PamAxisFX.ABOVE_LEFT, null, PamAxis.LABEL_NEAR_CENTRE, null); xAxisTop.setCrampLabels(true); @@ -145,67 +155,53 @@ public class PlotPane extends PamBorderPane { yAxisLeftPane=new PamAxisPane2(yAxisLeft, Side.LEFT); //yAxisLeftPane.setOrientation(Orientation.VERTICAL); - yAxisRight = new PamAxisFX(0, 1, 0, 1, 0, 10, PamAxisFX.BELOW_RIGHT, "Graph Y Units", PamAxisFX.LABEL_NEAR_CENTRE, "%4d"); yAxisRight.setCrampLabels(true); yAxisRightPane=new PamAxisPane2(yAxisRight, Side.RIGHT); - //yAxisRightPane.setOrientation(Orientation.VERTICAL); + //yAxisRightPane.setOrientation(Orientation.VERTICAL) - - //create the panes to hold the axis; - - //create the plot pane. - canvasHolder=new PamBorderPane(); - canvasHolder.setMaxWidth(4000); - canvasHolder.setMaxHeight(4000); + //allow hiding panes to be added + hiddenSidePane=new PamHiddenSidePane(); + hiddenSidePane.getChildren().add(canvas); + hiddenSidePane.toFront(); - canvas = new Canvas(50, 50); - canvas.heightProperty().bind(canvasHolder.heightProperty()); - canvas.widthProperty().bind(canvasHolder.widthProperty()); - - - //allow hiding panes to be added - hiddenSidePane=new PamHiddenSidePane(); - hiddenSidePane.getChildren().add(canvas); - hiddenSidePane.toFront(); - - - canvasHolder.setCenter(hiddenSidePane); - canvasHolder.setMinHeight(0); - canvasHolder.setMinWidth(0); - //canvasHolder.getStyleClass().add("pane-plot"); - //now add all axis together - holderPane=new PamBorderPane(); - - //now need to add some corner sections to the top and bottom axis as borderpane is being used - topHolder=createHorzHolder(xAxisTopPane); - bottomHolder=createHorzHolder(xAxisBottomPane); + canvasHolder.setCenter(hiddenSidePane); + canvasHolder.setMinHeight(10); + canvasHolder.setMinWidth(10); + //canvasHolder.getStyleClass().add("pane-plot"); - setAxisVisible(true, true, true, true); + //now add all axis together + holderPane=new PamBorderPane(); - -// topHolder.toFront(); - //yAxisRightPane.toFront(); -// yAxisLeftPane.toFront(); -// bottomHolder.toFront(); - - axisArray=new PamAxisFX[4]; - axisArray[0]=xAxisTop; - axisArray[1]=yAxisRight; - axisArray[2]=xAxisBottom; - axisArray[3]=yAxisLeft; - - axisPanes=new PamAxisPane2[4]; - axisPanes[0]=xAxisTopPane; - axisPanes[1]=yAxisRightPane; - axisPanes[2]=xAxisBottomPane; - axisPanes[3]=yAxisLeftPane; - - return holderPane; + //now need to add some corner sections to the top and bottom axis as borderpane is being used + topHolder=createHorzHolder(xAxisTopPane); + bottomHolder=createHorzHolder(xAxisBottomPane); + + setAxisVisible(true, true, true, true); + + + // topHolder.toFront(); + //yAxisRightPane.toFront(); + // yAxisLeftPane.toFront(); + // bottomHolder.toFront(); + + axisArray=new PamAxisFX[4]; + axisArray[0]=xAxisTop; + axisArray[1]=yAxisRight; + axisArray[2]=xAxisBottom; + axisArray[3]=yAxisLeft; + + axisPanes=new PamAxisPane2[4]; + axisPanes[0]=xAxisTopPane; + axisPanes[1]=yAxisRightPane; + axisPanes[2]=xAxisBottomPane; + axisPanes[3]=yAxisLeftPane; + + return holderPane; } - - + + /** * Set a hiding pane within the plot area * @param pane - the pane whihc is hidden @@ -231,7 +227,7 @@ public class PlotPane extends PamBorderPane { break; } } - + /** * Get one of the hiding panes within the plot area * @param side - the location of the pna eon the plot (left or right) @@ -258,7 +254,7 @@ public class PlotPane extends PamBorderPane { */ private PamHBox createHorzHolder(PamAxisPane2 axisPane){ PamHBox horzHolder=new PamHBox(); - + Pane leftPane=new Pane(); //need both min and pref to make binding work properly; leftPane.prefWidthProperty().bind(yAxisLeftPane.widthProperty()); @@ -267,26 +263,26 @@ public class PlotPane extends PamBorderPane { Pane rightPane=new Pane(); rightPane.prefWidthProperty().bind(yAxisRightPane.widthProperty()); rightPane.minWidthProperty().bind(yAxisRightPane.widthProperty()); - - horzHolder.getChildren().addAll(leftPane, axisPane, rightPane); + + horzHolder.getChildren().addAll(leftPane, axisPane); //axisPane.toFront(); this changes the order of children in a PamHBox. - HBox.setHgrow(axisPane, Priority.ALWAYS); - + HBox.setHgrow(axisPane, Priority.ALWAYS); + //horzHolder.getStyleClass().add("pane"); - return horzHolder; - + return horzHolder; + } - -// public void repaintAxis() { -// xAxisTopPane.repaint(); -// xAxisBottomPane.repaint(); -// yAxisRightPane.repaint(); -// yAxisLeftPane.repaint(); -// } - + // public void repaintAxis() { + // xAxisTopPane.repaint(); + // xAxisBottomPane.repaint(); + // yAxisRightPane.repaint(); + // yAxisLeftPane.repaint(); + // } + + /** * Get the canvas- this is where the plotting takes place. * @return the plot canvas. @@ -294,8 +290,8 @@ public class PlotPane extends PamBorderPane { public Canvas getPlotCanvas() { return canvas; } - - + + /** * Get an axis of the plot pane. * @param side the axis to get. @@ -314,10 +310,10 @@ public class PlotPane extends PamBorderPane { default: return null; } - + } - - + + /** * Get an axis pane. The axis pane is the node which displays a PamAxisFX. * @param side the axis pane to get. @@ -337,7 +333,7 @@ public class PlotPane extends PamBorderPane { return null; } } - + /** * Get all the axis of the plot pane. * @return a list of axis in the order: TOP, RIGHT, BOTTOM, LEFT. @@ -345,7 +341,7 @@ public class PlotPane extends PamBorderPane { public PamAxisFX[] getAllAxis() { return axisArray; } - + /** * Get an axis pane * @param side the axis to get. @@ -353,14 +349,14 @@ public class PlotPane extends PamBorderPane { public PamAxisPane2[] getAllAxisPanes() { return axisPanes; } - + public void setEmptyBorders(double top, double right, double bottom, double left) { this.topBorder = top; this.rightBorder = right; this.bottomBorder = bottom; this.leftBorder = left; } - + /** * Set which axis are visible. * @param top true to show the top axis @@ -370,9 +366,9 @@ public class PlotPane extends PamBorderPane { */ public void setAxisVisible(boolean top, boolean right, boolean bottom, boolean left) { - + //holderPane.getChildren().clear(); - + //HACK- 05/08/2016 have to do this because there is a bug in switching children postions in a border pane. //casues a duplicate childrne error. holderPane.setRight(null); @@ -381,41 +377,41 @@ public class PlotPane extends PamBorderPane { holderPane.setBottom(null); holderPane.getChildren().clear(); //end of HACK. - + if (top) { holderPane.setTop(topHolder) ; } else if (topBorder > 0) { -// holderPane.setTopSpace(topBorder); + // holderPane.setTopSpace(topBorder); } if (bottom) { holderPane.setBottom(bottomHolder); } else if (bottomBorder > 0) { -// holderPane.setBottomSpace(bottomBorder); + // holderPane.setBottomSpace(bottomBorder); } if (right) { holderPane.setRight(yAxisRightPane) ; } else if (rightBorder > 0){ -// holderPane.setRightSpace(rightBorder); + // holderPane.setRightSpace(rightBorder); } if (left) { holderPane.setLeft(yAxisLeftPane) ; } else if (leftBorder > 0) { -// holderPane.setLeftSpace(leftBorder); + // holderPane.setLeftSpace(leftBorder); } holderPane.setCenter(canvasHolder); //bottomHolder.toBack(); - -// this.xAxisTopPane.setVisible(top); -// this.xAxisBottomPane.setVisible(bottom); -// this.yAxisRightPane.setVisible(right); -// this.yAxisLeftPane.setVisible(left); + + // this.xAxisTopPane.setVisible(top); + // this.xAxisBottomPane.setVisible(bottom); + // this.yAxisRightPane.setVisible(right); + // this.yAxisLeftPane.setVisible(left); } - + /** * Set the minimium height of the right and left hide pane. Set -1 for there to be no minimum height. * If the hide pane goes below the minimum height it pops out of its holder. @@ -456,12 +452,12 @@ public class PlotPane extends PamBorderPane { public PamAxisFX getyAxisRight() { return yAxisRight; } - - - - - - - + + + + + + + } From 4707830953b616bb2c030f88cb974ae77902c3cd Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Mon, 27 May 2024 16:55:53 +0100 Subject: [PATCH 13/17] Updates to exporter --- readme.md | 74 +++++++- src/PamUtils/PamArrayUtils.java | 19 ++ src/export/CSVExport/CSVExportManager.java | 6 + src/export/MLExport/MLDetectionsManager.java | 108 ++++++------ src/export/PamDataUnitExporter.java | 5 + src/export/PamExporterManager.java | 16 +- src/export/RExport/RClickExport.java | 2 +- src/export/RExport/RExportManager.java | 162 +++++++++++------- src/export/RExport/RRawExport.java | 2 +- src/export/swing/ExportTask.java | 4 +- .../wavExport/WavFileExportManager.java | 8 + src/pamViewFX/fxPlotPanes/PlotPane.java | 2 +- 12 files changed, 289 insertions(+), 119 deletions(-) diff --git a/readme.md b/readme.md index a63ea137..721b10db 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,73 @@ -This is the main code repository for the PAMGuard software. +# PAMGuard +PAMGuard is a bioacoustics analysis program designed for use in real time research contexts and for the processing of large datasets. PAMGuard provides users access to a suite of state-of-the-art auotmated analysis algorithms alongside displays for visualisation data and a comprehensive data management systems. -This repository was created on 7 January 2022 from sourceforge SVN repository at https://sourceforge.net/p/pamguard/svn/HEAD/tree/ revision r6278. +# Why do we need PAMGuard? +PAMGuard fufills two main requirements within marine bioacoustics -If you are a PAMGuard developer, you should clone and branch this repository and share with any collaborators in your own workspace. When your work is ready, contact the PAMGuard team to have your changes merged back into this repo. +1) **Real time operation** - Almost all PAMGuard features and modules work in real time - this allows scientists and industry to detect, classify and loclaise animals in real time on a standard consumer laptop, enabling mitigations and research survey without expensive bespoke software solutions and the transparncy of open source software. + +2) **Processing and visuslisation of large datasets** - + + +## Installation +PAMGuard is available on Windows and can be downloaded from the [PAMGuard website](www.pamguard.org). Note that we are considering MacOS installers but they are not available at this time. + +## Tutorial +PAMGuard is a modular program with two modes; real-time and viewer. Typically a user will start with real-time model, either in the field collecting data or post processing sound files from a recorder. Once data are processed, users move on to viewer mode where data can be explored and further processed. + +Upon opening PAMGuard for the first time you are greeted with a blank screen. You must add a series of modules to create the desired acosutic workflow. For example if processing sound files then first add the Sound Acquisition module **_File->Add Modules->Sound Processing->Sound Acquisition_**. Then add the desired detection algorothms e.g. **_File->Add Modules->Detector->Click Detectors_**. Some modules (such as the click detector) have their own displays, others are added to more generalised displays. For example, the whislte and moan detector module shows detections on a spectrgram display. First add a new tab using **_File->Add Modules->Displays->User Display**. Click on the user display tab and then from the top menu select **_User display-> New Spectrgram_**. Right click on the added spectrgram and select whistle and moan contours to show whistle detections overlaid on the raw spectrgram. + +Make sure to add the database and binary file storage modules **_File->Add Modules->Utilities->..._**) to save data then press the run button (red button) and data will process. PAMGuard can handle huge datasets so runing might take hours or even days. Progress is shown on the bottom of the screen. + +## Features + +### Hardware integration +PAMGuard connects with hardware such as various GPS and AIS systems and a multitude of different sound cards (e.g. [National Instruments](www.ni.com) devices, [SAIL DAQ cards](www.smruconsulting.com/contact-us), almost all ASIO sound cards and standard computer sound cards) for real time data collection and processing. PAMGuard also works with some very bespoke hardware such as [DIFAR Sonobuoys](); + +### Real time operation +PAMGuard takes advanatge of multi-core processors to run multiple signal processing automatic analysis algorithms in real time to detect whales, dolphins, bats etc. Data are shown in different displayes, including interactive spectrograms and maps. You might be using PAMGuard for simply viewing a spectrgram and making recordings or running deep learning algorithms for multiple species and loclaising the results to view locations on a map. Whatever acosutic workflow a user creates, PAMGuard can run it in real time. + +### Support for compressed audio +PAMGuard supports processing audio data from standard files (e.g. wav, aif) and also compressed files (e.g. .flac and .sud). Notew that sud files are created on SoundTraps widely used marine recorders and can be read by PAMGuard without decompressing - PAMGuard will automtically import click detections if present in sud files. PAMGuard also supports importing detection data from CPODs and FPODs. + +### Comprehensive data management system +PAMGuard is designed to collect/process data from large acosutic datasets. PAMGuard stores data in an SQLite databases and "Binary" files. The database stores important metadata such as when data has been processed and some low volume data streams such as GPS. Binary files are not human readbale but efficient to access - PAMGuard stores detection data (e.g. clicks, whistles, noise, etc) in these files. this allows PAMGuard to rapidly access data from large datasets. Data from binary files can be viewed in PAMGuard viewer mode or can be exported to MATLAB using the PAMGuard-MATLAB library or the exported to R using the R PAMBinaries package. + +### Access to detection and classification algorithms +PAMGuard allows users to inegrate automated detection and classification algorithms directly into their acosutic workflow. There are a multitude of differwent algorothms to choose from, including a basic click detector, whislte and moan detector, GPL detector, click train detectors and many others. The idea behind PAMGuard is allow researchers to access open source state-of-the-art algorithms devleoped within the scientific community - if you want to contribute and get your algorithm into PAMGuard get in touch. + +###Localisation +PAMGuard has a mutltude of different options for acoustic loclaisation. There's a comprehesnive beam forming module for beam forming arrays, a large aperture localiser for 3D loclaisation and target motion analysis for towed hydrophone arrays. + +###Soundscape analysis +PAMGuard has a noise band (which supports third octave noise bands) and long term spectral average module for soundscape analysis. + +### GIS +Almsot all detection data can be visualised on a map. PAMGaurd also supports plotting GPS and AIS data. + +### Suite of data visualisation tools +An important aspect of PAMGuard is the ability for users to explore porcessed data. This is + +### Advanced manual annotation +The displays within PAMGuard support a variety of manual annottion tools. A simple spectrogram + +### Deep learning integration + +### Meatadata standard and Tethys compatibility + +## Feature roadmap +There's lots of features we would like to add to PAMGuard. If you want to add a feature you can either code it up yourself in Java and submit a pull request or get in touch with us to discuss how to it might be integrated. Some smaller features might be in our roadmap anyway but larger features usually require funding. Some features we are thinking about (but do not necassarily have time for yet) are; + +* Support for decidecade noise bands (base 10 filter bank) in noise band monitor to meet Euopean standards +* Capabaility to export data directly from PAMGaurd e.g. as MAT files. +* Automated test suite to make releases more stable. Note that unit and integration tests are also being slowly incorporated. + +## Development +This is the main code repository for the PAMGuard software and was created on 7 January 2022 from a [sourceforge SVN repository](https://sourceforge.net/p/pamguard/svn/HEAD/tree/) revision r6278. + +If you are a PAMGuard developer, you should clone and branch this repository and share with any collaborators in your own workspace. When your work is ready, contact the PAMGuard team to have your changes merged back into this repo. + +PAMGuard uses Maven as build tool. + +# Organisation and License +PAMGuard is open source under an MIT license. It is currently primarily managed by the Sea Mammal Research Unit within the [University of St Andrews](https://www.st-andrews.ac.uk/). Please get in touch if you have any questions. diff --git a/src/PamUtils/PamArrayUtils.java b/src/PamUtils/PamArrayUtils.java index a4209c74..cfb4e866 100644 --- a/src/PamUtils/PamArrayUtils.java +++ b/src/PamUtils/PamArrayUtils.java @@ -3,13 +3,16 @@ package PamUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.NoSuchElementException; import java.util.TreeMap; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.math.linear.Array2DRowRealMatrix; import org.apache.commons.math.linear.RealMatrix; +import PamguardMVC.PamDataUnit; import us.hebi.matlab.mat.types.Matrix; /** @@ -176,6 +179,22 @@ public class PamArrayUtils { } return median; } + + + /** + * Get the data unit with the lowest time in millis from a data unit list. + * @param dataUnits - a data unit list. + * @return the data unit with the lowest time in millis. + */ + public static PamDataUnit getMinTimeMillis(List dataUnits) { + // then + PamDataUnit minByTime = dataUnits + .stream() + .min(Comparator.comparing(PamDataUnit::getTimeMilliseconds)) + .orElseThrow(NoSuchElementException::new); + + return minByTime; + } /** * Calculate the median value of an array diff --git a/src/export/CSVExport/CSVExportManager.java b/src/export/CSVExport/CSVExportManager.java index 9e508f4d..bf011e30 100644 --- a/src/export/CSVExport/CSVExportManager.java +++ b/src/export/CSVExport/CSVExportManager.java @@ -35,4 +35,10 @@ public class CSVExportManager implements PamDataUnitExporter{ return "CSV Export"; } + @Override + public void close() { + // TODO Auto-generated method stub + + } + } diff --git a/src/export/MLExport/MLDetectionsManager.java b/src/export/MLExport/MLDetectionsManager.java index b6e65001..fe05cd39 100644 --- a/src/export/MLExport/MLDetectionsManager.java +++ b/src/export/MLExport/MLDetectionsManager.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.zip.Deflater; +import PamUtils.PamArrayUtils; import PamUtils.PamCalendar; import PamguardMVC.PamDataUnit; import export.PamDataUnitExporter; @@ -29,12 +30,12 @@ import us.hebi.matlab.mat.util.Casts; * */ public class MLDetectionsManager implements PamDataUnitExporter { - + public static final String extension = "mat"; - - // Creating date format - public static SimpleDateFormat dataFormat = new SimpleDateFormat( - "yyyyMMdd_HHmmss_SSS"); + + // Creating date format + public static SimpleDateFormat dataFormat = new SimpleDateFormat( + "yyyyMMdd_HHmmss_SSS"); /** * @@ -52,7 +53,7 @@ public class MLDetectionsManager implements PamDataUnitExporter { mlDataUnitsExport.add(new MLWhistleMoanExport()); mlDataUnitsExport.add(new MLRawExport()); } - + @Override public boolean hasCompatibleUnits(Class dataUnitType) { for (int i=0; i dataUnits, boolean append) { - + System.out.println("Export: " + dataUnits.size() + " data units " + append); - + if (dataUnits==null || dataUnits.size()<1) { //nothing to write but no error. return true; } - + try { - + Struct dataUnitsStruct = dataUnits2MAT(dataUnits); - - // then - PamDataUnit minByTime = dataUnits - .stream() - .min(Comparator.comparing(PamDataUnit::getTimeMilliseconds)) - .orElseThrow(NoSuchElementException::new); - + + // then + PamDataUnit minByTime = PamArrayUtils.getMinTimeMillis(dataUnits); + //matlab struct must start with a letter. - Date date = new Date(minByTime.getTimeMilliseconds()); + Date date = new Date(minByTime.getTimeMilliseconds()); String entryName = "det_" + dataFormat.format( date); - + //is there an existing sink? Is that sink writing to the correct file? if (sink==null || !fileName.equals(currentFile)) { - + System.out.println("Export: " + dataUnitsStruct.getNumDimensions() + entryName); - + currentFile = fileName; //create the sink for the next data so it can be appended to the file. sink = Sinks.newStreamingFile(fileName); - + //create the Mat File - gets all the headers right etc. Mat5File matFile = Mat5.newMatFile(); matFile.addArray(entryName, dataUnitsStruct); -// matFile.addArray("two", Mat5.newScalar(2)); + // matFile.addArray("two", Mat5.newScalar(2)); matFile.writeTo(sink); - + matFile.close(); - + } else { //write to the mat file without loading all contents into memory. Mat5Writer writer = Mat5.newWriter(sink); - + writer .writeArray(entryName, dataUnitsStruct) .setDeflateLevel(Deflater.NO_COMPRESSION); -// .writeArray("three", Mat5.newScalar(2)); + // .writeArray("three", Mat5.newScalar(2)); writer.flush(); } return true; - + } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } - + } /** @@ -156,7 +154,7 @@ public class MLDetectionsManager implements PamDataUnitExporter { //ArrayList> struct = new ArrayList>(); //if there's a mixed bunch of data units then we want separate arrays of structures. So a structure of arrays of structures. //so, need to sort compatible data units. - + Struct list = Mat5.newStruct(); //keep a track of the data units that have been transcribed. This means data units that are multiple types @@ -180,25 +178,25 @@ public class MLDetectionsManager implements PamDataUnitExporter { //create a structure for each type of data unit. Struct mlStructure= Mat5.newStruct(new int[]{n, 1}); - + float sampleRate = -1; - + n=0; //allocate the class now. for (int j=0; j=1) { + if (n>0) { list.set(mlDataUnitsExport.get(i).getName(),mlStructure); list.set(mlDataUnitsExport.get(i).getName()+"_sR", Mat5.newScalar(sampleRate)); } @@ -223,15 +221,15 @@ public class MLDetectionsManager implements PamDataUnitExporter { public String getName() { return "MATLAB"; } - + public static void main(String args[]) { - + String fileName = "/Users/au671271/MATLAB-Drive/MATLAB/PAMGUARD/_test/export_test.mat"; - + try { Mat5File matFile = Mat5.newMatFile(); - - + + Struct mlStruct = Mat5.newStruct(3, 1); Matrix triggerMap = Mat5.newScalar(Math.random()); @@ -243,21 +241,35 @@ public class MLDetectionsManager implements PamDataUnitExporter { //basic method to write to a file Mat5.writeToFile(matFile, fileName); - -// Sink sink = Sinks.newMappedFile(new File(fileName), Casts.sint32(1000000)); -// -// matFile.writeTo(sink); -// -// sink.close(); - + + // Sink sink = Sinks.newMappedFile(new File(fileName), Casts.sint32(1000000)); + // + // matFile.writeTo(sink); + // + // sink.close(); + } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } - + @Override + public void close() { + //handled in the mian funtion + if (sink!=null) { + try { + sink.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + } diff --git a/src/export/PamDataUnitExporter.java b/src/export/PamDataUnitExporter.java index 44c10599..8ab6fa56 100644 --- a/src/export/PamDataUnitExporter.java +++ b/src/export/PamDataUnitExporter.java @@ -46,5 +46,10 @@ public interface PamDataUnitExporter { */ public String getName(); + /** + * Clsoe the exporter + */ + public void close(); + } diff --git a/src/export/PamExporterManager.java b/src/export/PamExporterManager.java index e13d636c..61631f1a 100644 --- a/src/export/PamExporterManager.java +++ b/src/export/PamExporterManager.java @@ -74,15 +74,15 @@ public class PamExporterManager { if (dataUnit==null) { if (force) { - System.out.println("Write data 1!!" + dataUnitBuffer.size()); + System.out.println("Write data 1!!" + dataUnitBuffer.size() ); + //finish off saving any buffered data exportOK = pamExporters.get(exportParams.exportChoice).exportData(currentFile, dataUnitBuffer, true); dataUnitBuffer.clear(); } return true; } - //if the data unit is null then save everything to the buffer. - + //if file is null or too large create another a file for saving. if (currentFile == null || isFileSizeMax(currentFile)) { Date date = new Date(dataUnit.getTimeMilliseconds()); @@ -98,11 +98,10 @@ public class PamExporterManager { dataUnitBuffer.add(dataUnit); -// System.out.println("Write data unit " + dataUnitBuffer.size() + " to: "+ currentFile); + System.out.println("Write data unit " + dataUnitBuffer.size() + " to: "+ currentFile); if (dataUnitBuffer.size()>=BUFFER_SIZE || force) { System.out.println("Write data 2!!" + dataUnitBuffer.size()); - exportOK = pamExporters.get(exportParams.exportChoice).exportData(currentFile, dataUnitBuffer, true); dataUnitBuffer.clear(); } @@ -110,6 +109,11 @@ public class PamExporterManager { return exportOK; } + + public void close() { + pamExporters.get(exportParams.exportChoice).close(); + + } /** * Check whether the current file is greater than the maximum allowed file size. @@ -171,4 +175,6 @@ public class PamExporterManager { } + + } diff --git a/src/export/RExport/RClickExport.java b/src/export/RExport/RClickExport.java index 68293f54..8730c118 100644 --- a/src/export/RExport/RClickExport.java +++ b/src/export/RExport/RClickExport.java @@ -17,7 +17,7 @@ public class RClickExport extends RRawExport{ ClickDetection clickDetection = (ClickDetection) dataUnit; - super.addDetectionSpecificFields(rData, null, index); + super.addDetectionSpecificFields(rData, dataUnit, index); //add some basic click measurements rData.add("triggerMap", new DoubleArrayVector(clickDetection.getTriggerList())); diff --git a/src/export/RExport/RExportManager.java b/src/export/RExport/RExportManager.java index a795add5..2f68163a 100644 --- a/src/export/RExport/RExportManager.java +++ b/src/export/RExport/RExportManager.java @@ -4,16 +4,22 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.zip.GZIPOutputStream; import org.renjin.eval.Context; import org.renjin.primitives.io.serialization.RDataWriter; +import org.renjin.sexp.DoubleArrayVector; +import org.renjin.sexp.IntArrayVector; import org.renjin.sexp.ListVector; import org.renjin.sexp.PairList; +import org.renjin.sexp.PairList.Builder; +import PamUtils.PamArrayUtils; import PamguardMVC.PamDataUnit; import export.PamDataUnitExporter; +import export.MLExport.MLDetectionsManager; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; /** @@ -28,74 +34,76 @@ public class RExportManager implements PamDataUnitExporter { * * All the possible RDataUnit export classes. */ - ArrayList mlDataUnitsExport = new ArrayList(); - - private FileOutputStream fos; - - private GZIPOutputStream zos; + ArrayList rDataExport = new ArrayList(); private File currentFileName ; - private RDataWriter writer; - private Context context; + private Builder allData; public RExportManager(){ /***Add more options here to export data units****/ - mlDataUnitsExport.add(new RClickExport()); - mlDataUnitsExport.add(new RWhistleExport()); - mlDataUnitsExport.add(new RRawExport()); //should be last in case raw data holders have specific exporters + rDataExport.add(new RClickExport()); + rDataExport.add(new RWhistleExport()); + rDataExport.add(new RRawExport()); //should be last in case raw data holders have specific exporters } @Override public boolean exportData(File fileName, List dataUnits, boolean append) { - //convert the data units to - RData dataUnitsR = dataUnits2R(dataUnits); - -// System.out.println("Export R file!!" + dataUnits.size()); + /** + * Note - there is no way to save data units to R files wothout loading the file into memory. + * So everything is stored in memory until saved. + */ - //now write the file + // then + PamDataUnit minByTime = PamArrayUtils.getMinTimeMillis(dataUnits); + + //matlab struct must start with a letter. + Date date = new Date(minByTime.getTimeMilliseconds()); + String entryName = "det_" + MLDetectionsManager.dataFormat.format( date); + + // System.out.println("Save R data! "+ dataUnits.size()); + + // System.out.println("Export R file!!" + dataUnits.size()); + + //is there an existing writer? Is that writer writing to the correct file? + if (allData==null || !fileName.equals(currentFileName)) { + + if (allData!=null) { + writeRFile(); + } + + allData = new PairList.Builder(); + currentFileName = fileName; + } + + //convert the data units to R and save to the PairList builder + dataUnits2R(dataUnits, entryName, allData); + + return true; + + } + + private void writeRFile() { + Context context = Context.newTopLevelContext(); try { - //is there an existing writer? Is that writer writing to the correct file? - if (zos==null || fileName.equals(currentFileName)) { - - if (zos!=null) { - zos.close(); - writer.close(); - } - - currentFileName = fileName; - - // System.out.println("MLDATA size: "+ mlData.size()); - // System.out.println("---MLArray----"); - // for (int i=0; i dataUnits){ + PairList.Builder allData = new PairList.Builder(); + return dataUnits2R(dataUnits, null, allData); + } + + + /** + * Sort a list of data units into lists of the same type of units. Convert to a list of structures. + * @param dataUnits - a list of data units to convert to matlab structures. + * @param - a name for the structure. + * @return list of list of R strucutures ready for saving to .RData file. + */ + public RData dataUnits2R(List dataUnits, String name, PairList.Builder allData) { //if there's a mixed bunch of data units then we want separate arrays of structures. So a structure of arrays of structures. //so, need to sort compatible data units. - PairList.Builder allData = new PairList.Builder(); ArrayList dataUnitTypes = new ArrayList(); + //keep a track of the data units that have been transcribed. This means data units that are multiple types + //(e.g. a raw data holder and click) are not added to two different list of structures. + boolean[] alreadyStruct = new boolean[dataUnits.size()]; + //iterate through possible export functions. - for (int i=0; i1) { - allData.add(mlDataUnitsExport.get(i).getName(), dataListArray.build()); - dataUnitTypes.add(mlDataUnitsExport.get(i).getName()); + if (n>0) { + + String dataName; + if (name==null) { + dataName = rDataExport.get(i).getName(); + } + else dataName = name + "_" + rDataExport.get(i).getName(); + + allData.add(dataName, dataListArray.build()); + allData.add(rDataExport.get(i).getName()+"_sR", new DoubleArrayVector(sampleRate)); + + dataUnitTypes.add(rDataExport.get(i).getName()); } } @@ -195,7 +231,7 @@ public class RExportManager implements PamDataUnitExporter { public PairList.Builder rData; /** - * List of the names of the types of data units whihc were saved. + * List of the names of the types of data units which were saved. */ public ArrayList dataUnitTypes; } @@ -218,5 +254,13 @@ public class RExportManager implements PamDataUnitExporter { } + @Override + public void close() { + if (allData!=null) { + writeRFile(); + } + } + + } diff --git a/src/export/RExport/RRawExport.java b/src/export/RExport/RRawExport.java index 1a7e7858..69ca7784 100644 --- a/src/export/RExport/RRawExport.java +++ b/src/export/RExport/RRawExport.java @@ -73,7 +73,7 @@ public class RRawExport extends RDataUnitExport { @Override public String getName() { - return "pam_data_units"; + return "raw_data_units"; } } diff --git a/src/export/swing/ExportTask.java b/src/export/swing/ExportTask.java index a68f65c5..665d9ded 100644 --- a/src/export/swing/ExportTask.java +++ b/src/export/swing/ExportTask.java @@ -60,8 +60,10 @@ public class ExportTask extends OfflineTask>{ @Override public void loadedDataComplete() { System.out.println("EXPORTER: loaded data complete"); + //force the exporter so save any renaming data units in the buffer - exporter.exportDataUnit(null, true); + exporter.exportDataUnit(null, true); + exporter.close(); exporter.setCurrentFile(null); } diff --git a/src/export/wavExport/WavFileExportManager.java b/src/export/wavExport/WavFileExportManager.java index 407560a8..4c588761 100644 --- a/src/export/wavExport/WavFileExportManager.java +++ b/src/export/wavExport/WavFileExportManager.java @@ -459,6 +459,14 @@ public class WavFileExportManager implements PamDataUnitExporter { + @Override + public void close() { + // TODO Auto-generated method stub + + } + + + // hello(){ diff --git a/src/pamViewFX/fxPlotPanes/PlotPane.java b/src/pamViewFX/fxPlotPanes/PlotPane.java index 6f94e068..f85fc79b 100644 --- a/src/pamViewFX/fxPlotPanes/PlotPane.java +++ b/src/pamViewFX/fxPlotPanes/PlotPane.java @@ -264,7 +264,7 @@ public class PlotPane extends PamBorderPane { rightPane.prefWidthProperty().bind(yAxisRightPane.widthProperty()); rightPane.minWidthProperty().bind(yAxisRightPane.widthProperty()); - horzHolder.getChildren().addAll(leftPane, axisPane); + horzHolder.getChildren().addAll(leftPane, axisPane, rightPane); //axisPane.toFront(); this changes the order of children in a PamHBox. HBox.setHgrow(axisPane, Priority.ALWAYS); From 9e13e9a8d59b8d30615c28da91be85ecb8cba725 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Tue, 28 May 2024 16:53:20 +0100 Subject: [PATCH 14/17] Minor bug fixes and tidying up for exporters --- .classpath | 3 ++- .settings/org.eclipse.core.resources.prefs | 1 - .settings/org.eclipse.jdt.core.prefs | 6 +++--- .../data/generic/GenericLinePlotInfo.java | 2 +- .../menuOptions/MLExportOverlayMenu.java | 2 +- .../menuOptions/RExportOverlayMenu.java | 14 +++++++------- src/export/RExport/RExportManager.java | 3 --- src/export/wavExport/WavFileExportManager.java | 6 ++---- 8 files changed, 16 insertions(+), 21 deletions(-) diff --git a/.classpath b/.classpath index 924124d8..576b210a 100644 --- a/.classpath +++ b/.classpath @@ -6,8 +6,9 @@ - + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index a474f512..51bb81c3 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,6 +1,5 @@ eclipse.preferences.version=1 encoding//src/rawDeepLearningClassifer/segmenter/SegmenterProcess.java=UTF-8 -encoding//src/test=UTF-8 encoding//src/test/resources=UTF-8 encoding/=UTF-8 encoding/src=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 43939db6..5c924643 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -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=21 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=18 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.compliance=18 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=21 +org.eclipse.jdt.core.compiler.source=18 diff --git a/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java b/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java index 92e19034..98b39929 100644 --- a/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java +++ b/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java @@ -86,7 +86,7 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { double[][] detData = getDetData(pamDataUnit); - if (lastUnits[chan]==null && detData!=null) { + if ((lastUnits[chan]==null || lastUnits[chan].length<1) && detData!=null) { //System.out.println("lastUnits: " + lastUnits); //create the array of last units. lastUnits[chan] = new Point2D[detData.length]; diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java index d0ace300..f0702f7f 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java @@ -58,7 +58,7 @@ public class MLExportOverlayMenu extends ExportOverlayMenu { buttonNode = createButton(); defaultPath=FileSystemView.getFileSystemView().getDefaultDirectory().getPath(); - defaultPath=defaultPath + "/Pamguard Manual Export"; + defaultPath=defaultPath + File.separator + "Pamguard Manual Export"; currentFolder=defaultPath; diff --git a/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java index 60011a4f..fd6f6a0f 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java @@ -49,7 +49,7 @@ public class RExportOverlayMenu extends ExportOverlayMenu { buttonNode = createButton(); defaultPath=FileSystemView.getFileSystemView().getDefaultDirectory().getPath(); - defaultPath=defaultPath + "/Pamguard Manual Export"; + defaultPath=defaultPath + File.separator + "Pamguard Manual Export"; currentFolder=defaultPath; @@ -113,8 +113,8 @@ public class RExportOverlayMenu extends ExportOverlayMenu { dataUnits.add(fnDataUnit); } - RData mlData=rExportManger.dataUnits2R(dataUnits); - if (mlData==null ){ + RData rData=rExportManger.dataUnits2R(dataUnits); + if (rData==null ){ //do nothing System.out.println("rOverlayMenu: no data units were converted to structs"); } @@ -134,12 +134,12 @@ public class RExportOverlayMenu extends ExportOverlayMenu { long millisStart=foundDataUnits.getFirstTimeMillis(); String currentPath = PamCalendar.formatFileDateTime(millisStart, false); //add data types to the filen,ae - for (int i=0 ;i Date: Wed, 29 May 2024 07:17:13 +0100 Subject: [PATCH 15/17] Add scaling icons to PAMGuard swing --- .../filedate/FileDateDialogStrip.java | 3 ++- src/Map/MapDetectionsDialog.java | 3 ++- src/Map/MapPanel.java | 12 +++------- src/PamView/TopToolBar.java | 18 ++++++++------- .../component/PamSettingsIconButton.java | 18 +++++++++++---- src/PamView/dialog/SettingsButton.java | 4 +++- src/PamView/hidingpanel/HidingDialog.java | 23 +++++++++++++------ .../paneloverlay/OverlayCheckboxMenuItem.java | 13 +++++++++-- .../paneloverlay/OverlayDataManager.java | 12 ++++------ .../offlineFuncs/OfflineToolbar.java | 12 +++++++--- .../layout/CompoundHidingTabPane.java | 11 +++++++-- src/offlineProcessing/OLProcessDialog.java | 3 ++- .../fxNodes/hidingPane/HidingPane.java | 2 +- .../dataPlotFX/DLPredictionPlotInfoFX.java | 2 +- .../panels/TargetMotionMainPanel.java | 3 ++- 15 files changed, 89 insertions(+), 50 deletions(-) diff --git a/src/Acquisition/filedate/FileDateDialogStrip.java b/src/Acquisition/filedate/FileDateDialogStrip.java index fb97ab26..c695f6de 100644 --- a/src/Acquisition/filedate/FileDateDialogStrip.java +++ b/src/Acquisition/filedate/FileDateDialogStrip.java @@ -20,6 +20,7 @@ import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import org.kordamp.ikonli.swing.FontIcon; import PamUtils.PamCalendar; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamGridBagContraints; /** @@ -40,7 +41,7 @@ public class FileDateDialogStrip { private JButton settingsButton; // private ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - public static FontIcon settingsIcon = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + public static FontIcon settingsIcon = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); private Window parent; diff --git a/src/Map/MapDetectionsDialog.java b/src/Map/MapDetectionsDialog.java index 1a9b100b..ed79530d 100644 --- a/src/Map/MapDetectionsDialog.java +++ b/src/Map/MapDetectionsDialog.java @@ -21,6 +21,7 @@ import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import org.kordamp.ikonli.swing.FontIcon; import PamController.PamController; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamDialog; import PamguardMVC.PamDataBlock; import PamguardMVC.dataSelector.DataSelector; @@ -90,7 +91,7 @@ public class MapDetectionsDialog extends PamDialog { MapDetectionData md; // ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - FontIcon settingsIcon = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + FontIcon settingsIcon = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); for (int i = 0; i < n; i++) { md = mapDetectionsParameters.mapDetectionDatas.get(i); diff --git a/src/Map/MapPanel.java b/src/Map/MapPanel.java index 225f01ef..cc00acd2 100644 --- a/src/Map/MapPanel.java +++ b/src/Map/MapPanel.java @@ -30,7 +30,6 @@ import java.awt.MouseInfo; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -39,17 +38,14 @@ import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.text.DecimalFormat; import java.util.ArrayList; -import java.util.ConcurrentModificationException; import java.util.List; import java.util.ListIterator; import java.util.Vector; -import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; -import pamScrollSystem.PamScrollSlider; import Array.ArrayManager; import Array.PamArray; import Array.SnapshotGeometry; @@ -61,7 +57,6 @@ import GPS.GpsData; import GPS.GpsDataUnit; import Map.gridbaselayer.GridbaseControl; import Map.gridbaselayer.MapRasterImage; -import PamController.PamControlledUnit; import PamController.PamController; import PamController.PamControllerInterface; import PamController.masterReference.MasterReferencePoint; @@ -83,7 +78,6 @@ import PamView.PamColors.PamColor; import PamView.panel.JPanelWithPamKey; import PamView.panel.KeyPanel; import PamView.paneloverlay.OverlayCheckboxMenuItem; -import PamView.paneloverlay.overlaymark.MarkDataMatcher; import PamView.paneloverlay.overlaymark.MarkDataSelector; import PamView.paneloverlay.overlaymark.MarkOverlayDraw; import PamView.symbol.PamSymbolChooser; @@ -1476,9 +1470,9 @@ public class MapPanel extends JPanelWithPamKey implements PamObserver, ColorMana plotDetectorMenu.add(menuItem); plotDetectorMenu.addSeparator(); - ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - ImageIcon settingsIconNot = new ImageIcon( - ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); +// ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// ImageIcon settingsIconNot = new ImageIcon( +// ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); ArrayList mddList = simpleMapRef.mapDetectionsManager.getMapDetectionDatas(); for (int i = 0; i < mddList.size(); i++) { diff --git a/src/PamView/TopToolBar.java b/src/PamView/TopToolBar.java index 2844aff9..bedace5c 100644 --- a/src/PamView/TopToolBar.java +++ b/src/PamView/TopToolBar.java @@ -1,6 +1,7 @@ package PamView; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; @@ -13,6 +14,10 @@ import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.Timer; +import org.kordamp.ikonli.materialdesign2.MaterialDesignP; +import org.kordamp.ikonli.materialdesign2.MaterialDesignR; +import org.kordamp.ikonli.swing.FontIcon; + import warnings.PamWarning; import warnings.SingleLineWarningDisplay; import warnings.WarningDisplay; @@ -22,6 +27,7 @@ import PamController.PamControlledUnit; import PamController.PamController; import PamUtils.PamCalendar; import PamView.PamColors.PamColor; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamLabel; import PamView.panel.PamPanel; @@ -51,19 +57,15 @@ public class TopToolBar extends PamToolBar implements ColorManaged { pamController = PamController.getInstance(); if (pamController.getRunMode() == PamController.RUN_PAMVIEW) { - add(startButton = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/playStart.png")))); + add(startButton = new JButton(FontIcon.of(MaterialDesignP.PLAY, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); startButton.setToolTipText("Start sound playback"); - add(stopButton = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/playPause.png")))); + add(stopButton = new JButton(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); stopButton.setToolTipText("Stop sound playback"); } else { - add(startButton = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/recordStart.png")))); + add(startButton = new JButton(FontIcon.of(MaterialDesignR.RECORD_CIRCLE, PamSettingsIconButton.NORMAL_SIZE, Color.RED))); startButton.setToolTipText("Start PAM processing"); - add(stopButton = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/playPause.png")))); + add(stopButton = new JButton(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); stopButton.setToolTipText("Stop PAM processing"); } startButton.addActionListener(new StartButton()); diff --git a/src/PamView/component/PamSettingsIconButton.java b/src/PamView/component/PamSettingsIconButton.java index 20e6feda..cfc25fd6 100644 --- a/src/PamView/component/PamSettingsIconButton.java +++ b/src/PamView/component/PamSettingsIconButton.java @@ -2,21 +2,29 @@ package PamView.component; import java.awt.Color; -import javax.swing.ImageIcon; import javax.swing.JButton; +import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import org.kordamp.ikonli.swing.FontIcon; public class PamSettingsIconButton extends JButton { + - /** - * - */ private static final long serialVersionUID = 1L; + public static final int SMALL_SIZE = 17; + + public static final int NORMAL_SIZE = 20; + + /** + * The ikon enum for the the setting button + */ + public static Ikon SETTINGS_IKON = MaterialDesignC.COG; + + // private static final ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - private static final FontIcon settingsIcon = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + private static final FontIcon settingsIcon = FontIcon.of(SETTINGS_IKON, NORMAL_SIZE, Color.DARK_GRAY); /** * Create a simple square button using the given icon. diff --git a/src/PamView/dialog/SettingsButton.java b/src/PamView/dialog/SettingsButton.java index 510a1711..e734b045 100644 --- a/src/PamView/dialog/SettingsButton.java +++ b/src/PamView/dialog/SettingsButton.java @@ -10,6 +10,8 @@ import javax.swing.JButton; import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import org.kordamp.ikonli.swing.FontIcon; +import PamView.component.PamSettingsIconButton; + /** * Standard settings button with the little cogwheel for use throughout Swing components. * @author dg50 @@ -27,7 +29,7 @@ public class SettingsButton extends JButton { } private static Icon makeIcon() { - return FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + return FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.DARK_GRAY); // return new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); } diff --git a/src/PamView/hidingpanel/HidingDialog.java b/src/PamView/hidingpanel/HidingDialog.java index 90b6d2d8..94c3b327 100644 --- a/src/PamView/hidingpanel/HidingDialog.java +++ b/src/PamView/hidingpanel/HidingDialog.java @@ -16,8 +16,13 @@ import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JPanel; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.materialdesign2.MaterialDesignP; +import org.kordamp.ikonli.swing.FontIcon; + import PamView.PamColors; import PamView.PamColors.PamColor; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamButtonAlpha; import PamView.dialog.PamDialog; import PamView.panel.CornerLayoutContraint; @@ -29,14 +34,18 @@ public class HidingDialog extends PamDialog { JCheckBox pinButton; - private ImageIcon settings=new ImageIcon(ClassLoader - .getSystemResource("Resources/SettingsButtonSmallWhite.png")); +// private ImageIcon settings=new ImageIcon(ClassLoader +// .getSystemResource("Resources/SettingsButtonSmallWhite.png")); +// +// private ImageIcon pinImage = new ImageIcon(ClassLoader +// .getSystemResource("Resources/pinbuttonwhite.png")); +// +// private ImageIcon pinHide = new ImageIcon(ClassLoader +// .getSystemResource("Resources/deletewhite.png")); - private ImageIcon pinImage = new ImageIcon(ClassLoader - .getSystemResource("Resources/pinbuttonwhite.png")); - - private ImageIcon pinHide = new ImageIcon(ClassLoader - .getSystemResource("Resources/deletewhite.png")); + private static final FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); + private static final FontIcon pinImage = FontIcon.of(MaterialDesignP.PIN, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); + private static final FontIcon pinHide = FontIcon.of(MaterialDesignP.PIN_OFF, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); public HidingDialog(Window parentFrame, HidingDialogPanel hidingDialogPanel, String title, boolean hasDefault) { diff --git a/src/PamView/paneloverlay/OverlayCheckboxMenuItem.java b/src/PamView/paneloverlay/OverlayCheckboxMenuItem.java index e76891c1..7c9d2904 100644 --- a/src/PamView/paneloverlay/OverlayCheckboxMenuItem.java +++ b/src/PamView/paneloverlay/OverlayCheckboxMenuItem.java @@ -1,5 +1,6 @@ package PamView.paneloverlay; +import java.awt.Color; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -8,6 +9,10 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.swing.FontIcon; + +import PamView.component.PamSettingsIconButton; import PamView.symbol.PamSymbolManager; import PamguardMVC.PamDataBlock; import PamguardMVC.dataSelector.DataSelector; @@ -23,8 +28,12 @@ import PamguardMVC.dataSelector.DataSelector; public class OverlayCheckboxMenuItem extends JCheckBoxMenuItem { - public static final ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - public static final ImageIcon settingsIconNot = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); +// public static final ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// public static final ImageIcon settingsIconNot = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); + + private static final FontIcon settingsIcon = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.DARK_GRAY); + private static final FontIcon settingsIconNot = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); + private static final long serialVersionUID = 1L; diff --git a/src/PamView/paneloverlay/OverlayDataManager.java b/src/PamView/paneloverlay/OverlayDataManager.java index 0bdf69db..a8d52586 100644 --- a/src/PamView/paneloverlay/OverlayDataManager.java +++ b/src/PamView/paneloverlay/OverlayDataManager.java @@ -5,16 +5,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.swing.ImageIcon; import javax.swing.JComponent; -import javax.swing.JMenu; -import javax.swing.JMenuItem; - import PamController.PamController; import PamView.GeneralProjector; import PamView.GeneralProjector.ParameterType; import PamView.GeneralProjector.ParameterUnits; -import PamView.PanelOverlayDraw; import PamView.symbol.PamSymbolChooser; import PamView.symbol.PamSymbolManager; import PamguardMVC.DataBlockNameComparator; @@ -33,9 +28,12 @@ import PamguardMVC.dataSelector.DataSelector; */ public abstract class OverlayDataManager implements OverlayDataObserver { - private ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// private ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// private ImageIcon settingsIconNot = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); + +// private static final FontIcon settingsIcon = FontIcon.of(MaterialDesignC.COG, 16, Color.DARK_GRAY); +// private static final FontIcon settingsIconNot = FontIcon.of(MaterialDesignC.COG, 16, Color.WHITE); - private ImageIcon settingsIconNot = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmallWhite.png")); private OverlaySwingPanel swingPanel; diff --git a/src/clickDetector/offlineFuncs/OfflineToolbar.java b/src/clickDetector/offlineFuncs/OfflineToolbar.java index 86efaf3e..9ff5ba5f 100644 --- a/src/clickDetector/offlineFuncs/OfflineToolbar.java +++ b/src/clickDetector/offlineFuncs/OfflineToolbar.java @@ -22,6 +22,9 @@ import javax.swing.JRadioButton; import javax.swing.JToolBar; import javax.swing.border.EmptyBorder; +import org.kordamp.ikonli.materialdesign2.MaterialDesignP; +import org.kordamp.ikonli.swing.FontIcon; + import soundPlayback.PlaybackControl; import clickDetector.BTDisplayParameters; import clickDetector.ClickBTDisplay; @@ -29,6 +32,7 @@ import clickDetector.ClickControl; import clickDetector.ClickDisplay; import clickDetector.ClickClassifiers.ClickIdentifier; import PamView.PamToolBar; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamCheckBox; import PamView.dialog.PamLabel; import PamView.dialog.PamRadioButton; @@ -75,14 +79,16 @@ public class OfflineToolbar { toolBar = new PamToolBar("Offline Click Analysis"); if (isViewer) { - playClicks = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/clickPlayStart.png"))); + +// private static final FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); + + playClicks = new JButton(FontIcon.of(MaterialDesignP.PLAY_CIRCLE_OUTLINE, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY)); playClicks.addActionListener(new PlayClicks()); playClicks.setToolTipText("Play clicks (pack empty space with 0's)"); PlaybackControl.registerPlayButton(playClicks); reAnalyseClicks = new JButton(new ImageIcon(ClassLoader - .getSystemResource("Resources/reanalyseClicks.png"))); + .getSystemResource("Resources/reanalyseClicks.png"))); reAnalyseClicks.addActionListener(new ReanalyseClicks()); reAnalyseClicks.setToolTipText("Re-analyse clicks"); } diff --git a/src/dataPlots/layout/CompoundHidingTabPane.java b/src/dataPlots/layout/CompoundHidingTabPane.java index 8462d110..72a44e86 100644 --- a/src/dataPlots/layout/CompoundHidingTabPane.java +++ b/src/dataPlots/layout/CompoundHidingTabPane.java @@ -10,6 +10,10 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.swing.FontIcon; + +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamButtonAlpha; import PamView.dialog.PamDialog; import PamView.hidingpanel.TabbedHidingPane; @@ -25,8 +29,11 @@ public class CompoundHidingTabPane extends TabbedHidingPane{ private static final long serialVersionUID = 1L; - public static final ImageIcon settingsImage=new ImageIcon(ClassLoader - .getSystemResource("Resources/SettingsButtonSmallWhite.png")); +// public static final ImageIcon settingsImage=new ImageIcon(ClassLoader +// .getSystemResource("Resources/SettingsButtonSmallWhite.png")); + + public static final FontIcon settingsImage = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.SMALL_SIZE, Color.WHITE); + public CompoundHidingTabPane() { diff --git a/src/offlineProcessing/OLProcessDialog.java b/src/offlineProcessing/OLProcessDialog.java index fbaace42..79601623 100644 --- a/src/offlineProcessing/OLProcessDialog.java +++ b/src/offlineProcessing/OLProcessDialog.java @@ -31,6 +31,7 @@ import PamUtils.PamCalendar; import PamUtils.TxtFileUtils; import PamView.CancelObserver; import PamView.DBTextArea; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamDialog; import PamView.dialog.PamFileBrowser; import PamView.dialog.PamGridBagContraints; @@ -93,7 +94,7 @@ public class OLProcessDialog extends PamDialog { // public static ImageIcon settings = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); - public static FontIcon settings = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + public static FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); TaskStatus currentStatus = TaskStatus.IDLE; diff --git a/src/pamViewFX/fxNodes/hidingPane/HidingPane.java b/src/pamViewFX/fxNodes/hidingPane/HidingPane.java index 26a19fc3..9e2ef7d8 100644 --- a/src/pamViewFX/fxNodes/hidingPane/HidingPane.java +++ b/src/pamViewFX/fxNodes/hidingPane/HidingPane.java @@ -37,7 +37,7 @@ public class HidingPane extends StackPane { /** * The opacity of the button when the mouse is outside the show button */ - private double showButtonOpacity = 0.25; + private double showButtonOpacity = 0.8; /** diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java index 36aed7ed..d5ab0f7f 100644 --- a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java +++ b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java @@ -94,7 +94,7 @@ public class DLPredictionPlotInfoFX extends GenericLinePlotInfo { if (getDlControl().getDLModel()!=null) { DLClassName[] classNames = getDlControl().getDLModel().getClassNames(); - System.out.println("Class names are: !!! " + (classNames == null ? "null" : classNames.length)); +// System.out.println("Class names are: !!! " + (classNames == null ? "null" : classNames.length)); if (classNames!=null) { diff --git a/src/targetMotionModule/panels/TargetMotionMainPanel.java b/src/targetMotionModule/panels/TargetMotionMainPanel.java index acfd1432..7899b3ab 100644 --- a/src/targetMotionModule/panels/TargetMotionMainPanel.java +++ b/src/targetMotionModule/panels/TargetMotionMainPanel.java @@ -38,6 +38,7 @@ import targetMotionModule.algorithms.TargetMotionModel; import PamDetection.PamDetection; import PamView.ColorManaged; import PamView.PamColors.PamColor; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; import PamView.panel.PamPanel; @@ -79,7 +80,7 @@ public class TargetMotionMainPanel implements PamTabPanel // public ImageIcon settings = new ImageIcon(ClassLoader // .getSystemResource("Resources/SettingsButtonSmall2.png")); - public static FontIcon settings = FontIcon.of(MaterialDesignC.COG, 20, Color.DARK_GRAY); + public static FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); public TargetMotionMainPanel(TargetMotionLocaliser targetMotionLocaliser) { From d294d3260a76924e162b4f9bbcb6570abb35f7ec Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Wed, 29 May 2024 08:04:47 +0100 Subject: [PATCH 16/17] Bug for MATLAB r export when angles are null --- src/detectionPlotFX/layout/DetectionPlotDisplay.java | 2 +- src/export/MLExport/MLRawExport.java | 4 ++-- src/export/RExport/RRawExport.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/detectionPlotFX/layout/DetectionPlotDisplay.java b/src/detectionPlotFX/layout/DetectionPlotDisplay.java index 1c77fa6e..16120cee 100644 --- a/src/detectionPlotFX/layout/DetectionPlotDisplay.java +++ b/src/detectionPlotFX/layout/DetectionPlotDisplay.java @@ -420,7 +420,7 @@ public class DetectionPlotDisplay extends PamBorderPane { } - System.out.println("Axis Width: " + dDPlotPane.getAxisPane(Side.BOTTOM).getWidth() + " canvas width " + dDPlotPane.getPlotCanvas().getWidth()); +// System.out.println("Axis Width: " + dDPlotPane.getAxisPane(Side.BOTTOM).getWidth() + " canvas width " + dDPlotPane.getPlotCanvas().getWidth()); currentDataInfo.drawData(dDPlotPane.getPlotCanvas().getGraphicsContext2D(), diff --git a/src/export/MLExport/MLRawExport.java b/src/export/MLExport/MLRawExport.java index eb01a350..9271ec9f 100644 --- a/src/export/MLExport/MLRawExport.java +++ b/src/export/MLExport/MLRawExport.java @@ -39,9 +39,9 @@ public class MLRawExport extends MLDataUnitExport{ Matrix angleErrors; if (dataUnit.getLocalisation()!=null) { //bearing angles - angles = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngles()); + angles = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngles() == null ? new double[] {0.} : dataUnit.getLocalisation().getAngles()); //angle errors - angleErrors = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngleErrors()); + angleErrors = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngleErrors() == null ? new double[] {0.} : dataUnit.getLocalisation().getAngleErrors()); } else { //bearing angles diff --git a/src/export/RExport/RRawExport.java b/src/export/RExport/RRawExport.java index 69ca7784..24f5abaa 100644 --- a/src/export/RExport/RRawExport.java +++ b/src/export/RExport/RRawExport.java @@ -52,9 +52,9 @@ public class RRawExport extends RDataUnitExport { //time delay stuff. if (dataUnit.getLocalisation()!=null) { //bearing angles - rData.add("angles", new DoubleArrayVector(dataUnit.getLocalisation().getAngles())); + rData.add("angles", dataUnit.getLocalisation().getAngles() == null ? new DoubleArrayVector(0.) : new DoubleArrayVector(dataUnit.getLocalisation().getAngles())); //angle errors - rData.add("angleErrors", new DoubleArrayVector(dataUnit.getLocalisation().getAngleErrors())); + rData.add("angleErrors", dataUnit.getLocalisation().getAngleErrors() == null? new DoubleArrayVector(0.) : new DoubleArrayVector(dataUnit.getLocalisation().getAngleErrors())); } else { //bearing angles From c89f05565bdf152f21ad7d5d9cf8b0a59c62d1c9 Mon Sep 17 00:00:00 2001 From: Jamie Mac Date: Wed, 29 May 2024 08:44:40 +0100 Subject: [PATCH 17/17] update to high res icons --- src/PamView/TopToolBar.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PamView/TopToolBar.java b/src/PamView/TopToolBar.java index bedace5c..d245c78c 100644 --- a/src/PamView/TopToolBar.java +++ b/src/PamView/TopToolBar.java @@ -58,14 +58,19 @@ public class TopToolBar extends PamToolBar implements ColorManaged { pamController = PamController.getInstance(); if (pamController.getRunMode() == PamController.RUN_PAMVIEW) { add(startButton = new JButton(FontIcon.of(MaterialDesignP.PLAY, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); + startButton.setDisabledIcon(FontIcon.of(MaterialDesignP.PLAY, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_GRAY)); startButton.setToolTipText("Start sound playback"); add(stopButton = new JButton(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); + stopButton.setDisabledIcon(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_GRAY)); + stopButton.setToolTipText("Stop sound playback"); } else { add(startButton = new JButton(FontIcon.of(MaterialDesignR.RECORD_CIRCLE, PamSettingsIconButton.NORMAL_SIZE, Color.RED))); + startButton.setDisabledIcon(FontIcon.of(MaterialDesignR.RECORD_CIRCLE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_GRAY)); startButton.setToolTipText("Start PAM processing"); add(stopButton = new JButton(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY))); + stopButton.setDisabledIcon(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_GRAY)); stopButton.setToolTipText("Stop PAM processing"); } startButton.addActionListener(new StartButton());