diff --git a/src/generalDatabase/SQLLogging.java b/src/generalDatabase/SQLLogging.java index 2588318a..142b3998 100644 --- a/src/generalDatabase/SQLLogging.java +++ b/src/generalDatabase/SQLLogging.java @@ -2000,6 +2000,10 @@ public abstract class SQLLogging { } PamSubtableData subtableData = new PamSubtableData(); long utc = SQLTypes.millisFromTimeStamp(subtableTableDef.getTimeStampItem().getValue()); + if (utc % 1000 == 0) { + int millis = subtableTableDef.getTimeStampMillis().getIntegerValue(); + utc += millis; + } subtableData.setChildUTC(utc); subtableData.setParentID(subtableTableDef.getParentID().getIntegerValue()); subtableData.setParentUID(subtableTableDef.getParentUID().getLongValue()); diff --git a/src/generalDatabase/SQLTypes.java b/src/generalDatabase/SQLTypes.java index a81edd89..e90aebf5 100644 --- a/src/generalDatabase/SQLTypes.java +++ b/src/generalDatabase/SQLTypes.java @@ -351,15 +351,13 @@ public class SQLTypes { * goes to the default, so it needs to be on UTC at the moment data are written to the database, not * just in this function. */ +// return timeMillis; if (timeMillis == null) { return null; } -// TimeZone tz = TimeZone.getDefault(); -// TimeZone.setDefault(PamCalendar.defaultTimeZone); -// Timestamp ts = new Timestamp(timeMillis - tz.getOffset(timeMillis)); - Timestamp ts = new Timestamp(timeMillis); -// TimeZone.setDefault(tz); -// Timestamp newTS = ts.toLocalDateTime(); +// return PamCalendar.formatDBDateTime(timeMillis, false); + TimeZone tz = TimeZone.getDefault(); + Timestamp ts = new UTCTimestamp(timeMillis - tz.getOffset(timeMillis)); return ts; } diff --git a/src/generalDatabase/UTCTimestamp.java b/src/generalDatabase/UTCTimestamp.java new file mode 100644 index 00000000..0bf3205d --- /dev/null +++ b/src/generalDatabase/UTCTimestamp.java @@ -0,0 +1,30 @@ +package generalDatabase; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * Override standard Timestamp class and stop it making UTC corrections as it writes to database + * @author dg50 + * + */ +public class UTCTimestamp extends Timestamp { + + private static final long serialVersionUID = 1L; + + public UTCTimestamp(long time) { + super(time); + // TODO Auto-generated constructor stub + } + + @Override + public long getTime() { + return super.getTime(); + } + + @Override + public LocalDateTime toLocalDateTime() { + return super.toLocalDateTime(); + } + +} diff --git a/src/generalDatabase/sqlite/SqliteSQLTypes.java b/src/generalDatabase/sqlite/SqliteSQLTypes.java index fa1a59ea..580c8c5e 100644 --- a/src/generalDatabase/sqlite/SqliteSQLTypes.java +++ b/src/generalDatabase/sqlite/SqliteSQLTypes.java @@ -44,6 +44,31 @@ public class SqliteSQLTypes extends SQLTypes { return super.systemSqlType(sqlType); } + @Override + public Object getTimeStamp(Long timeMillis) { + /** + * This has just got nasty WRT time zones. + * When the TimeStamp is written to database it uses the default time zone correction, which of course + * I don't want since I only do UTC. I was therefore subtracting this off before creating the ts + * so that it all worked fine when it was added back on again. + * This was fine for many years until someone processed data from exactly when the clocks went + * forward in the spring. Because the data were just after the clocks going forward, it took off + * an hour, then failed to add it back on again since the time was now before daylight saving. + * Amazed this has never happened before. Well done G and E ! I can fix it by setting the + * default time zone to UTC when PAMGuard starts, but note that all future references to local time + * will then be UTC. If I try to change it temporarily it doesn't help since the Timestamp always + * goes to the default, so it needs to be on UTC at the moment data are written to the database, not + * just in this function. + * + * Seems that for SQLite we can get away with a string, for MySQL we need a TimeStamp object still + */ +// return timeMillis; + if (timeMillis == null) { + return null; + } + return PamCalendar.formatDBDateTime(timeMillis, true); + } + @Override public String formatDBDateTimeQueryString(long timeMilliseconds) { switch (dateClass) { diff --git a/src/pamguard/Pamguard.java b/src/pamguard/Pamguard.java index d5825b26..f6865503 100644 --- a/src/pamguard/Pamguard.java +++ b/src/pamguard/Pamguard.java @@ -135,7 +135,7 @@ public class Pamguard { Thread folderSizeThread = new Thread(folderSizeMon); folderSizeThread.start(); - TimeZone.setDefault(PamCalendar.defaultTimeZone); +// TimeZone.setDefault(PamCalendar.defaultTimeZone); System.out.println("**********************************************************"); try { diff --git a/src/whistleClassifier/WhistleClasificationDataBlock.java b/src/whistleClassifier/WhistleClasificationDataBlock.java index e613bebf..e2b292e4 100644 --- a/src/whistleClassifier/WhistleClasificationDataBlock.java +++ b/src/whistleClassifier/WhistleClasificationDataBlock.java @@ -2,12 +2,25 @@ package whistleClassifier; import PamguardMVC.PamDataBlock; import PamguardMVC.PamProcess; +import PamguardMVC.dataSelector.DataSelectorCreator; +import whistleClassifier.dataselect.WslClsDataSelectCreator; public class WhistleClasificationDataBlock extends PamDataBlock { + + private WslClsDataSelectCreator dataSelectCreator; + private WhistleClassifierControl wslClassifierControl; - public WhistleClasificationDataBlock(PamProcess parentProcess, int channelMap) { + public WhistleClasificationDataBlock(WhistleClassifierControl wslClassifierControl, PamProcess parentProcess, int channelMap) { super(WhistleClassificationDataUnit.class, "Whistle Classification", parentProcess, channelMap); + this.wslClassifierControl = wslClassifierControl; + } + @Override + public DataSelectorCreator getDataSelectCreator() { + if (dataSelectCreator == null) { + dataSelectCreator = new WslClsDataSelectCreator(wslClassifierControl, this); + } + return dataSelectCreator; } } diff --git a/src/whistleClassifier/WhistleClassificationDataUnit.java b/src/whistleClassifier/WhistleClassificationDataUnit.java index 70750a94..3b7b4ebf 100644 --- a/src/whistleClassifier/WhistleClassificationDataUnit.java +++ b/src/whistleClassifier/WhistleClassificationDataUnit.java @@ -1,9 +1,10 @@ package whistleClassifier; +import PamDetection.PamDetection; import PamguardMVC.AcousticDataUnit; import PamguardMVC.PamDataUnit; -public class WhistleClassificationDataUnit extends PamDataUnit implements AcousticDataUnit { +public class WhistleClassificationDataUnit extends PamDataUnit implements AcousticDataUnit, PamDetection { private double[] speciesLogLikelihoods; diff --git a/src/whistleClassifier/WhistleClassifierProcess.java b/src/whistleClassifier/WhistleClassifierProcess.java index a6299148..dbbda4e3 100644 --- a/src/whistleClassifier/WhistleClassifierProcess.java +++ b/src/whistleClassifier/WhistleClassifierProcess.java @@ -65,7 +65,7 @@ public class WhistleClassifierProcess extends PamProcess { this.whistleClassifierControl = whistleClassifierControl; - whistleClasificationDataBlock = new WhistleClasificationDataBlock(this, 3); + whistleClasificationDataBlock = new WhistleClasificationDataBlock(whistleClassifierControl, this, 3); addOutputDataBlock(whistleClasificationDataBlock); diff --git a/src/whistleClassifier/dataselect/SppClsSelectParams.java b/src/whistleClassifier/dataselect/SppClsSelectParams.java new file mode 100644 index 00000000..7bf4ff94 --- /dev/null +++ b/src/whistleClassifier/dataselect/SppClsSelectParams.java @@ -0,0 +1,35 @@ +package whistleClassifier.dataselect; + +import java.io.Serializable; + +/** + * Data selector params for a single species. + * @author dg50 + * + */ +public class SppClsSelectParams implements Serializable, Cloneable { + + private static final long serialVersionUID = 1L; + + public String name; + public boolean selected; + public double minScore; + + public SppClsSelectParams(String name, boolean selected, double minScore) { + super(); + this.name = name; + this.selected = selected; + this.minScore = minScore; + } + + @Override + protected SppClsSelectParams clone() { + try { + return (SppClsSelectParams) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/whistleClassifier/dataselect/WslClsDataSelectCreator.java b/src/whistleClassifier/dataselect/WslClsDataSelectCreator.java new file mode 100644 index 00000000..77a3c850 --- /dev/null +++ b/src/whistleClassifier/dataselect/WslClsDataSelectCreator.java @@ -0,0 +1,31 @@ +package whistleClassifier.dataselect; + +import PamguardMVC.PamDataBlock; +import PamguardMVC.dataSelector.DataSelectParams; +import PamguardMVC.dataSelector.DataSelector; +import PamguardMVC.dataSelector.DataSelectorCreator; +import whistleClassifier.WhistleClasificationDataBlock; +import whistleClassifier.WhistleClassifierControl; + +public class WslClsDataSelectCreator extends DataSelectorCreator { + + private WhistleClassifierControl wslClassifierControl; + private WhistleClasificationDataBlock wslClassifierDataBlock; + + public WslClsDataSelectCreator(WhistleClassifierControl wslClassifierControl, WhistleClasificationDataBlock pamDataBlock) { + super(pamDataBlock); + this.wslClassifierControl = wslClassifierControl; + this.wslClassifierDataBlock = pamDataBlock; + } + + @Override + public DataSelector createDataSelector(String selectorName, boolean allowScores, String selectorType) { + return new WslClsDataSelector(wslClassifierControl, wslClassifierDataBlock, selectorName, allowScores); + } + + @Override + public DataSelectParams createNewParams(String name) { + return new WslClsSelectorParams(); + } + +} diff --git a/src/whistleClassifier/dataselect/WslClsDataSelector.java b/src/whistleClassifier/dataselect/WslClsDataSelector.java new file mode 100644 index 00000000..ca3e477a --- /dev/null +++ b/src/whistleClassifier/dataselect/WslClsDataSelector.java @@ -0,0 +1,70 @@ +package whistleClassifier.dataselect; + +import PamView.dialog.PamDialogPanel; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import PamguardMVC.dataSelector.DataSelectParams; +import PamguardMVC.dataSelector.DataSelector; +import pamViewFX.fxSettingsPanes.DynamicSettingsPane; +import whistleClassifier.WhistleClassificationDataUnit; +import whistleClassifier.WhistleClassifierControl; + +/** + * Species selector for whistle classifier. Currently only does yes / no, will + * maybe one day be extended to allow scores as well. + * @author dg50 + * + */ +public class WslClsDataSelector extends DataSelector { + + private WhistleClassifierControl wslClassifierControl; + + private WslClsSelectorParams wcsParams = new WslClsSelectorParams(); + + public WslClsDataSelector(WhistleClassifierControl wslClassifierControl, PamDataBlock pamDataBlock, String selectorName, boolean allowScores) { + super(pamDataBlock, selectorName, allowScores); + this.wslClassifierControl = wslClassifierControl; + } + + + @Override + public void setParams(DataSelectParams dataSelectParams) { + if (dataSelectParams instanceof WslClsSelectorParams) { + wcsParams = (WslClsSelectorParams) dataSelectParams; + } + } + + @Override + public WslClsSelectorParams getParams() { + return wcsParams; + } + + @Override + public PamDialogPanel getDialogPanel() { + return new WslClsDialogPanel(wslClassifierControl, this); + } + + @Override + public DynamicSettingsPane getDialogPaneFX() { + // TODO Auto-generated method stub + return null; + } + + @Override + public double scoreData(PamDataUnit pamDataUnit) { + WhistleClassificationDataUnit wcdu = (WhistleClassificationDataUnit) pamDataUnit; + String species = wcdu.getSpecies(); +// score = wcdu.get + SppClsSelectParams sppParams = wcsParams.getSppParams(species); +// if () +// if (sppParams.selected == false) { +// return 0; +// } +// if (isAllowScores()) { +// return sppP +// } +// wslClassifierControl.getWhistleClassificationParameters(). + return sppParams.selected ? 1 : 0; + } + +} diff --git a/src/whistleClassifier/dataselect/WslClsDialogPanel.java b/src/whistleClassifier/dataselect/WslClsDialogPanel.java new file mode 100644 index 00000000..1b2fe1b9 --- /dev/null +++ b/src/whistleClassifier/dataselect/WslClsDialogPanel.java @@ -0,0 +1,132 @@ +package whistleClassifier.dataselect; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.TitledBorder; + +import PamView.dialog.PamDialog; +import PamView.dialog.PamDialogPanel; +import PamView.dialog.PamGridBagContraints; +import PamguardMVC.dataSelector.DataSelectParams; +import whistleClassifier.FragmentClassifierParams; +import whistleClassifier.WhistleClassificationParameters; +import whistleClassifier.WhistleClassifierControl; + +/** + * dialog for whistle classifier data selector + * @author dg50 + * + */ +public class WslClsDialogPanel implements PamDialogPanel { + + private WhistleClassifierControl wslClassifierControl; + private WslClsDataSelector wslClsDataSelector; + + private JPanel mainPanel; + + private JPanel sppPanel; + + private JCheckBox[] speciesBoxes; + + private JTextField[] speciesScores; + + public WslClsDialogPanel(WhistleClassifierControl wslClassifierControl, WslClsDataSelector wslClsDataSelector) { + this.wslClassifierControl = wslClassifierControl; + this.wslClsDataSelector = wslClsDataSelector; + + this.mainPanel = new JPanel(new BorderLayout()); + mainPanel.setBorder(new TitledBorder("Select species")); + + sppPanel = new JPanel(new GridBagLayout()); + mainPanel.add(BorderLayout.CENTER, sppPanel); + } + + @Override + public JComponent getDialogComponent() { + return mainPanel; + } + + @Override + public void setParams() { + WhistleClassificationParameters wslParams = wslClassifierControl.getWhistleClassificationParameters(); + WslClsSelectorParams selParams = wslClsDataSelector.getParams(); + if (wslParams == null) { + return; + } + FragmentClassifierParams fragParams = wslParams.fragmentClassifierParams; + if (fragParams == null) { + return; + } + fillSppPanel(wslParams, selParams); + } + + private void fillSppPanel(WhistleClassificationParameters wslParams, WslClsSelectorParams selParams) { + boolean allowScores = wslClsDataSelector.isAllowScores(); + sppPanel.removeAll(); + String[] sppList = wslParams.fragmentClassifierParams.getSpeciesList(); + if (sppList == null) { + return; + } + int nSpp = sppList.length; + speciesBoxes = new JCheckBox[nSpp]; + speciesScores = new JTextField[nSpp]; + GridBagConstraints c = new PamGridBagContraints(); + sppPanel.add(new JLabel("Species", JLabel.CENTER), c); + if (allowScores) { + c.gridx++; + JLabel lab = new JLabel(" Min score ", JLabel.CENTER); + lab.setToolTipText("Minimum classification score (between 0 and 1)"); + sppPanel.add(lab , c); + } + for (int i = 0; i < nSpp; i++) { + speciesBoxes[i] = new JCheckBox(sppList[i]); + speciesScores[i] = new JTextField(3); + c.gridx = 0; + c.gridy++; + sppPanel.add(speciesBoxes[i], c); + if (allowScores) { + c.gridx++; + sppPanel.add(speciesScores[i], c); + } + SppClsSelectParams sppSel = selParams.getSppParams(sppList[i]); + speciesBoxes[i].setSelected(sppSel.selected); + speciesScores[i].setText(String.format("%3.2f", sppSel.minScore)); + } + } + + @Override + public boolean getParams() { + WslClsSelectorParams selParams = wslClsDataSelector.getParams(); + boolean allowScores = wslClsDataSelector.isAllowScores(); + if (speciesBoxes == null) { + return false; + } + int nSpp = speciesBoxes.length; + for (int i = 0; i < nSpp; i++) { + String name = speciesBoxes[i].getText(); + boolean sel = speciesBoxes[i].isSelected(); + double score = 0; + if (allowScores) { + try { + score = Double.valueOf(speciesScores[i].getText()); + } + catch (NumberFormatException e) { + score = -1; + } + } + if (score < 0 || score > 1) { + return PamDialog.showWarning(null, "Invalid score value for " + name, "Score values must be betwween 0 and 1"); + } + selParams.setSppParams(name, new SppClsSelectParams(name, sel, score)); + } + return true; + } + +} diff --git a/src/whistleClassifier/dataselect/WslClsSelectorParams.java b/src/whistleClassifier/dataselect/WslClsSelectorParams.java new file mode 100644 index 00000000..c3526433 --- /dev/null +++ b/src/whistleClassifier/dataselect/WslClsSelectorParams.java @@ -0,0 +1,46 @@ +package whistleClassifier.dataselect; + +import java.io.Serializable; +import java.util.HashMap; + +import PamguardMVC.dataSelector.DataSelectParams; + +public class WslClsSelectorParams extends DataSelectParams implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; + + private HashMap sppParamsTable = new HashMap<>(); + + public WslClsSelectorParams() { + // TODO Auto-generated constructor stub + } + + @Override + public WslClsSelectorParams clone() { + try { + return (WslClsSelectorParams) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + public void setSppParams(String sppName, SppClsSelectParams params) { + if (sppParamsTable == null) { + sppParamsTable = new HashMap(); + } + sppParamsTable.put(sppName, params); + } + + public SppClsSelectParams getSppParams(String sppName) { + if (sppParamsTable == null) { + sppParamsTable = new HashMap(); + } + SppClsSelectParams sppParams = sppParamsTable.get(sppName); + if (sppParams == null) { + sppParams = new SppClsSelectParams(sppName, false, 0); + } + return sppParams; + } + +}