diff --git a/pom.xml b/pom.xml index 1cc20d52..bbb2fbe6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.pamguard Pamguard - 2.02.10b + 2.02.10ad Pamguard Java12+ Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/src/Array/ArrayManager.java b/src/Array/ArrayManager.java index 2de2069c..6a605fff 100644 --- a/src/Array/ArrayManager.java +++ b/src/Array/ArrayManager.java @@ -25,6 +25,7 @@ import Array.sensors.swing.ArraySensorPanelProvider; import GPS.GPSControl; import GPS.GPSDataBlock; import GPS.GpsData; +import GPS.GpsDataUnit; import PamController.PamControlledUnit; import PamController.PamControlledUnitGUI; import PamController.PamControlledUnitSettings; @@ -32,6 +33,7 @@ import PamController.PamController; import PamController.PamGUIManager; import PamController.PamSettingManager; import PamController.PamSettings; +import PamController.masterReference.MasterReferencePoint; import PamController.positionreference.PositionReference; import PamModel.PamModuleInfo; import PamUtils.PamUtils; @@ -993,6 +995,7 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO * @return geometry data. */ public SnapshotGeometry getSnapshotGeometry(int hydrophoneMap, long timeMillis) { + PamArray currentArray = getCurrentArray(); if (currentArray == null) { return null; @@ -1080,14 +1083,45 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO } } - if (nGood > 0) for (int p = 0; p < 3; p++) { - centre[p] /= nGood; + if (nGood > 0) { + for (int p = 0; p < 3; p++) { + centre[p] /= nGood; + } + return new SnapshotGeometry(currentArray, timeMillis, streamerList, hydrophoneList, firstStreamerPos, + new PamVector(centre), geometry, streamerError, hydrophoneError); } - - return new SnapshotGeometry(currentArray, timeMillis, streamerList, hydrophoneList, firstStreamerPos, - new PamVector(centre), geometry, streamerError, hydrophoneError); + else { + return getMasterReferenceGeometry(timeMillis); + } } + + /** + * Create a snapshot geometry from the master reference position, which will either be the GPS data, or + * the centre point of the array. Worst case is that it ends up as 0,0,0 + * @param timeMillis + * @return + */ + private SnapshotGeometry getMasterReferenceGeometry(long timeMillis) { + GPSControl gpsControl = GPSControl.getGpsControl(); + GpsData referencePoint = null; + if (gpsControl != null) { + GpsDataUnit shipPos = gpsControl.getShipPosition(timeMillis, true); + if (shipPos != null) { + referencePoint = shipPos.getGpsData(); + } + } + if (referencePoint == null) { + // running out of options, so fall back to the master reference point, interpolated (probably has zero speeed) + referencePoint = new GpsData(MasterReferencePoint.getFixTime(), MasterReferencePoint.getLatLong()).getPredictedGPSData(timeMillis); + } + if (referencePoint == null) { + return null; + } + SnapshotGeometry snapgeom = new SnapshotGeometry(getCurrentArray(), timeMillis, null, null, referencePoint, new PamVector(0,0,0), null, null, null); + return snapgeom; + } + // // public SnapshotGeometry getSubDetectionGeometry(PamDataUnit superDataUnit) { // /** diff --git a/src/GPS/GPSControl.java b/src/GPS/GPSControl.java index 4bbca604..a2f9899d 100644 --- a/src/GPS/GPSControl.java +++ b/src/GPS/GPSControl.java @@ -4,6 +4,7 @@ import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.Serializable; +import java.util.ListIterator; import javax.swing.JMenu; import javax.swing.JMenuItem; @@ -22,6 +23,7 @@ import PamController.PamSettings; import PamController.positionreference.PositionReference; import PamUtils.PamCalendar; import PamView.dialog.warn.WarnOnce; +import PamguardMVC.PamDataBlock; public class GPSControl extends PamControlledUnit implements PamSettings, PositionReference { @@ -242,9 +244,68 @@ public class GPSControl extends PamControlledUnit implements PamSettings, Positi return super.removeUnit(); } + /** + * Gets the closest position based on time. No interpolation + * @param timeMilliseconds + * @return closest ship position based on time. + */ public GpsDataUnit getShipPosition(long timeMilliseconds) { return getGpsDataBlock().getClosestUnitMillis(timeMilliseconds); } + + /** + * Gets the closest position based on time. + * @param timeMilliseconds time in milliseconds + * @param interpolate interpolate between the point before and the one after. + * @return interpolated gps position. + */ + public GpsDataUnit getShipPosition(long timeMilliseconds, boolean interpolate) { + if (interpolate == false) { + return getGpsDataBlock().getClosestUnitMillis(timeMilliseconds); + } + // otherwise try to fine a point either side and weighted mean them or extrapolate. + GpsDataUnit pointBefore = null, pointAfter = null; + synchronized (getGpsDataBlock().getSynchLock()) { + ListIterator iter = getGpsDataBlock().getListIterator(timeMilliseconds, 0, PamDataBlock.MATCH_BEFORE, PamDataBlock.POSITION_BEFORE); + if (iter.hasNext()) { + pointBefore = iter.next(); + } + if (iter.hasNext()) { + pointAfter = iter.next(); + } + } + if (pointBefore == null && pointAfter == null) { + return null; + } + if (pointBefore != null & pointAfter != null) { + // interpolate since we have them both. + double tB = timeMilliseconds-pointBefore.getTimeMilliseconds(); + double tA = pointAfter.getTimeMilliseconds()-timeMilliseconds; + double wB = tA/(tB+tA); // weight for data before + double wA = tB/(tB+tA); // wedight for data after + double newLat = pointBefore.getGpsData().getLatitude()*wB + pointAfter.getGpsData().getLatitude()*wA; + double newLon = pointBefore.getGpsData().getLongitude()*wB + pointAfter.getGpsData().getLongitude()*wA; + // interpolating heading is more faff: + double hB = pointBefore.getGpsData().getHeading(); + double hA = pointAfter.getGpsData().getHeading(); + double hX = Math.sin(Math.toRadians(hB))*wB + Math.sin(Math.toRadians(hB))*wB; + double hY = Math.cos(Math.toRadians(hB))*wB + Math.cos(Math.toRadians(hB))*wB; + double newHead = Math.toDegrees(Math.atan2(hX, hY)); + GpsData newData = new GpsData(newLat, newLon, 0, timeMilliseconds); + newData.setTrueHeading(newHead); + return new GpsDataUnit(timeMilliseconds, newData); + } + else if (pointBefore != null) { + // relatively common if we enter data just after a position was received. + GpsData newPos = pointBefore.getGpsData().getPredictedGPSData(timeMilliseconds); + return new GpsDataUnit(timeMilliseconds, newPos); + } + else if (pointAfter != null) { + GpsData newPos = pointAfter.getGpsData().getPredictedGPSData(timeMilliseconds); + return new GpsDataUnit(timeMilliseconds, newPos); + } + return null; // should never happen since one of the above 4 clauses must be met. + } /** * Do we want this string ? It will be either RMC or GGA and may want wildcarding * @param stringId @@ -322,6 +383,7 @@ public class GPSControl extends PamControlledUnit implements PamSettings, Positi public GPSDataMatcher getGpsDataMatcher() { return gpsDataMatcher; } + @Override public GpsData getReferencePosition(long timeMillis) { GpsDataUnit gpDU = getShipPosition(timeMillis); diff --git a/src/PamController/PamConfiguration.java b/src/PamController/PamConfiguration.java index 44866e39..16496901 100644 --- a/src/PamController/PamConfiguration.java +++ b/src/PamController/PamConfiguration.java @@ -184,6 +184,23 @@ public class PamConfiguration { return dataBlock; } } + return tryShortName(blockType, name); + } + + /** + * For backwards compatibility, some blocks may still use the short name. + * @param blockType + * @param name + * @return + */ + private PamDataBlock tryShortName(Class blockType, String name) { + if (name == null) return null; + ArrayList blocks = getDataBlocks(blockType, true); + for (PamDataBlock dataBlock:blocks) { + if (name.equals(dataBlock.getDataName())) { // check for a long name match first + return dataBlock; + } + } return null; } diff --git a/src/PamController/PamController.java b/src/PamController/PamController.java index dfa37f56..447c876e 100644 --- a/src/PamController/PamController.java +++ b/src/PamController/PamController.java @@ -196,7 +196,7 @@ public class PamController implements PamControllerInterface, PamSettings { private Timer diagnosticTimer; - private boolean debugDumpBufferAtRestart = false; + private boolean debugDumpBufferAtRestart = true; private NetworkController networkController; private int nNetPrepared; @@ -1434,7 +1434,7 @@ public class PamController implements PamControllerInterface, PamSettings { pamControlledUnits.get(iU).flushDataBlockBuffers(2000); } } - dumpBufferStatus("In pamStopped, now idle", true); + dumpBufferStatus("In pamStopped, now idle", false); // wait here until the status has changed to Pam_Idle, so that we know // that we've really finished processing all data diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index ddb47056..12814aa5 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -31,12 +31,12 @@ public class PamguardVersionInfo { * Version number, major version.minorversion.sub-release. * Note: can't go higher than sub-release 'f' */ - static public final String version = "2.02.10b"; + static public final String version = "2.02.10ad"; /** * Release date */ - static public final String date = "2 March 2024"; + static public final String date = "13 March 2024"; // /** // * Release type - Beta or Core diff --git a/src/PamguardMVC/PamDataBlock.java b/src/PamguardMVC/PamDataBlock.java index 3bcce61a..ddc42abe 100644 --- a/src/PamguardMVC/PamDataBlock.java +++ b/src/PamguardMVC/PamDataBlock.java @@ -4296,6 +4296,9 @@ public class PamDataBlock extends PamObservable { * @param sayEmpties dump info even if a buffer is empty (otherwise, only ones that have stuff still) */ public void dumpBufferStatus(String message, boolean sayEmpties) { + if (sayEmpties || unitsAdded > 0) { + System.out.printf("Datablock %s: Added %d data units\n", getLongDataName(), unitsAdded); + } int nObs = countObservers(); for (int i = 0; i < nObs; i++) { PamObserver obs = getPamObserver(i); diff --git a/src/PamguardMVC/PamDataUnit.java b/src/PamguardMVC/PamDataUnit.java index 8630a7d1..12a9f085 100644 --- a/src/PamguardMVC/PamDataUnit.java +++ b/src/PamguardMVC/PamDataUnit.java @@ -564,7 +564,8 @@ abstract public class PamDataUnit /** * Get the latlong of the mean hydrophone position at the time of - * this detection. + * this detection. If the data unit has a channel bitmap of zero, then + * get the GPS position of the vessel at that time. * @param recalculate * @return Lat long of detection origin (usually the position of the reference hydrophone at time of detection) */ diff --git a/src/PamguardMVC/PamProcess.java b/src/PamguardMVC/PamProcess.java index e6321207..7aafca1f 100644 --- a/src/PamguardMVC/PamProcess.java +++ b/src/PamguardMVC/PamProcess.java @@ -126,6 +126,8 @@ abstract public class PamProcess implements PamObserver, ProcessAnnotator { private long cpuUsage; private long lastCPUCheckTime = System.currentTimeMillis(); private double cpuPercent; + private double totalCPU, peakCPU; + private int nCPU; /** * Flag for the process to say whether or not it's primary data connection @@ -140,6 +142,11 @@ abstract public class PamProcess implements PamObserver, ProcessAnnotator { * Last received data unit - used for working out timing offsets. */ private PamDataUnit lastAcousticDataunit; + + /* + * diagnostic count of all data processed + */ + private int nDataProcessed; // some flags and variables needed to deal with conversion from // milliseconds to samples and back // private boolean acousticDataSource = false; @@ -742,6 +749,7 @@ abstract public class PamProcess implements PamObserver, ProcessAnnotator { } // long cpuStart = SystemTiming.getProcessCPUTime(); long threadId = Thread.currentThread().getId(); + nDataProcessed++; long cpuStart = tmxb.getThreadCpuTime(threadId); newData(o, arg); if (processCheck != null) { @@ -762,6 +770,12 @@ abstract public class PamProcess implements PamObserver, ProcessAnnotator { * get percent. Total is -9+3+2 = /!0^4 ! */ cpuPercent = (double) cpuUsage / (now - lastCPUCheckTime) / 10000.; + // these next two allow us to take an average cpu. + totalCPU += cpuPercent; + nCPU++; + // and hte max cpu (may always go to 100 at end ?) + peakCPU = Math.max(cpuPercent, peakCPU); + lastCPUCheckTime = now; cpuUsage = 0; } @@ -1073,6 +1087,9 @@ abstract public class PamProcess implements PamObserver, ProcessAnnotator { * @param sayEmpties include info even if a buffer is empty. */ public void dumpBufferStatus(String message, boolean sayEmpties) { + if (sayEmpties || nDataProcessed > 0) { + System.out.printf("Process %s: ran %d datas, peak CPU %3.1f%%, mean CPU %3.1f%%\n", this.getProcessName(), nDataProcessed, peakCPU, totalCPU/nCPU); + } ArrayList outputs = getOutputDataBlocks(); try { for (PamDataBlock output : outputs) { diff --git a/src/help/JavaHelpSearch/DOCS b/src/help/JavaHelpSearch/DOCS index 0ed73b13..cde17fdc 100644 Binary files a/src/help/JavaHelpSearch/DOCS and b/src/help/JavaHelpSearch/DOCS differ diff --git a/src/help/JavaHelpSearch/DOCS.TAB b/src/help/JavaHelpSearch/DOCS.TAB index 47fba810..cc2b39a1 100644 Binary files a/src/help/JavaHelpSearch/DOCS.TAB and b/src/help/JavaHelpSearch/DOCS.TAB differ diff --git a/src/help/JavaHelpSearch/OFFSETS b/src/help/JavaHelpSearch/OFFSETS index 6dd107ae..7009f1f2 100644 Binary files a/src/help/JavaHelpSearch/OFFSETS and b/src/help/JavaHelpSearch/OFFSETS differ diff --git a/src/help/JavaHelpSearch/POSITIONS b/src/help/JavaHelpSearch/POSITIONS index b960a5dc..e221dfd5 100644 Binary files a/src/help/JavaHelpSearch/POSITIONS and b/src/help/JavaHelpSearch/POSITIONS differ diff --git a/src/help/JavaHelpSearch/SCHEMA b/src/help/JavaHelpSearch/SCHEMA index a7d9a08c..fbd20e84 100644 --- a/src/help/JavaHelpSearch/SCHEMA +++ b/src/help/JavaHelpSearch/SCHEMA @@ -1,2 +1,2 @@ JavaSearch 1.0 -TMAP bs=2048 rt=1 fl=-1 id1=6882 id2=1 +TMAP bs=2048 rt=1 fl=-1 id1=6905 id2=1 diff --git a/src/help/JavaHelpSearch/TMAP b/src/help/JavaHelpSearch/TMAP index b0102e13..f5edfdd4 100644 Binary files a/src/help/JavaHelpSearch/TMAP and b/src/help/JavaHelpSearch/TMAP differ diff --git a/src/help/Map.jhm b/src/help/Map.jhm index 69065281..3df49796 100644 --- a/src/help/Map.jhm +++ b/src/help/Map.jhm @@ -686,6 +686,8 @@ + + diff --git a/src/help/PAMGUARDHelpProject.xml b/src/help/PAMGUARDHelpProject.xml index fe06f32b..cca512e8 100644 --- a/src/help/PAMGUARDHelpProject.xml +++ b/src/help/PAMGUARDHelpProject.xml @@ -6,7 +6,7 @@ PAMGUARD - C:\Users\dg50\source\repos\PAMGuardDG_2\src\help + C:\Users\dg50\source\repos\PAMGuardPAMGuard\src\help index.html diff --git a/src/help/PAMGUARDTOC.xml b/src/help/PAMGUARDTOC.xml index e668ab2c..a5e48642 100644 --- a/src/help/PAMGUARDTOC.xml +++ b/src/help/PAMGUARDTOC.xml @@ -557,6 +557,10 @@ + + + + diff --git a/src/help/classifiers/matchedtemplate/mathchedtemplate.html b/src/help/classifiers/matchedtemplate/mathchedtemplate.html new file mode 100644 index 00000000..3173f5bd --- /dev/null +++ b/src/help/classifiers/matchedtemplate/mathchedtemplate.html @@ -0,0 +1,71 @@ + + +ROCCA Classifier Overview + + + +

Matched click classifier

+

Overview

+

The matched click classifier is an alternative to the in built click classifier in the click detection module which uses two click templates (target and reject) to classify individual clicks detections from the click detection module. The idea behind this classifier is to more accurately classify rarer clicks when there

+

How it works

+

The classifier is based on a matched filter i.e. a candidate click detection is compared to a template and it's maximum correlation value with the template is then used for classification. Each click is compared to both a match and a reject template. If the difference between the correlation match to the reject template exceeds a certain threshold, then the click is classified. There can be multiple combinations of match to reject templates allowing the matched classifier to deal with different types of clicks or even clicks from multiple species.

+

Configuring the matched click classifier

+

The matched click classifier settings are accessed via Settings-> Matched click classifier_. The settings are split into the three sections, general settings, click waveform and click templates.

+

+ +

+ +

The settings pane of the matched click classifier

+

General Settings

+

The general settings allow for channel options, species ID and the default colours for classified clicks to be to be set. +_ +Channel Options _allows users to define whether a single click from one channel, all clicks or an averaged click should be used to be used for classification in multi-channel situations. if there is only one channel then this makes no difference.

+

Click Type sets the number that defines the species ID. Make sure this is not the same as any of the species IDs in the default click classifier (this is why the default is set so high).

+

Symbol and Fill define the default colours clicks which have been classified by the matched click classifier should be plotted on displays.

+

Click Waveform Settings

+

Before a click is classified it undergoes some pre-conditioning by the matched click classifier.

+

Restrict parameter extraction to XX samples sets the maximum length of the waveform to the classifier. If this is selected, then center of the click is located and samples trimmed around the center. In some use cases, for example SoundTrap detections, which may be 10,000 samples long, setting a lower number of maximum samples can greatly increase processing speed and improve the accuracy of results. Note that if the number of samples is set too low (e.g. well below the click template length) then the correaltion values will not make much sense.

+

Peak threshold and Smoothing are both parameters used to find the click center so that a click can be trimmed to the maximum number of samples. The click length is measured by calculating the waveform envelope using a Hilbert Transform. The envelope is smoothed using a moving average filter (the Smoothing parameter defines the size of the averaging window). The click is trimmed as follows. First the peak of the waveform envelope is found. The length of the click is defined as the point at which the click falls Peak threshold dB below the peak. The center of the click is then the middle of this snippet. The click is then trimmed from the center of the click.

+

Amplitude normalisation If there is a very loud click compared to a template it's correlation score will be different to that of a very quiet click of with exactly the same waveform. It is therefore a good idea to normalise the waveform before it is compared the match click classifier. The types of normalisation are

+
    +
  • norm - this is the default - the click is divided by it's RMS amplitude.
  • +
  • peak to peak this can be useful for some types of shorter click e.g. dolphins - the click is divided by it's peak to peak amplitude.
  • +
  • none no normalisation (not recommended).
  • +
+

Template settings

+

The matched click classifier has two templates, a match and a reject. The match template should match a stereotypical click of the target species and the reject template can either be a click from a confounding species (e.g. dolphin) or blank. Selecting the drop-down menu from the Import button allows for a selection of some default templates. Custom templates can be imported by selecting the Import button. Custom templates can either be a .csv. or .mat file using the following format.

+

.csv - the first row are the waveform measurements from -1 to 1 (make sure you save with a sufficient number of decimal points). The second row and first column is the sample rate in sample per second.

+

.mat - a .mat file that contains two variables named sR and waveform. sR is the sample rate in samples per second and waveform is a 1D array containing the waveform for the template form -1 to 1. Arrays of click structures imported using the PAMGuard to MATLAB library can also be used. The first channel of the first click in a list of clicks structures will be imported as the template. The click structure should be names clicks and the sample rate should be saved as a seperate variable named sR in sampes per second.

+

The match and reject templates are plotted to provide a user with some visualisation of the classifier settings - the drop down menu allows the user to select different ways to plot the templates and is purely for visualisation purposes so makes no difference to classifier settings.

+

threshold is the threshold at which a click is classified. If the difference between the match and reject templates is above the threshold value then the +click is classified.

+

The + button can be used to add more tabs. Each tab contains a click/reject template pair and unique threshold setting. A click is classified if at least one of the match/reject templates passes it's set threshold.

+

Visualising Results

+

The matched click classifier changes the species type flag of a click if at least one of the classifiers passes threshold. This means clicks can be visualised in the click bearing time display or the time base display. The classifier also saves the correlation values for each match/reject template pair which can be accessed in MATLAB/R or through the PAMGuard GUI.

+

Click bearing time display

+

Matched clicks can be viewed in the bearing time display. If a click passes the threshold of one match/reject pair then the click symbol (defined in general settings) is shown in the bearing time display if Colour by Matched Template Classifier is selected in the right click menu. The correlation values are shown by hovering the mouse over a click to bring up the info tool tip.

+

+ +

+ +

Screenshot of clicks classified from the matched click classifier showing matched clicks (coloured pink)

+

Time base display

+

The time base display FX can show clicks classified by the matched clicks classifier. Colour by Matched Template Classifier must be selecting in the right settings window.

+

+ +

+ +

Screenshot of Time Base display FX showing clicks classified by the match click classifier (coloured pink). The correlation values can be found in the meta data section of the detection pop up menu (highlighted)

+

Note that the time base display allows users to export clicks to be used as templates. Using the advanced pop up menu right click on a click detection and select the MATLAB icon. A .mat file of the selected click or clicks will be saved to your root user folder in a folder called PAMGuard Manual Export. This .mat file can be opened by the matched click classifier - the first channel of the first click in the list will be imported as a template.

+

+ +

+ + +

Extracting correlation values using MATLAB/R

+

Clicks are loaded from binary files using the MATLAB/R function

+
clicks  = loadPamguardBinaryFile("/path/to/pamguardfile.pgdf");
+

where clicks is a list of MATLAB/R structures containing the data for each click in the file.

+

The matched click classifier template threshold, match value and reject value are then accessed from each click using

+
matchedtemplatevals = clicks(1).annotations. mclassification
+

matachedtemplatevals is a list of where each row is the threshold value, match value and reject value for each match/reject template pair.

diff --git a/src/help/classifiers/matchedtemplate/resources/exporting_mat_clicks.png b/src/help/classifiers/matchedtemplate/resources/exporting_mat_clicks.png new file mode 100644 index 00000000..3a2f1c01 Binary files /dev/null and b/src/help/classifiers/matchedtemplate/resources/exporting_mat_clicks.png differ diff --git a/src/help/classifiers/matchedtemplate/resources/matched_click_bt_display.png b/src/help/classifiers/matchedtemplate/resources/matched_click_bt_display.png new file mode 100644 index 00000000..3b369018 Binary files /dev/null and b/src/help/classifiers/matchedtemplate/resources/matched_click_bt_display.png differ diff --git a/src/help/classifiers/matchedtemplate/resources/matched_click_dialog_summary.png b/src/help/classifiers/matchedtemplate/resources/matched_click_dialog_summary.png new file mode 100644 index 00000000..a8809d7b Binary files /dev/null and b/src/help/classifiers/matchedtemplate/resources/matched_click_dialog_summary.png differ diff --git a/src/help/classifiers/matchedtemplate/resources/matched_click_tdisplay_example.png b/src/help/classifiers/matchedtemplate/resources/matched_click_tdisplay_example.png new file mode 100644 index 00000000..5bdfd7e8 Binary files /dev/null and b/src/help/classifiers/matchedtemplate/resources/matched_click_tdisplay_example.png differ diff --git a/src/serialComms/SerialPortPanel.java b/src/serialComms/SerialPortPanel.java index dbf3b816..4fdc63d9 100644 --- a/src/serialComms/SerialPortPanel.java +++ b/src/serialComms/SerialPortPanel.java @@ -236,9 +236,12 @@ public class SerialPortPanel extends Object { } } - public String getPort() { if (portList == null) return null; + Object selItem = portList.getSelectedItem(); + if (selItem == null) { + return null; + } return portList.getSelectedItem().toString(); }