diff --git a/.classpath b/.classpath index 26c568d7..49faf461 100644 --- a/.classpath +++ b/.classpath @@ -6,9 +6,8 @@ - + - diff --git a/.gitignore b/.gitignore index 61250077..6254b898 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,73 @@ settings.xml .classpath .classpath .classpath +.metadata/version.ini +.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml +.metadata/.plugins/org.eclipse.ui.intro/introstate +.metadata/.plugins/org.eclipse.tips.ide/dialog_settings.xml +.metadata/.plugins/org.eclipse.oomph.setup/workspace.setup +.metadata/.plugins/org.eclipse.m2e.logback/logback.2.1.100.20230106-1511.xml +.metadata/.plugins/org.eclipse.m2e.core/workspaceState.ser +.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml +.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml +.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml +.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat +.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache +.metadata/.plugins/org.eclipse.jdt.core/javaLikeNames.txt +.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache +.metadata/.plugins/org.eclipse.jdt.core/assumedExternalFilesCache +.metadata/.plugins/org.eclipse.egit.core/.org.eclipse.egit.core.cmp/.settings/org.eclipse.core.resources.prefs +.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.m2e.discovery.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs +.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources +.metadata/.plugins/org.eclipse.core.resources/.root/1.tree +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version +.metadata/.plugins/org.eclipse.core.resources/.projects/.org.eclipse.egit.core.cmp/.location +.metadata/.lock +.metadata/version.ini +.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml +.metadata/.plugins/org.eclipse.ui.intro/introstate +.metadata/.plugins/org.eclipse.tips.ide/dialog_settings.xml +.metadata/.plugins/org.eclipse.oomph.setup/workspace.setup +.metadata/.plugins/org.eclipse.m2e.logback/logback.2.1.100.20230106-1511.xml +.metadata/.plugins/org.eclipse.m2e.core/workspaceState.ser +.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml +.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml +.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml +.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat +.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache +.metadata/.plugins/org.eclipse.jdt.core/javaLikeNames.txt +.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache +.metadata/.plugins/org.eclipse.jdt.core/assumedExternalFilesCache +.metadata/.plugins/org.eclipse.egit.core/.org.eclipse.egit.core.cmp/.settings/org.eclipse.core.resources.prefs +.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.m2e.discovery.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs +.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs +.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources +.metadata/.plugins/org.eclipse.core.resources/.root/1.tree +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index +.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version +.settings/org.eclipse.jdt.core.prefs +.settings/org.eclipse.core.resources.prefs +.classpath +.classpath +.project +.classpath +.classpath +.classpath +.classpath +.classpath +.classpath +.settings/org.eclipse.jdt.core.prefs diff --git a/.metadata/.lock b/.metadata/.lock new file mode 100644 index 00000000..e69de29b diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/.org.eclipse.egit.core.cmp/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/.org.eclipse.egit.core.cmp/.location new file mode 100644 index 00000000..98851383 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/.org.eclipse.egit.core.cmp/.location differ diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version new file mode 100644 index 00000000..25cb955b --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index new file mode 100644 index 00000000..d2372511 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index differ diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version new file mode 100644 index 00000000..6b2aaa76 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree b/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree new file mode 100644 index 00000000..89bbc184 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree differ diff --git a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources new file mode 100644 index 00000000..bd315dac Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources differ diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..30841ebf --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding=UTF-8 +version=1 diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000..67094041 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.ui.formatterprofiles.version=23 +spelling_locale=en_GB +spelling_locale_initialized=true +typefilter_migrated_2=true +useAnnotationsPrefPage=true +useQuickDiffPrefPage=true diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.m2e.discovery.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.m2e.discovery.prefs new file mode 100644 index 00000000..67b1d96c --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.m2e.discovery.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.m2e.discovery.pref.projects= diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs new file mode 100644 index 00000000..bc1720e5 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +platformState=1678968029917 +quickStart=false +tipsAndTricks=true diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs new file mode 100644 index 00000000..08076f23 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +showIntro=false diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs new file mode 100644 index 00000000..7e380fb0 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs @@ -0,0 +1,12 @@ +//org.eclipse.ui.commands/state/org.eclipse.ui.navigator.resources.nested.changeProjectPresentation/org.eclipse.ui.commands.radioState=false +PLUGINS_NOT_ACTIVATED_ON_STARTUP=;org.eclipse.m2e.discovery; +eclipse.preferences.version=1 +org.eclipse.ui.workbench.ACTIVE_NOFOCUS_TAB_BG_END=41,41,41 +org.eclipse.ui.workbench.ACTIVE_NOFOCUS_TAB_BG_START=43,44,45 +org.eclipse.ui.workbench.ACTIVE_NOFOCUS_TAB_TEXT_COLOR=204,204,204 +org.eclipse.ui.workbench.ACTIVE_TAB_BG_END=41,41,41 +org.eclipse.ui.workbench.ACTIVE_TAB_BG_START=43,44,45 +org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR=221,221,221 +org.eclipse.ui.workbench.INACTIVE_TAB_BG_END=49,53,56 +org.eclipse.ui.workbench.INACTIVE_TAB_BG_START=59,64,66 +org.eclipse.ui.workbench.INACTIVE_TAB_TEXT_COLOR=187,187,187 diff --git a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi new file mode 100644 index 00000000..8106a865 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi @@ -0,0 +1,2360 @@ + + + + activeSchemeId:org.eclipse.ui.defaultAcceleratorConfiguration + + + + + + + + topLevel + + + + + persp.actionSet:org.eclipse.ui.cheatsheets.actionSet + persp.actionSet:org.eclipse.search.searchActionSet + persp.actionSet:org.eclipse.text.quicksearch.actionSet + persp.actionSet:org.eclipse.ui.edit.text.actionSet.annotationNavigation + persp.actionSet:org.eclipse.ui.edit.text.actionSet.navigation + persp.actionSet:org.eclipse.ui.edit.text.actionSet.convertLineDelimitersTo + persp.actionSet:org.eclipse.ui.externaltools.ExternalToolsSet + persp.actionSet:org.eclipse.ui.actionSet.keyBindings + persp.actionSet:org.eclipse.ui.actionSet.openFiles + persp.actionSet:org.eclipse.debug.ui.launchActionSet + persp.actionSet:org.eclipse.jdt.ui.JavaActionSet + persp.actionSet:org.eclipse.jdt.ui.JavaElementCreationActionSet + persp.actionSet:org.eclipse.ui.NavigateActionSet + persp.viewSC:org.eclipse.jdt.ui.PackageExplorer + persp.viewSC:org.eclipse.jdt.ui.TypeHierarchy + persp.viewSC:org.eclipse.jdt.ui.SourceView + persp.viewSC:org.eclipse.jdt.ui.JavadocView + persp.viewSC:org.eclipse.search.ui.views.SearchView + persp.viewSC:org.eclipse.ui.console.ConsoleView + persp.viewSC:org.eclipse.ui.views.ContentOutline + persp.viewSC:org.eclipse.ui.views.ProblemView + persp.viewSC:org.eclipse.ui.views.TaskList + persp.viewSC:org.eclipse.ui.views.ProgressView + persp.viewSC:org.eclipse.ui.navigator.ProjectExplorer + persp.viewSC:org.eclipse.ui.texteditor.TemplatesView + persp.viewSC:org.eclipse.pde.runtime.LogView + persp.newWizSC:org.eclipse.jdt.ui.wizards.JavaProjectWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewPackageCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewClassCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewInterfaceCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewEnumCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewRecordCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewAnnotationCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewSourceFolderCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewSnippetFileCreationWizard + persp.newWizSC:org.eclipse.jdt.ui.wizards.NewJavaWorkingSetWizard + persp.newWizSC:org.eclipse.ui.wizards.new.folder + persp.newWizSC:org.eclipse.ui.wizards.new.file + persp.newWizSC:org.eclipse.ui.editors.wizards.UntitledTextFileWizard + persp.perspSC:org.eclipse.jdt.ui.JavaBrowsingPerspective + persp.perspSC:org.eclipse.debug.ui.DebugPerspective + persp.showIn:org.eclipse.jdt.ui.PackageExplorer + persp.showIn:org.eclipse.team.ui.GenericHistoryView + persp.showIn:org.eclipse.ui.navigator.ProjectExplorer + persp.actionSet:org.eclipse.debug.ui.breakpointActionSet + persp.actionSet:org.eclipse.jdt.debug.ui.JDTDebugActionSet + persp.showIn:org.eclipse.egit.ui.RepositoriesView + persp.newWizSC:org.eclipse.m2e.core.wizards.Maven2ProjectWizard + persp.actionSet:org.eclipse.eclemma.ui.CoverageActionSet + persp.showIn:org.eclipse.eclemma.ui.CoverageView + persp.viewSC:org.eclipse.tm.terminal.view.ui.TerminalsView + persp.showIn:org.eclipse.tm.terminal.view.ui.TerminalsView + persp.viewSC:org.eclipse.jdt.bcoview.views.BytecodeOutlineView + persp.newWizSC:org.eclipse.jdt.junit.wizards.NewTestCaseCreationWizard + persp.actionSet:org.eclipse.jdt.junit.JUnitActionSet + persp.viewSC:org.eclipse.ant.ui.views.AntView + + + + org.eclipse.e4.primaryNavigationStack + active + noFocus + + View + categoryTag:Java + + + View + categoryTag:Java + + + View + categoryTag:General + + + View + categoryTag:Java + + + + + View + categoryTag:Git + + + + + + + + org.eclipse.e4.secondaryNavigationStack + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:Java + + + View + categoryTag:Ant + + + + + org.eclipse.e4.secondaryDataStack + + View + categoryTag:General + + + View + categoryTag:Java + + + View + categoryTag:Java + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:Terminal + + + + + + + + + View + categoryTag:Help + + + View + categoryTag:General + + + View + categoryTag:Help + + + + + + + View + categoryTag:Help + + + + + + View + categoryTag:General + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:Help + + + + org.eclipse.e4.primaryDataStack + EditorStack + + + + + + + View + categoryTag:Java + active + activeOnClose + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:Java + + + + + View + categoryTag:General + + + + + + View + categoryTag:General + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + + View + categoryTag:General + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:Git + + + + + View + categoryTag:Terminal + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:Ant + + + + toolbarSeparator + + + + Draggable + + + + toolbarSeparator + + + + Draggable + + + toolbarSeparator + + + + Draggable + + + Draggable + + + Draggable + + + toolbarSeparator + + + + Draggable + + + + toolbarSeparator + + + + toolbarSeparator + + + + Draggable + + + stretch + SHOW_RESTORE_MENU + + + Draggable + HIDEABLE + SHOW_RESTORE_MENU + + + + + stretch + + + Draggable + + + Draggable + + + + + TrimStack + Draggable + + + + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + + + + platform:cocoa + + + platform:cocoa + + + + + + platform:cocoa + + + + + + + + + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + + + + + + + + platform:cocoa + + + + + + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + + + + + platform:cocoa + + + + + + + platform:cocoa + + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + + + + platform:cocoa + + + + platform:cocoa + + + + + platform:cocoa + + + + platform:cocoa + + + + + + + + + platform:cocoa + + + + + platform:cocoa + + + platform:cocoa + + + + + + + + platform:cocoa + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + + + + + platform:cocoa + + + + + + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + + platform:cocoa + + + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + + + + platform:cocoa + + + + + + + + + + + + + + + + platform:cocoa + + + + + platform:cocoa + + + platform:cocoa + + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + platform:cocoa + + + + + platform:cocoa + + + + + + + platform:cocoa + + + + platform:cocoa + + + + + + + + + + + + + + + + + + + + + + + platform:cocoa + + + platform:cocoa + + + + + + + platform:cocoa + + + + platform:cocoa + + + + + platform:cocoa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + platform:cocoa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Editor + removeOnHide + + + + + View + categoryTag:Ant + + + + + View + categoryTag:Gradle + + + + + View + categoryTag:Gradle + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Debug + + + View + categoryTag:Debug + + + + + View + categoryTag:Java + + + + + View + categoryTag:Git + + + + + View + categoryTag:Git + + + + + View + categoryTag:Git + + + + + View + categoryTag:Git + NoRestore + + + + + View + categoryTag:Git + + + + + View + categoryTag:Help + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:Debug + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java Browsing + + + + + View + categoryTag:Java Browsing + + + + + View + categoryTag:Java Browsing + + + + + View + categoryTag:Java Browsing + + + + + View + categoryTag:Java + + + + + View + categoryTag:General + + + + + View + categoryTag:Java + + + + + View + categoryTag:Java + + + + + View + categoryTag:Maven + + + + + View + categoryTag:Maven + + + + + View + categoryTag:Maven + + + + + View + categoryTag:Oomph + + + + + View + categoryTag:General + + + + + View + categoryTag:Version Control (Team) + + + + + View + categoryTag:Version Control (Team) + + + View + categoryTag:Help + + + + + View + categoryTag:Terminal + + + + + View + categoryTag:Other + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:Help + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + glue + move_after:PerspectiveSpacer + SHOW_RESTORE_MENU + + + move_after:Spacer Glue + HIDEABLE + SHOW_RESTORE_MENU + + + glue + move_after:SearchField + SHOW_RESTORE_MENU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.metadata/.plugins/org.eclipse.egit.core/.org.eclipse.egit.core.cmp/.settings/org.eclipse.core.resources.prefs b/.metadata/.plugins/org.eclipse.egit.core/.org.eclipse.egit.core.cmp/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..99f26c02 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.egit.core/.org.eclipse.egit.core.cmp/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.metadata/.plugins/org.eclipse.jdt.core/assumedExternalFilesCache b/.metadata/.plugins/org.eclipse.jdt.core/assumedExternalFilesCache new file mode 100644 index 00000000..593f4708 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.jdt.core/assumedExternalFilesCache differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache b/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache new file mode 100644 index 00000000..593f4708 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/javaLikeNames.txt b/.metadata/.plugins/org.eclipse.jdt.core/javaLikeNames.txt new file mode 100644 index 00000000..85863977 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.jdt.core/javaLikeNames.txt @@ -0,0 +1 @@ +java \ No newline at end of file diff --git a/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache b/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache new file mode 100644 index 00000000..593f4708 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat b/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat new file mode 100644 index 00000000..0edae4b2 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat differ diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml new file mode 100644 index 00000000..a4ee3cbc --- /dev/null +++ b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml @@ -0,0 +1,2 @@ + + diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml new file mode 100644 index 00000000..9e390f50 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml @@ -0,0 +1,2 @@ + + diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml new file mode 100644 index 00000000..c8468c1f --- /dev/null +++ b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml @@ -0,0 +1,10 @@ + +
+
+ + + + + +
+
diff --git a/.metadata/.plugins/org.eclipse.m2e.core/workspaceState.ser b/.metadata/.plugins/org.eclipse.m2e.core/workspaceState.ser new file mode 100644 index 00000000..abbf8e52 Binary files /dev/null and b/.metadata/.plugins/org.eclipse.m2e.core/workspaceState.ser differ diff --git a/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.1.100.20230106-1511.xml b/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.1.100.20230106-1511.xml new file mode 100644 index 00000000..abdf1c73 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.1.100.20230106-1511.xml @@ -0,0 +1,41 @@ + + + + %date [%thread] %-5level %logger{35} - %msg%n + + + OFF + + + + + ${org.eclipse.m2e.log.dir}/0.log + + ${org.eclipse.m2e.log.dir}/%i.log + 1 + 10 + + + 10MB + + + %date [%thread] %-5level %logger{35} - %msg%n + + + + + + WARN + + + + + + + + + + + + + diff --git a/.metadata/.plugins/org.eclipse.oomph.setup/workspace.setup b/.metadata/.plugins/org.eclipse.oomph.setup/workspace.setup new file mode 100644 index 00000000..1f73e14c --- /dev/null +++ b/.metadata/.plugins/org.eclipse.oomph.setup/workspace.setup @@ -0,0 +1,6 @@ + + diff --git a/.metadata/.plugins/org.eclipse.tips.ide/dialog_settings.xml b/.metadata/.plugins/org.eclipse.tips.ide/dialog_settings.xml new file mode 100644 index 00000000..5ca0b776 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.tips.ide/dialog_settings.xml @@ -0,0 +1,3 @@ + +
+
diff --git a/.metadata/.plugins/org.eclipse.ui.intro/introstate b/.metadata/.plugins/org.eclipse.ui.intro/introstate new file mode 100644 index 00000000..02f134f0 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.ui.intro/introstate @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml b/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml new file mode 100644 index 00000000..cc5a0219 --- /dev/null +++ b/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.metadata/version.ini b/.metadata/version.ini new file mode 100644 index 00000000..c005b7a5 --- /dev/null +++ b/.metadata/version.ini @@ -0,0 +1,3 @@ +#Thu Mar 16 12:01:36 GMT 2023 +org.eclipse.core.runtime=2 +org.eclipse.platform=4.27.0.v20230302-0300 diff --git a/.project b/.project index 48129c11..c8e79578 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - PamGuard Main Tethys + PAMGuard diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 4df196f4..51bb81c3 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 encoding//src/rawDeepLearningClassifer/segmenter/SegmenterProcess.java=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 fbf1aacf..ecb498c8 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=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.compliance=17 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=11 +org.eclipse.jdt.core.compiler.source=17 diff --git a/README.html b/README.html index d4faac7a..8f8d31ad 100644 --- a/README.html +++ b/README.html @@ -387,8 +387,11 @@ PamguardBeta_ViewerMode.exe):

Latest Version 2.02.10 January 2024

+font-family:"Cambria",serif;font-style:normal'>Latest +Version 2.02.11 April 2024 + +

Version 2.02.10 January 2024

Version 2.02.09 June 2023

@@ -461,14 +464,47 @@ Version 2.00.10 June 2017

Version 2.02.10 January 2024

+name="_Version_2.02.10_January">Version +2.02.11 April 2024 + +

 

+ +

New Features

+ +

Click detector: Remembers locations of displays and doesnt +continually reset them.

+ +

Help for Matched Click Classifier

+ +

Bug Fixes

+ +

Linking clicks to offline clicks table. We had a database +that had become corrupted so added code to relink offline clicks to their corresponding +clicks from binary data.

+ +

Drawing non-acoustic data: Data that were not associated +with any hydrophones, e.g. visual sightings in Logger forms were not drawing on +the map. This fixed and PAMGuard will use the vessels GPS position as +reference.

+ +

Lookup tables: fix feature which was causing table entries +to repeat.

+ +

Click Train Detector: Add exception handlers to avoid errors +as PAMGuard stops / restarts.

+ +

Group Detection starts and ends: Check data integrity +function fixed and now inserts correct times of start and ends of events into +database.

+ +

Version 2.02.10 January 2024

New Features

Importing modules from other configurations: New options from file menu allowing -import of specific modules, or module settings from other configurations. E.g. -if you had three similar configurations and had set one of them up with a new +lang=EN-US> from other configurations: New options from file menu allowing import +of specific modules, or module settings from other configurations. E.g. if you +had three similar configurations and had set one of them up with a new detector, or got the click classifier settings set up just right in one of those configurations, you can import the additional modules or the click detector settings easily into the other configurations.

@@ -496,9 +532,9 @@ correctly saving updated bearings to the database. Now fixed.

ROCCA Classifier fixes

-

Allow Rocca to run without classifiers: -Fixed bug that threw an error if no classifier files were  specified in -Rocca Params dialog

+

Allow Rocca to run without classifiers: Fixed +bug that threw an error if no classifier files were  specified in Rocca +Params dialog

Fix memory issue with RoccaContourDataBlocks not being released for garbage collection

@@ -596,9 +632,9 @@ angle offsets applied to static hydrophones in viewer mode. This is now fixed. <

Click tool bar: Correctly shows event selection options even if no species classification options are in place.

-

Fixed Landmarks: Earlier versions were losing -these every time PAMGuard started or new data were loaded in viewer mode. Now -fixed.

+

Fixed Landmarks: Earlier versions were +losing these every time PAMGuard started or new data were loaded in viewer +mode. Now fixed.

ROCCA: Fixed (another) memory leak which caused PAMGuard to crash when processing large data sets with the ROCCA @@ -767,8 +803,9 @@ the TF FX display to crash if no data were displayed.

See major release notes for V 2.02.01 below.

-

Bug 495: TD FX display throws NullPointerException -if user has removed all data units and then moves mouse over display area.

+

Bug 495: TD FX display throws +NullPointerException if user has removed all data units and then moves mouse +over display area.

Version 2.02.01 October 2021

@@ -791,11 +828,11 @@ font-family:"Times New Roman",serif'> 

File Format Change

-

Changes have been made to the binary file format to support the -output of additional noise outputs for certain detectors (See below). Binary -files created with this version will not be compatible with earlier versions -2.01.### and below. This version will read and may convert earlier format -binary files.

+

Changes have been made to the binary file format to support +the output of additional noise outputs for certain detectors (See below). +Binary files created with this version will not be compatible with earlier +versions 2.01.### and below. This version will read and may convert earlier +format binary files.

 

@@ -1181,10 +1218,10 @@ lang=EN-US> Add functionality for bluetooth headsets.

2.        Add user-facing option to adjust the startup delay for the -time-correction (Global Time module). This provides a workaround to speed up -analysis of thousands of wav files (i.e. by setting startup delay to 0 instead -of default value of 2000 ms).

+lang=EN-US> Add user-facing option to adjust the startup delay for the time-correction +(Global Time module). This provides a workaround to speed up analysis of +thousands of wav files (i.e. by setting startup delay to 0 instead of default +value of 2000 ms).

3.       

2.        -Java 12 is better at handling Windows scaling issues on high-DPI displays. -Beyond that, users should not notice much of a difference between this version -and previous beta releases.

+Java 12 is better at handling Windows scaling issues on high-DPI displays. Beyond +that, users should not notice much of a difference between this version and +previous beta releases.

@@ -1706,8 +1743,8 @@ with installation and use of this version.

1.        -Bug 338. Problem displaying coastlines and bathymetric contours around the -dateline (+/- 180 longitude) in the map.

+Bug 338. Problem displaying coastlines and bathymetric contours around +the dateline (+/- 180 longitude) in the map.

2.        @@ -2218,8 +2255,8 @@ UID

If old data are opened with the PAMGuard viewer they will automatically be converted. For safety, the original binary files will not be overwritten and the new data will be placed in a new folder -on your computer with the same path as the old data, but suffixed with _WithUID, -e.g. if your binary data were previously stored in the folder +on your computer with the same path as the old data, but suffixed with +_WithUID, e.g. if your binary data were previously stored in the folder C:\MySurvey\binarydata the new data will be written to C:\MySurvey\binarydata_WithUID.

@@ -2295,8 +2332,8 @@ Hawaii/Temperate Pacific/North Atlantic datasets. This has been corrected.

2.       -Bug 320. Pamguard stopped reading Click Detector Event data from database -when target motion analysis information was encountered. Corrected.

+Bug 320. Pamguard stopped reading Click Detector Event data from +database when target motion analysis information was encountered. Corrected.

3.       @@ -2578,9 +2615,9 @@ main click detector display.

Target Motion Analysis

-

A major piece of work has been undertaken -to improve the Target Motion tracking with PAMGuard. Details are available in -the online help. Users of the Click Detector will notice the following changes:

+

A major piece of work has been undertaken to +improve the Target Motion tracking with PAMGuard. Details are available in the +online help. Users of the Click Detector will notice the following changes:

1.       @@ -2752,8 +2789,8 @@ being imported into the new database. Problem 2 was that indexing of imported click events in the new database was incorrect. Both these issues have now been fixed.

-

Version 1.15.02 March -2016

+

Version 1.15.02 +March 2016

A number of small bug fixes following release of 1.15.00.

@@ -2905,13 +2942,13 @@ for details.

3.       -GPS Loading into PAMGuard Viewer. This has been modified -so that the rules governing GPS data collection and storage also apply when -loading data from the database. For instance, if you've stored all GPS data, -you've probably got a record every second in the database which can create -memory overflows if you try to load a lot of data in the viewer. You can now -tell PAMGuard to only load a data point every n seconds which will reduce the -number of points loaded. Useful when making large scale overview maps of a +GPS Loading into PAMGuard Viewer. This has been +modified so that the rules governing GPS data collection and storage also apply +when loading data from the database. For instance, if you've stored all GPS +data, you've probably got a record every second in the database which can +create memory overflows if you try to load a lot of data in the viewer. You can +now tell PAMGuard to only load a data point every n seconds which will reduce +the number of points loaded. Useful when making large scale overview maps of a survey.

4.9.        -Bug 239. Fixed bug in the DIFAR module that was -incorrectly preventing cross-fixes for some calls.

+Bug 239. Fixed bug in the DIFAR module that was incorrectly +preventing cross-fixes for some calls.

Details of these bugs can be found at https://sourceforge.net/p/pamguard/bugs

@@ -3210,8 +3247,9 @@ to read on a time. Fixed

Menu Layout

-

The PAMGuard menus have been rearranged into a more intuitive -grouping which we believe will help users find functionality more easily.

+

The PAMGuard menus have been rearranged into a more +intuitive grouping which we believe will help users find functionality more +easily.

'Detection' menu has been renamed to 'Settings' since many menu items within this menu were not directly to do with 'Detection'.

@@ -3302,9 +3340,9 @@ of third octave noise bands. See online help for details.

Filtered Noise Measurement (Sound Processing Group)

-

This module, developed by Douglas Gillespie, measures noise levels -in a single frequency band using a variety of filter functions. See online help -for details.

+

This module, developed by Douglas Gillespie, measures noise +levels in a single frequency band using a variety of filter functions. See +online help for details.

Envelope Tracing (Beta Only, Sound Processing Group)

@@ -3340,10 +3378,11 @@ different. Details are available in the online help.

FLAC File Support

Can now read raw audio data direct from FLAC files. FLAC is a lossless compression algorithm -for audio data. Files, or folders of files are accessed in the same way as WAV -and AIFF files in the Sound Acquisition module. In a future release we also -hope to provide support for writing FLAC files from the sound recorder module.

+href="http://en.wikipedia.org/wiki/FLAC">FLAC is a lossless compression +algorithm for audio data. Files, or folders of files are accessed in the same +way as WAV and AIFF files in the Sound Acquisition module. In a future release +we also hope to provide support for writing FLAC files from the sound recorder +module.

Sound Recorder Module

@@ -3476,9 +3515,9 @@ to these menus to provide additional information to users.  

Radar Display

-

Functionality has been added to -the radar display so that bearings can be shown relative to either the vessel -or to true North.

+

Functionality has been added to the +radar display so that bearings can be shown relative to either the vessel or to +true North.

Better control of data in viewer mode, making is easy to scroll through and view data for short time periods.

@@ -4116,8 +4155,8 @@ Symbol'>''         -This results in a major speed up of data exchange between modules and can -lead to a x4 improvement in overall performance.

+
This results in a major speed up of data exchange between modules and +can lead to a x4 improvement in overall performance.

'         @@ -4230,9 +4269,9 @@ Symbol'>''         -Channel lists in output data streams of Decimator and other modules fixed, -so that when channel numbers change, downstream modules configurations get the -correct list of available channels.

+Channel lists in output data streams of Decimator and other modules +fixed, so that when channel numbers change, downstream modules configurations +get the correct list of available channels.

'         @@ -4263,11 +4302,11 @@ Symbol'>''         -New menu functionality by right clicking on any of the tabs of the main -tab control will allow the user to copy the tab contents to the system -clipboard from where it can be copied into other programs (e.g. Word, -Powerpoint, etc.).Some modules, such as the map, have this implemented in other -menus (right click) and also allow printing. 

+New menu functionality by right clicking on any of the tabs of the main tab +control will allow the user to copy the tab contents to the system clipboard +from where it can be copied into other programs (e.g. Word, Powerpoint, +etc.).Some modules, such as the map, have this implemented in other menus +(right click) and also allow printing. 

'         @@ -4335,8 +4374,8 @@ online help.

PAMGUARD Mixed Mode operation

-

Analyses data from wav or AIF file and synchronises it with -GPS data reloaded from a database so that detected sounds may be correctly +

Analyses data from wav or AIF file and synchronises it with GPS +data reloaded from a database so that detected sounds may be correctly localised. Multiple display frames - enables PAMGUARD GUI to be split into multiple display windows, displayed on multiple monitors if desired. Enables the operator to simultaneously view the map and the click detector for example, @@ -4855,8 +4894,8 @@ Symbol'>'1.0Beta 22 Jan 2008 - Pamguard -starts two releases, core and beta release,

+class=Heading2Char>1.0Beta 22 Jan 2008 - +Pamguard starts two releases, core and beta release,

this is the beta release

diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 73fb08dd..e135ce29 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -3,16 +3,17 @@ 4.0.0 org.pamguard Pamguard - Pamguard Java12+ - 2.02.10b - Pamguard for Java 12+, using Maven to control dependcies + Pamguard + 2.02.11d + Pamguard using Maven to control dependencies www.pamguard.org Sea Mammal Research Unit, University of St. Andrews http://www.smru.st-andrews.ac.uk - src + ${basedir}/src + ${basedir}/src/test src @@ -54,7 +55,7 @@ maven-compiler-plugin - 3.8.1 + 3.12.1 org.eclipse.tycho @@ -63,22 +64,14 @@ - 11 + 21 jdt - - .settings/org.eclipse.jdt.core.prefs - org.openjfx javafx-maven-plugin - 0.0.6 - - 17 - 17 - 17 - + 0.0.8 maven-shade-plugin @@ -165,11 +158,8 @@ https://artifacts.unidata.ucar.edu/repository/unidata-all/ - - false - bedatadriven - bedatadriven_renjin + bedatadriven public repo https://nexus.bedatadriven.com/content/groups/public/ @@ -198,7 +188,7 @@ 11 11 UTF-8 - 16 + 21 2.4.0-b180830.0438 2.4.0-b180830.0438 2.4.0-b180830.0359 diff --git a/nullPamguardSettings_20171106_185953.psfx b/nullPamguardSettings_20171106_185953.psfx new file mode 100644 index 00000000..b2e2bed1 Binary files /dev/null and b/nullPamguardSettings_20171106_185953.psfx differ diff --git a/nullPamguardSettings_20240401_141954.psfx b/nullPamguardSettings_20240401_141954.psfx new file mode 100644 index 00000000..4896ca9b Binary files /dev/null and b/nullPamguardSettings_20240401_141954.psfx differ diff --git a/nullPamguardSettings_20240401_143317.psfx b/nullPamguardSettings_20240401_143317.psfx new file mode 100644 index 00000000..a77426c4 Binary files /dev/null and b/nullPamguardSettings_20240401_143317.psfx differ diff --git a/pom.xml b/pom.xml index bbb2fbe6..dd3572a4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,12 @@ - 4.0.0 org.pamguard Pamguard - 2.02.10ad - Pamguard Java12+ - Pamguard for Java 12+, using Maven to control dependcies + 2.02.11d + Pamguard + Pamguard using Maven to control dependencies www.pamguard.org Sea Mammal Research Unit, University of St. Andrews @@ -15,80 +14,73 @@ - - 16 - 11 - 11 - UTF-8 - 2.4.0-b180830.0438 + + UTF-8 + 21 + 11 + 11 + + 2.4.0-b180830.0438 2.4.0-b180830.0359 2.4.0-b180830.0438 - - - - src - - - src - - META-INF/*.SF,META-INF/*.DSA,META-INF/*.RSA - **/*.java - jars/*.* - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - - - 11 - jdt - - .settings/org.eclipse.jdt.core.prefs - - - - - - - org.eclipse.tycho - tycho-compiler-jdt - 1.5.1 - - - - - - - org.openjfx - javafx-maven-plugin - 0.0.6 - - 17 - 17 - 17 - - + + + + ${basedir}/src + ${basedir}/src/test + + + src + + META-INF/*.SF,META-INF/*.DSA,META-INF/*.RSA + **/*.java + jars/*.* + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + + 21 + jdt + + + + + org.eclipse.tycho + tycho-compiler-jdt + 1.5.1 + + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + @@ -135,114 +127,114 @@ - + + - - com.github.marschall - jdeps-maven-plugin - 0.5.1 - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.1 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/tempDependencies - false - false - true - - - - - - - - - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - - org.apache.maven.plugins - - - maven-dependency-plugin - - - [3.1.1,) - - - - purge-local-repository - - - - - - - - - - - - - - - - - - - com.github.marschall - jdeps-maven-plugin - 0.5.1 - - - - - - - - + this site for details: https://maven.apache.org/plugins/maven-jdeps-plugin/index.html --> + + com.github.marschall + jdeps-maven-plugin + 0.5.1 + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/tempDependencies + false + false + true + + + + + + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.apache.maven.plugins + + + maven-dependency-plugin + + + [3.1.1,) + + + + purge-local-repository + + + + + + + + + + + + + + + + + + + com.github.marschall + jdeps-maven-plugin + 0.5.1 + + + + + + + @@ -256,35 +248,18 @@ file://${project.basedir}/repo - - - - - - - - - - - - - - - - false - - unidata-all - Unidata netCDF - https://artifacts.unidata.ucar.edu/repository/unidata-all/ - - - + + + false + + unidata-all + Unidata netCDF + https://artifacts.unidata.ucar.edu/repository/unidata-all/ + + + - - - - - false - - bedatadriven - bedatadriven_renjin - https://nexus.bedatadriven.com/content/groups/public/ - - - - - false - - talan - talan - https://nexus.talanlabs.com/content/repositories/releases/ - - - - central - https://repo1.maven.org/maven2 - - - + + + + bedatadriven + bedatadriven public repo + https://nexus.bedatadriven.com/content/groups/public/ + + + + + + false + + talan + talan + https://nexus.talanlabs.com/content/repositories/releases/ + + + + central + https://repo1.maven.org/maven2 + + + - - + + + + + + io.github.macster110 + jpamutils + 0.0.59 + - - - io.github.macster110 - jpamutils - 0.0.56 - - - - - io.github.macster110 - jdl4pam - 0.0.94 - - - - - gov.nist.math - jama - 1.0.3 - - - - + + io.github.macster110 + jdl4pam + 0.0.99a + + + + + gov.nist.math + jama + 1.0.3 + + + + - - org.openjfx - javafx-controls - ${javafx.version} - + + org.openjfx + javafx-controls + ${javafx.version} + - - + - - org.openjfx - javafx-swing - ${javafx.version} - - - - + - - org.openjfx - javafx-media - ${javafx.version} - + + org.openjfx + javafx-media + ${javafx.version} + - - + - - org.openjfx - javafx-web - ${javafx.version} - + + org.openjfx + javafx-web + ${javafx.version} + + + + + net.synedra + validatorfx + 0.4.2 + + + + + org.apache.commons + commons-compress + 1.19 + - - - net.synedra - validatorfx - 0.4.0 - - - - - org.apache.commons - commons-compress - 1.19 - - - - - org.apache.commons - commons-csv - 1.7 - - - - - commons-io - commons-io - 2.6 - - - - - org.apache.commons - commons-lang3 - 3.9 - - - - - org.apache.commons - commons-math3 - 3.6.1 - - - - - org.apache.commons - commons-math - 2.2 - - - - - commons-net - commons-net - 3.6 - - - - - org.controlsfx - controlsfx - 11.0.0 - - - org.openjfx - javafx-base - - - org.openjfx - javafx-controls - - - org.openjfx - javafx-graphics - - - org.openjfx - javafx-media - - - org.openjfx - javafx-web - - - - - - + + org.apache.commons + commons-csv + 1.7 + + + + + commons-io + commons-io + 2.6 + + + + + org.apache.commons + commons-lang3 + 3.9 + + + + + org.apache.commons + commons-math3 + 3.6.1 + + + + + org.apache.commons + commons-math + 2.2 + + + + + commons-net + commons-net + 3.6 + + + + + org.controlsfx + controlsfx + 11.2.0 + + + org.openjfx + javafx-base + + + org.openjfx + javafx-controls + + + org.openjfx + javafx-graphics + + + org.openjfx + javafx-media + + + org.openjfx + javafx-web + + + + + + - - org.kordamp.ikonli - ikonli-javafx - 12.2.0 - - - - + + org.kordamp.ikonli + ikonli-swing + 12.3.1 + + + org.kordamp.ikonli - ikonli-fontawesome5-pack - 12.2.0 + ikonli-materialdesign2-pack + 12.3.1 + + + + + org.kordamp.ikonli + ikonli-fileicons-pack + 12.3.1 - --> - - - org.kordamp.ikonli - ikonli-materialdesign2-pack - 12.2.0 - + + + net.sf.geographiclib + GeographicLib-Java + 1.50 + + + + - - - net.sf.geographiclib - GeographicLib-Java - 1.50 - - - - - org.jogamp.gluegen - gluegen-rt-main - 2.3.2 - - - - - - - - - com.healthmarketscience.jackcess - jackcess - 3.0.1 - - - - - com.fasterxml.jackson.core - jackson-databind - 2.10.1 - - - - - org.jogamp.jogl - jogl-all-main - 2.3.2 - - - + + + + com.fasterxml.jackson.core + jackson-databind + 2.10.1 + + + + + - - - - org.jflac - jflac-codec - 1.5.2 - - - - - javax.help - javahelp - 2.0.05 - - - - - net.java.dev.jna - jna - 5.5.0 - - + + + + org.jflac + jflac-codec + 1.5.2 + + + + + javax.help + javahelp + 2.0.05 + + + + + net.java.dev.jna + jna + 5.5.0 + + - - net.java.dev.jna - jna-platform - 5.5.0 - + + net.java.dev.jna + jna-platform + 5.5.0 + + + + + com.jcraft + jsch + 0.1.55 + + + + + com.fazecast + jSerialComm + 2.5.3 + - - - com.jcraft - jsch - 0.1.55 - + + + edu.emory.mathcs + JTransforms + 2.4 + - - - com.fazecast - jSerialComm - 2.5.3 - + + + com.sun.mail + javax.mail + 1.6.2 + - - - edu.emory.mathcs - JTransforms - 2.4 - - - - com.sun.mail - javax.mail - 1.6.2 - + + + com.drewnoakes + metadata-extractor + 2.12.0 + - - - com.diffplug.matsim - matfilerw - 3.1.1 - + + + mysql + mysql-connector-java + 8.0.18 + - - - com.drewnoakes - metadata-extractor - 2.12.0 - - - - - mysql - mysql-connector-java - 8.0.18 - - - - - + + - + + com.google.protobuf + protobuf-java + 3.17.0 + + + + + + edu.ucar + netcdfAll + 4.6.14 + + com.google.protobuf protobuf-java - 3.17.0 - + + + com.google.protobuf + protobuf-java-util + + + compile + - - - - edu.ucar - netcdfAll - 4.6.14 - - - com.google.protobuf - protobuf-java - - - com.google.protobuf - protobuf-java-util - - - compile - + + + com.opencsv + opencsv + 5.0 + - - - com.opencsv - opencsv - 5.0 - + - + + + org.postgresql + postgresql + 42.2.24 + - - - org.postgresql - postgresql - 42.2.24 - - - - - org.renjin - renjin-script-engine - 0.9.2725 - - - + + org.renjin + renjin-script-engine + 0.9.2725 + + + - - org.slf4j - slf4j-api - 1.8.0-beta4 - - - - - - org.slf4j - slf4j-nop - 1.8.0-beta4 - + + org.slf4j + slf4j-nop + 1.8.0-beta4 + + + - - - + - - - org.docx4j - docx4j-JAXB-ReferenceImpl - 11.1.3 - - + - - - org.xerial - sqlite-jdbc - 3.28.0 - + + + org.xerial + sqlite-jdbc + 3.45.3.0 + - - - net.sf.ucanaccess - ucanaccess - 5.0.1 - + + + net.sf.ucanaccess + ucanaccess + 4.0.4 + - - - nz.ac.waikato.cms.weka - weka-dev - 3.7.7 - + + + nz.ac.waikato.cms.weka + weka-dev + 3.7.7 + - - - javax.vecmath - vecmath - 1.5.2 - + + + javax.vecmath + vecmath + 1.5.2 + - - - + + + + + org.eclipse.persistence + org.eclipse.persistence.moxy + 2.5.0 + + + javax.xml.bind + jaxb-api + ${jaxb.api.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb.runtime.version} + + + org.glassfish.jaxb + jaxb-xjc + ${jaxb.xjc.version} + + + + + + com.sun.jersey.contribs + jersey-multipart + 1.18.1 + + + + + commons-cli + commons-cli + 1.2 + + + + + org.apache.poi + poi + 3.10-beta1 + + + + + com.sun.jersey + jersey-client + 1.18.1 + + + + + com.sun.jersey.contribs + jersey-apache-client + 1.18.1 + + + + + com.miglayout + miglayout + 3.7.4 + + + + + ca.juliusdavies + not-yet-commons-ssl + 0.3.11 + + + + + javax.ws.rs + javax.ws.rs-api + 2.1.1 + + + + + javax.xml.bind + jaxb-api + 2.2.11 + + + + + com.sun.xml.bind + jaxb-impl + 2.2.11 + + + + + javax.activation + activation + 1.1 + + + + + org.glassfish.jaxb + jaxb-core + 2.2.11 + + + + + org.glassfish.jersey.core + jersey-common + 2.2 + + + + + org.apache.commons + commons-text + 1.9 + + + + + + + + tethys.org + nilus + 3.0 + - - - org.eclipse.persistence - org.eclipse.persistence.moxy - 2.5.0 - - - javax.xml.bind - jaxb-api - ${jaxb.api.version} - - - org.glassfish.jaxb - jaxb-runtime - ${jaxb.runtime.version} - - - org.glassfish.jaxb - jaxb-xjc - ${jaxb.xjc.version} - - - - com.sun.jersey.contribs - jersey-multipart - 1.18.1 - - - commons-cli - commons-cli - 1.2 - - - org.apache.poi - poi - 3.10-beta1 - - - com.sun.jersey - jersey-client - 1.18.1 - - - com.sun.jersey.contribs - jersey-apache-client - 1.18.1 - - - com.miglayout - miglayout - 3.7.4 - - - ca.juliusdavies - not-yet-commons-ssl - 0.3.11 - - - javax.ws.rs - javax.ws.rs-api - 2.1.1 - - - javax.xml.bind - jaxb-api - 2.2.11 - - - com.sun.xml.bind - jaxb-impl - 2.2.11 - - - javax.activation - activation - 1.1 - - - org.glassfish.jaxb - jaxb-core - 2.2.11 - - - org.glassfish.jersey.core - jersey-common - 2.2 - - - org.apache.commons - commons-text - 1.9 - + + tethys.org + javaclient + 3.0 + - - + - pamguard.org + org.pamguard x3 2.2.7 - + + + + it.sauronsoftware + jave + 1.0.2 + + + + + com.synthbot + jasiohost + 1.0.0 + - - tethys.org - nilus - 3.0 - - - - tethys.org - javaclient - 3.0 - - - - - - it.sauronsoftware - jave - 1.0.2 - - - - - com.synthbot - jasiohost - 1.0.0 - - - - + + + + + org.springframework + spring-core + 5.2.3.RELEASE + + + + + + com.1stleg + jnativehook + 2.1.0 + + + + + + org.swinglabs.swingx + swingx-all + 1.6.5-1 + - - - org.springframework - spring-core - 5.2.3.RELEASE - + + + + org.fxyz3d + fxyz3d + 0.6.0 + + + + + io.github.mkpaz + atlantafx-base + 2.0.1 + - - - - com.1stleg - jnativehook - 2.1.0 - - - - - - org.swinglabs.swingx - swingx-all - 1.6.5-1 - - - - - io.github.mkpaz - atlantafx-base - 1.0.0 - - - - +
\ No newline at end of file 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/repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom b/repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom index 609bc1d5..8d48a0d5 100644 --- a/repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom +++ b/repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom @@ -1,9 +1,15 @@ - 4.0.0 +<<<<<<<< HEAD:repo/tethys/org/javaclient/3.0/javaclient-3.0.pom + tethys.org + javaclient + 3.0 +======== pamguard.org x3 2.2.6 +>>>>>>>> upstream/main:repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom POM was created from install:install-file diff --git a/repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom b/repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom index 7eeede30..dc2f63cc 100644 --- a/repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom +++ b/repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom @@ -2,8 +2,14 @@ 4.0.0 +<<<<<<<< HEAD:repo/tethys/org/nilus/3.0/nilus-3.0.pom + tethys.org + nilus + 3.0 +======== pamguard.org x3 2.2.7 +>>>>>>>> upstream/main:repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom POM was created from install:install-file diff --git a/repo/tethys/org/javaclient/3.0/javaclient-3.0-javadoc.jar.lastUpdated b/repo/tethys/org/javaclient/3.0/javaclient-3.0-javadoc.jar.lastUpdated new file mode 100644 index 00000000..17ccf4ee --- /dev/null +++ b/repo/tethys/org/javaclient/3.0/javaclient-3.0-javadoc.jar.lastUpdated @@ -0,0 +1,16 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Thu Mar 07 12:09:00 GMT 2024 +@default-talan-https\://nexus.talanlabs.com/content/repositories/releases/.lastUpdated=1705320262317 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.lastUpdated=1705320259110 +https\://repo1.maven.org/maven2/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.error= +https\://artifacts.unidata.ucar.edu/repository/unidata-all/.error= +https\://nexus.bedatadriven.com/content/groups/public/.error= +https\://artifacts.unidata.ucar.edu/repository/unidata-all/.lastUpdated=1705320260572 +https\://repo1.maven.org/maven2/.lastUpdated=1705320262598 +https\://nexus.talanlabs.com/content/repositories/releases/.error=Could not transfer artifact tethys.org\:javaclient\:jar\:javadoc\:3.0 from/to talan (https\://nexus.talanlabs.com/content/repositories/releases/)\: nexus.talanlabs.com +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.lastUpdated=1705569305037 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.error= +https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1705320262257 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1709813340827 diff --git a/repo/tethys/org/javaclient/3.0/javaclient-3.0-sources.jar.lastUpdated b/repo/tethys/org/javaclient/3.0/javaclient-3.0-sources.jar.lastUpdated new file mode 100644 index 00000000..6544bcc1 --- /dev/null +++ b/repo/tethys/org/javaclient/3.0/javaclient-3.0-sources.jar.lastUpdated @@ -0,0 +1,16 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Thu Mar 07 12:08:16 GMT 2024 +@default-talan-https\://nexus.talanlabs.com/content/repositories/releases/.lastUpdated=1703957320119 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.error= +https\://repo1.maven.org/maven2/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.lastUpdated=1705074190844 +https\://artifacts.unidata.ucar.edu/repository/unidata-all/.error= +https\://nexus.bedatadriven.com/content/groups/public/.error= +https\://artifacts.unidata.ucar.edu/repository/unidata-all/.lastUpdated=1703957319870 +https\://repo1.maven.org/maven2/.lastUpdated=1703957320395 +https\://nexus.talanlabs.com/content/repositories/releases/.error=Could not transfer artifact tethys.org\:javaclient\:jar\:sources\:3.0 from/to talan (https\://nexus.talanlabs.com/content/repositories/releases/)\: nexus.talanlabs.com +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.lastUpdated=1703957318729 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.error= +https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1703957320102 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1709813296623 diff --git a/repo/tethys/org/javaclient/3.0/javaclient-3.0.pom b/repo/tethys/org/javaclient/3.0/javaclient-3.0.pom index 80fee21c..8d48a0d5 100644 --- a/repo/tethys/org/javaclient/3.0/javaclient-3.0.pom +++ b/repo/tethys/org/javaclient/3.0/javaclient-3.0.pom @@ -2,8 +2,14 @@ 4.0.0 +<<<<<<<< HEAD:repo/tethys/org/javaclient/3.0/javaclient-3.0.pom tethys.org javaclient 3.0 +======== + pamguard.org + x3 + 2.2.6 +>>>>>>>> upstream/main:repo/pamguard/org/x3/2.2.6/x3-2.2.6.pom POM was created from install:install-file diff --git a/repo/tethys/org/javaclient/3.0/m2e-lastUpdated.properties b/repo/tethys/org/javaclient/3.0/m2e-lastUpdated.properties new file mode 100644 index 00000000..db908e36 --- /dev/null +++ b/repo/tethys/org/javaclient/3.0/m2e-lastUpdated.properties @@ -0,0 +1,15 @@ +#Thu Mar 07 12:09:00 GMT 2024 +bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|javadoc=1709813340828 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo|javadoc=1705320262599 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo|javadoc=1709813340828 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo|javadoc=1705569305040 +talan|https\://nexus.talanlabs.com/content/repositories/releases/|sources=1709813296626 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo|sources=1709813296626 +bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|sources=1709813296626 +unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|sources=1709813296626 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo|sources=1703957320396 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo|sources=1705074190848 +talan|https\://nexus.talanlabs.com/content/repositories/releases/|javadoc=1709813340828 +central|https\://repo1.maven.org/maven2|sources=1709813296626 +unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|javadoc=1709813340828 +central|https\://repo1.maven.org/maven2|javadoc=1709813340828 diff --git a/repo/tethys/org/nilus/3.0/_remote.repositories b/repo/tethys/org/nilus/3.0/_remote.repositories index a5060b8b..de3047e4 100644 --- a/repo/tethys/org/nilus/3.0/_remote.repositories +++ b/repo/tethys/org/nilus/3.0/_remote.repositories @@ -1,4 +1,4 @@ #NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. -#Thu Dec 21 11:14:13 GMT 2023 +#Thu Mar 07 11:55:57 GMT 2024 nilus-3.0.pom>= nilus-3.0.jar>= diff --git a/repo/tethys/org/nilus/3.0/m2e-lastUpdated.properties b/repo/tethys/org/nilus/3.0/m2e-lastUpdated.properties index 8382f138..5e4193d5 100644 --- a/repo/tethys/org/nilus/3.0/m2e-lastUpdated.properties +++ b/repo/tethys/org/nilus/3.0/m2e-lastUpdated.properties @@ -1,11 +1,15 @@ -#Thu Dec 21 16:45:12 GMT 2023 -bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|javadoc=1703177112968 -talan|https\://nexus.talanlabs.com/content/repositories/releases/|javadoc=1703177112968 +#Thu Jan 18 09:15:05 GMT 2024 +bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|javadoc=1705569305022 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo|javadoc=1705320259098 repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo|javadoc=1703177112968 -central|https\://repo1.maven.org/maven2|sources=1703157324238 -unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|javadoc=1703177112968 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo|javadoc=1705569305022 repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo|sources=1703157324238 -talan|https\://nexus.talanlabs.com/content/repositories/releases/|sources=1703157324238 -bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|sources=1703157324238 -unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|sources=1703157324238 -central|https\://repo1.maven.org/maven2|javadoc=1703177112968 +talan|https\://nexus.talanlabs.com/content/repositories/releases/|sources=1705074190830 +bedatadriven|https\://nexus.bedatadriven.com/content/groups/public/|sources=1705074190830 +unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|sources=1705074190830 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo|sources=1703957318711 +repo|file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo|sources=1705074190830 +talan|https\://nexus.talanlabs.com/content/repositories/releases/|javadoc=1705569305022 +central|https\://repo1.maven.org/maven2|sources=1705074190830 +unidata-all|https\://artifacts.unidata.ucar.edu/repository/unidata-all/|javadoc=1705569305022 +central|https\://repo1.maven.org/maven2|javadoc=1705569305022 diff --git a/repo/tethys/org/nilus/3.0/nilus-3.0-javadoc.jar.lastUpdated b/repo/tethys/org/nilus/3.0/nilus-3.0-javadoc.jar.lastUpdated index 34e39dc5..1faf7573 100644 --- a/repo/tethys/org/nilus/3.0/nilus-3.0-javadoc.jar.lastUpdated +++ b/repo/tethys/org/nilus/3.0/nilus-3.0-javadoc.jar.lastUpdated @@ -1,12 +1,16 @@ #NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. -#Thu Dec 21 16:45:12 GMT 2023 +#Thu Jan 18 09:15:05 GMT 2024 @default-talan-https\://nexus.talanlabs.com/content/repositories/releases/.lastUpdated=1703177112601 https\://repo1.maven.org/maven2/.error= -file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= -file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1703177110940 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.lastUpdated=1705320259096 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.error= https\://artifacts.unidata.ucar.edu/repository/unidata-all/.error= https\://nexus.bedatadriven.com/content/groups/public/.error= -https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1703177112519 https\://artifacts.unidata.ucar.edu/repository/unidata-all/.lastUpdated=1703177112101 https\://repo1.maven.org/maven2/.lastUpdated=1703177112965 https\://nexus.talanlabs.com/content/repositories/releases/.error=Could not transfer artifact tethys.org\:nilus\:jar\:javadoc\:3.0 from/to talan (https\://nexus.talanlabs.com/content/repositories/releases/)\: nexus.talanlabs.com +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.lastUpdated=1705569305018 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1703177110940 +https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1703177112519 diff --git a/repo/tethys/org/nilus/3.0/nilus-3.0-sources.jar.lastUpdated b/repo/tethys/org/nilus/3.0/nilus-3.0-sources.jar.lastUpdated index 8ceb547f..6dbbc1bb 100644 --- a/repo/tethys/org/nilus/3.0/nilus-3.0-sources.jar.lastUpdated +++ b/repo/tethys/org/nilus/3.0/nilus-3.0-sources.jar.lastUpdated @@ -1,12 +1,16 @@ #NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. -#Thu Dec 21 11:15:24 GMT 2023 +#Fri Jan 12 15:43:10 GMT 2024 @default-talan-https\://nexus.talanlabs.com/content/repositories/releases/.lastUpdated=1703157323819 https\://repo1.maven.org/maven2/.error= -file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= -file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1703157322932 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.lastUpdated=1705074190824 https\://artifacts.unidata.ucar.edu/repository/unidata-all/.error= https\://nexus.bedatadriven.com/content/groups/public/.error= -https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1703157323770 https\://artifacts.unidata.ucar.edu/repository/unidata-all/.lastUpdated=1703157323508 https\://repo1.maven.org/maven2/.lastUpdated=1703157324237 https\://nexus.talanlabs.com/content/repositories/releases/.error=Could not transfer artifact tethys.org\:nilus\:jar\:sources\:3.0 from/to talan (https\://nexus.talanlabs.com/content/repositories/releases/)\: nexus.talanlabs.com +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG/repo/.lastUpdated=1703957318705 +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardPAMGuard/repo/.error= +file\://C\:\\Users\\dg50\\source\\repos\\PAMGuardDG_2/repo/.lastUpdated=1703157322932 +https\://nexus.bedatadriven.com/content/groups/public/.lastUpdated=1703157323770 diff --git a/repo/tethys/org/nilus/3.0/nilus-3.0.jar b/repo/tethys/org/nilus/3.0/nilus-3.0.jar index 195d268e..c00ba775 100644 Binary files a/repo/tethys/org/nilus/3.0/nilus-3.0.jar and b/repo/tethys/org/nilus/3.0/nilus-3.0.jar differ diff --git a/repo/tethys/org/nilus/3.0/nilus-3.0.pom b/repo/tethys/org/nilus/3.0/nilus-3.0.pom index ba47dee9..dc2f63cc 100644 --- a/repo/tethys/org/nilus/3.0/nilus-3.0.pom +++ b/repo/tethys/org/nilus/3.0/nilus-3.0.pom @@ -2,8 +2,14 @@ 4.0.0 +<<<<<<<< HEAD:repo/tethys/org/nilus/3.0/nilus-3.0.pom tethys.org nilus 3.0 +======== + pamguard.org + x3 + 2.2.7 +>>>>>>>> upstream/main:repo/pamguard/org/x3/2.2.7/x3-2.2.7.pom POM was created from install:install-file diff --git a/repo/tethys/org/nilus/maven-metadata-local.xml b/repo/tethys/org/nilus/maven-metadata-local.xml index d053c39d..0aa256c5 100644 --- a/repo/tethys/org/nilus/maven-metadata-local.xml +++ b/repo/tethys/org/nilus/maven-metadata-local.xml @@ -7,6 +7,6 @@ 3.0 - 20231221111413 + 20240307115557 diff --git a/src/Acquisition/FileInputSystem.java b/src/Acquisition/FileInputSystem.java index 2727ca05..33779534 100644 --- a/src/Acquisition/FileInputSystem.java +++ b/src/Acquisition/FileInputSystem.java @@ -429,6 +429,8 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe // acquisitionDialog.NotifyChange(); if (file.isFile() && !file.isHidden() && acquisitionDialog != null) { try { + + System.out.println("FileInputSystem - interpretNewFile"); AudioInputStream audioStream = PamAudioFileManager.getInstance().getAudioInputStream(file); // // Get additional information from the header if it's a wav file. @@ -600,6 +602,8 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe audioStream.close(); } + System.out.println("FileInputSystem - prepareInputFile"); + audioStream = PamAudioFileManager.getInstance().getAudioInputStream(currentFile); if (audioStream instanceof SudAudioInputStream) { @@ -810,6 +814,10 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe protected void collectFlacData() { FileInputStream fileStream; try { + File currFile = getCurrentFile(); + if (currFile == null) { + return; + } fileStream = new FileInputStream(getCurrentFile()); } catch (FileNotFoundException e) { e.printStackTrace(); diff --git a/src/Acquisition/FolderInputSystem.java b/src/Acquisition/FolderInputSystem.java index 7b20311e..c8c2d872 100644 --- a/src/Acquisition/FolderInputSystem.java +++ b/src/Acquisition/FolderInputSystem.java @@ -602,6 +602,7 @@ public class FolderInputSystem extends FileInputSystem implements PamSettings, D @Override public File getCurrentFile() { //System.out.println("All files: " + allFiles); +// System.out.printf("Folder: getCurrentfile. on %d of %d\n", currentFile, allFiles.size()); if (allFiles != null && allFiles.size() > currentFile) { return allFiles.get(currentFile); } @@ -689,7 +690,7 @@ public class FolderInputSystem extends FileInputSystem implements PamSettings, D } if (currentFile < allFiles.size()) { // only restart if the file ended - not if it stopped - if (getStreamStatus() == STREAM_ENDED) { + if (getStreamStatus() == STREAM_ENDED && PamController.getInstance().isManualStop() == false) { // System.out.println(String.format("Start new file timer (file %d/%d)",currentFile+1,allFiles.size())); newFileTimer.start(); } diff --git a/src/Acquisition/filedate/FileDateDialogStrip.java b/src/Acquisition/filedate/FileDateDialogStrip.java index 836850e6..c695f6de 100644 --- a/src/Acquisition/filedate/FileDateDialogStrip.java +++ b/src/Acquisition/filedate/FileDateDialogStrip.java @@ -1,6 +1,7 @@ package Acquisition.filedate; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -15,7 +16,11 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.swing.FontIcon; + import PamUtils.PamCalendar; +import PamView.component.PamSettingsIconButton; import PamView.dialog.PamGridBagContraints; /** @@ -35,7 +40,9 @@ public class FileDateDialogStrip { private JButton settingsButton; - private ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// private ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); + public static FontIcon settingsIcon = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); + private Window parent; diff --git a/src/Acquisition/filedate/StandardFileDate.java b/src/Acquisition/filedate/StandardFileDate.java index 53033f60..54f6d998 100644 --- a/src/Acquisition/filedate/StandardFileDate.java +++ b/src/Acquisition/filedate/StandardFileDate.java @@ -135,6 +135,8 @@ public class StandardFileDate implements FileDate, PamSettings { @Override public long getTimeFromFile(File file) { +// System.out.println("Get time from file: getTimeFromFile" ); + // if the user wants to force the local PC time, return immediately if (settings.isForcePCTime()) return 0; diff --git a/src/Acquisition/layoutFX/AcquisitionPaneFX.java b/src/Acquisition/layoutFX/AcquisitionPaneFX.java index 7f866f7c..59400599 100644 --- a/src/Acquisition/layoutFX/AcquisitionPaneFX.java +++ b/src/Acquisition/layoutFX/AcquisitionPaneFX.java @@ -290,7 +290,7 @@ public class AcquisitionPaneFX extends SettingsPane{ //custom pane for each aquisition system. systemPane=new PamBorderPane(); - offlineDAQPaneFX= new OfflineDAQPane(acquisitionControl, this); + offlineDAQPaneFX= new OfflineDAQPane(acquisitionControl); //the main pane is for reference only in viewer mode. Pane samplingPane=createSamplingPane(); diff --git a/src/Acquisition/layoutFX/CheckWavHeadersPane.java b/src/Acquisition/layoutFX/CheckWavHeadersPane.java index c97cd6d4..4f849e86 100644 --- a/src/Acquisition/layoutFX/CheckWavHeadersPane.java +++ b/src/Acquisition/layoutFX/CheckWavHeadersPane.java @@ -147,7 +147,12 @@ public class CheckWavHeadersPane extends PamBorderPane { else { folderName.setText(folderInputSystem.getCurrentFolder()); } - folder = new File(folderInputSystem.getCurrentFolder()); + + if (folderInputSystem.getCurrentFolder()!=null){ + folder = new File(folderInputSystem.getCurrentFolder()); + } + else folder = null; + textArea.setText(" "); allFiles.clear(); nFiles = countFiles(folder); @@ -159,6 +164,7 @@ public class CheckWavHeadersPane extends PamBorderPane { private int countFiles(File folder) { + if (folder == null) return 0; int nF = 0; File[] files = folder.listFiles(new PamAudioFileFilter()); if (files == null) return 0; diff --git a/src/Acquisition/layoutFX/FileDataDialogStripFX.java b/src/Acquisition/layoutFX/FileDataDialogStripFX.java index e4fae902..7658f7da 100644 --- a/src/Acquisition/layoutFX/FileDataDialogStripFX.java +++ b/src/Acquisition/layoutFX/FileDataDialogStripFX.java @@ -215,7 +215,7 @@ public class FileDataDialogStripFX extends PamBorderPane { popOver.show(advSettingsButton); ((Parent) popOver.getSkin().getNode()).getStylesheets() - .add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + .addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); advDatePane.setParams(); } diff --git a/src/Acquisition/layoutFX/FolderInputPane.java b/src/Acquisition/layoutFX/FolderInputPane.java index d815fd06..cf0b0e3e 100644 --- a/src/Acquisition/layoutFX/FolderInputPane.java +++ b/src/Acquisition/layoutFX/FolderInputPane.java @@ -193,20 +193,18 @@ public class FolderInputPane extends DAQSettingsPane{ //TODO }); - browseFileButton.setGraphic(Glyph.create("FontAwesome|FILES_ALT"). - size(PamGuiManagerFX.iconSize).color(Color.WHITE.darker())); + browseFileButton.setGraphic(PamGlyphDude.createPamIcon("mdi2f-file-multiple", PamGuiManagerFX.iconSize)); browseFileButton.prefHeightProperty().bind(fileSelectBox.heightProperty()); //make browse button same height as combo box. - browseFileButton.setMinWidth(35); + browseFileButton.setMinWidth(40); browseFileButton.setOnAction( (action) ->{ selectFolder(false); }); browseFileButton.setTooltip(new Tooltip("Select a folder of files")); - browseFolderButton.setGraphic(Glyph.create("FontAwesome|FOLDER"). - size(PamGuiManagerFX.iconSize).color(Color.WHITE.darker())); + browseFolderButton.setGraphic(PamGlyphDude.createPamIcon("mdi2f-folder", PamGuiManagerFX.iconSize)); browseFolderButton.prefHeightProperty().bind(fileSelectBox.heightProperty()); //make browse button same height as combo box. - browseFolderButton.setMinWidth(35); + browseFolderButton.setMinWidth(40); browseFolderButton.setOnAction( (action) ->{ selectFolder(true); }); diff --git a/src/Acquisition/layoutFX/OfflineDAQPane.java b/src/Acquisition/layoutFX/OfflineDAQPane.java index f5ff7726..56cb2003 100644 --- a/src/Acquisition/layoutFX/OfflineDAQPane.java +++ b/src/Acquisition/layoutFX/OfflineDAQPane.java @@ -30,8 +30,7 @@ public class OfflineDAQPane extends SettingsPane{ private PamBorderPane mainPane; - public OfflineDAQPane(OfflineFileDataStore acquisitionControl, - AcquisitionPaneFX acquisitionPaneFX){ + public OfflineDAQPane(OfflineFileDataStore acquisitionControl){ super(null); this.mainPane= new PamBorderPane(); mainPane.setCenter(createOfflinePane()); diff --git a/src/Acquisition/pamAudio/PamAudioFileManager.java b/src/Acquisition/pamAudio/PamAudioFileManager.java index 76fc6910..bf13f904 100644 --- a/src/Acquisition/pamAudio/PamAudioFileManager.java +++ b/src/Acquisition/pamAudio/PamAudioFileManager.java @@ -135,7 +135,7 @@ public class PamAudioFileManager { } if (stream == null) { - System.err.println("PamAudioFileManager: unable to open an AudioStream for " + file.getName()); + System.err.println("PamAudioFileManager: unable to open an AudioStream for " + file.getName() + " size: " + file.length()); } return stream; @@ -153,7 +153,7 @@ public class PamAudioFileManager { } /** - * Get the audio file filter + * Get the audio file filter. * * @return the audio file filter. */ @@ -164,7 +164,7 @@ public class PamAudioFileManager { /** * Get the current audio file * - * @return a list oif the current audio loaders. + * @return a list of the current audio loaders. */ public ArrayList getAudioFileLoaders() { return this.pamAudioFileTypes; diff --git a/src/Acquisition/pamAudio/WavAudioFile.java b/src/Acquisition/pamAudio/WavAudioFile.java index 77d07b54..e8789da0 100644 --- a/src/Acquisition/pamAudio/WavAudioFile.java +++ b/src/Acquisition/pamAudio/WavAudioFile.java @@ -254,7 +254,7 @@ public class WavAudioFile implements PamAudioFileLoader { @Override public AudioInputStream getAudioStream(File soundFile) { - if (soundFile.exists() == false) return null; + if (soundFile.exists() == false || soundFile.length()<44) return null; if (soundFile != null && isSoundFile(soundFile)) { try { return WavFileInputStream.openInputStream(soundFile); @@ -262,7 +262,7 @@ public class WavAudioFile implements PamAudioFileLoader { catch (UnsupportedAudioFileException | IOException e) { e.printStackTrace(); // don't do anything and it will try the built in Audiosystem - System.err.println("Could not open wav file: trying default audio stream: " + soundFile.getName()); + System.err.println("Could not open wav file: trying default audio stream: " + soundFile.getName() + " " + soundFile.length()); } } try { @@ -276,9 +276,24 @@ public class WavAudioFile implements PamAudioFileLoader { } - public boolean isSoundFile(File soundFile) { + public static boolean isSoundFile(File soundFile) { String extension = FileUtils.getExtension(soundFile.getName()); - return (extension.equals(".wav")); + //2023-03-12 - for some reason this was .wav + return (extension.equals("wav")); + } + + + public static void main(String args[]) { + + File wavFile = new File("E:\\SoundNet\\1chan_analysis\\pamguard\\67150826\\mf_wav\\20180529\\PAM_20180529_055114_000.wav"); + try { + WavFileInputStream.openInputStream(wavFile); + System.out.println("Wav file opened successfully: " + isSoundFile(wavFile)); + + } catch (UnsupportedAudioFileException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } diff --git a/src/Acquisition/pamAudio/WavFileInputStream.java b/src/Acquisition/pamAudio/WavFileInputStream.java index a0f2c0e5..fbb2126a 100644 --- a/src/Acquisition/pamAudio/WavFileInputStream.java +++ b/src/Acquisition/pamAudio/WavFileInputStream.java @@ -51,13 +51,15 @@ public class WavFileInputStream extends AudioInputStream { if (wavHeader.readHeader(windowsFile) == false) { throw new UnsupportedAudioFileException("Unsupprted wav file format in " + file.getName()); } + + long nFrames = wavHeader.getDataSize() / wavHeader.getBlockAlign(); //29/03/2017 Found that the block align read from header was wrong in SoundTrap. This solves the problem and is still OK for normal //wav files (this is in fact tha standard emthod for calculated blockalign) int blockAlign = wavHeader.getNChannels() * (wavHeader.getBitsPerSample() / 8); - //System.out.println("NFRAMES: " + nFrames + " "+ wavHeader.getDataSize() + " " + wavHeader.getBlockAlign() + " "+blockAlign ); +// System.out.println("NFRAMES: " + nFrames + " "+ wavHeader.getDataSize() + " " + wavHeader.getBlockAlign() + " "+blockAlign ); Encoding encoding = getEncoding(wavHeader.getFmtTag()); if (encoding == null) { diff --git a/src/Acquisition/sud/SUDFileTime.java b/src/Acquisition/sud/SUDFileTime.java index 92ac94ca..c0ccc16b 100644 --- a/src/Acquisition/sud/SUDFileTime.java +++ b/src/Acquisition/sud/SUDFileTime.java @@ -45,11 +45,17 @@ public class SUDFileTime { // return Long.MIN_VALUE; // } // long t = sudMap.getFirstChunkTimeMillis(); + System.out.println("Error getting time from SUD file: " + file==null? null : (file.getName() + " size: " + file.length() / (1024 * 1024) + " MB")); + long t = SudAudioInputStream.quickFileTime(file); t=t/1000; //turn to milliseconds. - if (t != 0) { + if (t > 0) { sudTime = t; } + else { + //an error has occurred + System.err.println("Error getting time from SUD file: " + file==null? null : (file.getName() + " size: " + file.length() / (1024 * 1024) + " MB")); + } // sudAudioInputStream.addSudFileListener((chunkID, sudChunk)->{ // ChunkHeader chunkHead = sudChunk.chunkHeader; diff --git a/src/Acquisition/sud/SUDNotificationManager.java b/src/Acquisition/sud/SUDNotificationManager.java index 28bed217..b34758f2 100644 --- a/src/Acquisition/sud/SUDNotificationManager.java +++ b/src/Acquisition/sud/SUDNotificationManager.java @@ -59,7 +59,7 @@ public class SUDNotificationManager implements SUDNotificationHandler { @Override public void interpretNewFile(String newFile, SudAudioInputStream sudAudioStream) { for (SUDNotificationHandler handler : handlers) { - handler.interpretNewFile(newFile, sudAudioStream); + handler.interpretNewFile(newFile, sudAudioStream); } } diff --git a/src/Array/ArrayManager.java b/src/Array/ArrayManager.java index 6a605fff..3fa7a21a 100644 --- a/src/Array/ArrayManager.java +++ b/src/Array/ArrayManager.java @@ -10,13 +10,9 @@ import java.util.ArrayList; import java.util.Arrays; import javax.swing.JFrame; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - import pamMaths.PamQuaternion; import pamMaths.PamVector; import userDisplay.UserDisplayControl; -import depthReadout.DepthControl; import Array.importHydrophoneData.HydrophoneImport; import Array.importHydrophoneData.StreamerImport; import Array.layoutFX.ArrayGUIFX; @@ -91,7 +87,7 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO // private DepthControl depthControl; - private ImportDataSystem> hydrophoneImportManager; + private ImportDataSystem hydrophoneImportManager; private ImportDataSystem> streamerImportManager; @@ -140,7 +136,7 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO //enable importing of time stamped hydrophone and streamer data if in viewer mode. if (isViewer){ - hydrophoneImportManager= new ImportDataSystem>(new HydrophoneImport(hydrophonesProcess.getHydrophoneDataBlock())); + hydrophoneImportManager= new ImportDataSystem(new HydrophoneImport(hydrophonesProcess.getHydrophoneDataBlock())); hydrophoneImportManager.setName("Hydrophone Data Import"); streamerImportManager = new ImportDataSystem>(new StreamerImport(hydrophonesProcess.getStreamerDataBlock())); streamerImportManager.setName("Streamer Data Import"); @@ -1054,12 +1050,17 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO /** * Rotate the hydrophone about the centre of it's streamer. */ - Hydrophone hydrophone = currentArray.getHiddenHydrophone(i); +// Hydrophone hydrophone = currentArray.getHiddenHydrophone(i); + Hydrophone hydrophone = currentArray.getHydrophone(i, timeMillis); + + + if (hydrophone == null) { continue; } PamVector hydrophoneVec = hydrophone.getVector(); PamVector hydrophoneErrorVec = hydrophone.getErrorVector(); + if (streamerQuaternion != null) { hydrophoneVec = PamVector.rotateVector(hydrophoneVec, streamerQuaternion); hydrophoneErrorVec = PamVector.rotateVector(hydrophoneErrorVec, streamerQuaternion); @@ -1111,7 +1112,7 @@ public class ArrayManager extends PamControlledUnit implements PamSettings, PamO referencePoint = shipPos.getGpsData(); } } - if (referencePoint == null) { + if (referencePoint == null && MasterReferencePoint.getFixTime() != null && MasterReferencePoint.getLatLong() != 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); } diff --git a/src/Array/Hydrophone.java b/src/Array/Hydrophone.java index c2f82bd5..be3b11f8 100644 --- a/src/Array/Hydrophone.java +++ b/src/Array/Hydrophone.java @@ -203,7 +203,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { /** * @return error on the hydrophone x coordinate. */ - protected double getdX() { + public double getdX() { return getCoordinateError(0); } @@ -211,7 +211,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { * Set the error on the hydrophone x coordinate * @param error error in metres. */ - protected void setdX(double error) { + public void setdX(double error) { setCoordinateError(0, error); } @@ -219,7 +219,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { /** * @return error on the hydrophone y coordinate. */ - protected double getdY() { + public double getdY() { return getCoordinateError(1); } @@ -227,14 +227,14 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { * Set the error on the hydrophone y coordinate * @param error error in metres. */ - protected void setdY(double error) { + public void setdY(double error) { setCoordinateError(1, error); } /** * @return error on the hydrophone depth coordinate. */ - protected double getdZ(){ + public double getdZ(){ return getCoordinateError(2); } @@ -242,19 +242,19 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { * Set the error on the hydrophone z coordinate * @param error error in metres. */ - protected void setdZ(double error) { + public void setdZ(double error) { setCoordinateError(2, error); } - protected double getX() { + public double getX() { return coordinate[0]; } - protected void setX(double x) { + public void setX(double x) { this.coordinate[0] = x; } - protected double getY() { + public double getY() { return coordinate[1]; } @@ -289,7 +289,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { return y2; } - protected void setY(double y) { + public void setY(double y) { this.coordinate[1] = y; } @@ -297,7 +297,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { return coordinate[2]; } - protected void setZ(double z) { + public void setZ(double z) { this.coordinate[2] = z; } @@ -316,7 +316,7 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { // need to explicity create a new copy of the double[3] coordinate // and the double[2] bandwidth Hydrophone h = (Hydrophone) super.clone(); - h.setBandwidth(Arrays.copyOf(getBandwidth(), h.getBandwidth().length)); + h.setBandwidth(getBandwidth() == null ? null : Arrays.copyOf(getBandwidth(), h.getBandwidth().length)); h.setCoordinate(Arrays.copyOf(getCoordinates(), 3)); h.setCoordinateErrors(Arrays.copyOf(getCoordinateErrors(), 3)); h.checkDepthInversion(); @@ -379,14 +379,14 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { /** * @return Returns the iD. */ - protected int getID() { + public int getID() { return iD; } /** * @param id The iD to set. */ - protected void setID(int id) { + public void setID(int id) { iD = id; } // @@ -427,11 +427,11 @@ public class Hydrophone implements Serializable, Cloneable, ManagedParameters { this.symbol = symbol; } - protected int getStreamerId() { + public int getStreamerId() { return streamerId; } - protected void setStreamerId(int streamerId) { + public void setStreamerId(int streamerId) { this.streamerId = streamerId; } diff --git a/src/Array/HydrophoneDataBlock.java b/src/Array/HydrophoneDataBlock.java index 43ec7fb3..a5c17785 100644 --- a/src/Array/HydrophoneDataBlock.java +++ b/src/Array/HydrophoneDataBlock.java @@ -5,6 +5,7 @@ import java.util.ListIterator; import GPS.NavDataSynchronisation; import pamScrollSystem.ViewLoadObserver; import PamController.PamController; +import PamUtils.PamCalendar; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.dataOffline.OfflineDataLoadInfo; @@ -68,6 +69,11 @@ public class HydrophoneDataBlock extends PamDataBlock { @Override public boolean loadViewerData(OfflineDataLoadInfo offlineDataLoadInfo, ViewLoadObserver loadObserver) { + +// if (offlineDataLoadInfo!=null) { +// System.out.print("Load Hydrophones: " + ((offlineDataLoadInfo.getEndMillis() - offlineDataLoadInfo.getStartMillis())/1000/60/60 + " hours")); +// System.out.print("From: " +PamCalendar.formatDateTime(offlineDataLoadInfo.getStartMillis()) + " to " + PamCalendar.formatDateTime(offlineDataLoadInfo.getEndMillis())); +// } /** * Always put in default data units at time zero. */ @@ -103,6 +109,7 @@ public class HydrophoneDataBlock extends PamDataBlock { } unit = listIterator.previous(); difference = Math.abs(startTime - unit.getTimeMilliseconds()); + while (listIterator.hasPrevious()) { preceedingUnit = listIterator.previous(); if (preceedingUnit.getHydrophone().getID()!=ihydrophone) { @@ -110,6 +117,7 @@ public class HydrophoneDataBlock extends PamDataBlock { } newdifference = Math.abs(startTime- preceedingUnit.getTimeMilliseconds()); if (newdifference > difference) { +// System.out.println("Hydrophone datablock: newDifference: " + newdifference + " " + unit.getHydrophone().getZ()); return unit; } else { diff --git a/src/Array/HydrophoneElementDialog.java b/src/Array/HydrophoneElementDialog.java index a3f482cd..76eabacd 100644 --- a/src/Array/HydrophoneElementDialog.java +++ b/src/Array/HydrophoneElementDialog.java @@ -397,6 +397,8 @@ public class HydrophoneElementDialog extends PamDialog { dz.setText(null); } } + + boolean getParams() { double zCoeff = PamController.getInstance().getGlobalMediumManager().getZCoeff(); diff --git a/src/Array/PamArray.java b/src/Array/PamArray.java index a116108d..8bc8fd8d 100644 --- a/src/Array/PamArray.java +++ b/src/Array/PamArray.java @@ -39,6 +39,7 @@ import PamModel.parametermanager.PamParameterSet.ParameterSetType; import PamModel.parametermanager.PrivatePamParameterData; import PamUtils.LatLong; import PamUtils.PamArrayUtils; +import PamUtils.PamCalendar; import PamView.PamSymbol; import PamguardMVC.ChannelIterator; import PamguardMVC.PamConstants; @@ -265,7 +266,9 @@ public class PamArray implements Serializable, Cloneable, ManagedParameters { } protected Hydrophone getHydrophone(int iPhone, long timeMilliseconds) { - // Debug.println("PAMArray: Get hydrophone coords: " + timeMilliseconds + " iPhone: " + iPhone); +// System.out.println("PAMArray: Get hydrophone coords: " + PamCalendar.formatDateTime(timeMilliseconds) + " iPhone: " + iPhone); + + if (hydrophoneInterpolation == ORIGIN_USE_LATEST) { return getHydrophone(iPhone); } @@ -275,12 +278,14 @@ public class PamArray implements Serializable, Cloneable, ManagedParameters { //FIXME - for some reason the above lines were always returning the first hydrophone in the datablock ^ HydrophoneDataUnit hdu = ArrayManager.getArrayManager().getHydrophoneDataBlock().getClosestHydrophone(timeMilliseconds, iPhone); + +// System.out.println("PAMArray: hdu: " + hdu + " " + (hdu==null? null: PamCalendar.formatDateTime(hdu.getTimeMilliseconds()) + " Z" + hdu.getHydrophone().getdZ())); if (hdu != null) { - // Debug.println("PAMArray: found unit: " + hdu.getTimeMilliseconds()); - // long firstTime = ArrayManager.getArrayManager().getHydrophoneDataBlock().getFirstUnit().getTimeMilliseconds(); - // long lastTime = ArrayManager.getArrayManager().getHydrophoneDataBlock().getLastUnit().getTimeMilliseconds(); - // Debug.println("PAMArray: found unit: " + firstTime + " " + lastTime + " no: units: " + ArrayManager.getArrayManager().getHydrophoneDataBlock().getUnitsCount()); +// System.out.println("PAMArray: found unit: " + hdu.getTimeMilliseconds()); +// long firstTime = ArrayManager.getArrayManager().getHydrophoneDataBlock().getFirstUnit().getTimeMilliseconds(); +// long lastTime = ArrayManager.getArrayManager().getHydrophoneDataBlock().getLastUnit().getTimeMilliseconds(); +// System.out.println("PAMArray: found unit: " + firstTime + " " + lastTime + " no: units: " + ArrayManager.getArrayManager().getHydrophoneDataBlock().getUnitsCount()); // TODO should maybe do something here to average out two hydrophones if interpolation option is selected. return hdu.getHydrophone(); } @@ -1305,7 +1310,6 @@ public class PamArray implements Serializable, Cloneable, ManagedParameters { * @return */ public int addStreamer(Streamer streamer) { - synchronized (streamers) { streamers.add(streamer); checkStreamerIndexes(); diff --git a/src/Array/Streamer.java b/src/Array/Streamer.java index 7910da0f..f7fad355 100644 --- a/src/Array/Streamer.java +++ b/src/Array/Streamer.java @@ -571,7 +571,7 @@ public class Streamer implements Serializable, Cloneable, ManagedParameters { /** * Make a streamer data unit and add it to the data block. */ - protected void makeStreamerDataUnit() { + public void makeStreamerDataUnit() { StreamerDataUnit sdu = new StreamerDataUnit(PamCalendar.getTimeInMillis(), this); ArrayManager.getArrayManager().getStreamerDatabBlock().addPamData(sdu); } @@ -667,8 +667,8 @@ public class Streamer implements Serializable, Cloneable, ManagedParameters { */ @Override public String toString() { - return super.toString() + "; OriginSettings: " + getOriginSettings().toString() + "," + getHydrophoneOrigin().getOriginSettings().toString() + - "; Locator " + getLocatorSettings().toString(); + return super.toString() + "; OriginSettings: " + getOriginSettings()==null ? "null" : getOriginSettings().toString() + "," + getHydrophoneOrigin().getOriginSettings().toString() + + "; Locator " + getLocatorSettings()==null ? "null" : getLocatorSettings().toString(); } public static Streamer getAverage(Streamer sd1, diff --git a/src/Array/StreamerDialog.java b/src/Array/StreamerDialog.java index 73a81469..52bea18c 100644 --- a/src/Array/StreamerDialog.java +++ b/src/Array/StreamerDialog.java @@ -318,7 +318,7 @@ public class StreamerDialog extends PamDialog { PamDialog.setDoubleValue(heading, defaultStreamer.getHeading(), "%3.1f"); PamDialog.setDoubleValue(pitch, defaultStreamer.getPitch(), "%3.1f"); PamDialog.setDoubleValue(roll, defaultStreamer.getRoll(), "%3.1f"); - + interpolationPanel.setSelection(currentArray.getOriginInterpolation()); ArraySensorFieldType[] sensorFields = ArraySensorFieldType.values(); @@ -376,6 +376,9 @@ public class StreamerDialog extends PamDialog { defaultStreamer.setStreamerName(streamerName.getText()); int im = interpolationPanel.getSelection(); + + System.out.println("GetParams: INTERPOLATION SELECTION: " + currentArray.getOriginInterpolation()); + if (im < 0) { return showWarning("Invalid interpolation selection"); } diff --git a/src/Array/importHydrophoneData/HydrophoneImport.java b/src/Array/importHydrophoneData/HydrophoneImport.java index 93a924be..1d634d39 100644 --- a/src/Array/importHydrophoneData/HydrophoneImport.java +++ b/src/Array/importHydrophoneData/HydrophoneImport.java @@ -1,15 +1,22 @@ package Array.importHydrophoneData; +import java.io.IOException; import java.util.ArrayList; import Array.ArrayManager; import Array.Hydrophone; import Array.HydrophoneDataBlock; import Array.HydrophoneDataUnit; +import PamUtils.PamCalendar; import PamUtils.TxtFileUtils; import PamView.importData.DataImport; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.Array; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; /** * Class for importing hydrophone data from external file and saving to database. @@ -27,29 +34,31 @@ import PamguardMVC.PamDataUnit; * * @author Jamie Macaulay */ -public class HydrophoneImport extends DataImport>{ - - String[] extensionStrings={".csv"}; +public class HydrophoneImport extends DataImport{ + + String[] extensionStrings={".csv", ".mat"}; private ArrayList> hydrophonePositions; private int errorCode; private HydrophoneDataBlock hydrophoneDataBlock; - + + + /** * Streamer id to use if imported data has no streamer id info */ public static int defaultStreamerID=0; - -// /** -// *Gain value to use if imported data has no gain info -// */ -// public static int defaultGain=0; // use ArrayManager default instead - -// /** -// *Sensitivity value to use if imported data has no sensitivty info -// */ -// public static int defaultSens=-170; // use ArrayManager default instead - + // /** + // *Gain value to use if imported data has no gain info + // */ + // public static int defaultGain=0; // use ArrayManager default instead + + // /** + // *Sensitivity value to use if imported data has no sensitivty info + // */ + // public static int defaultSens=-170; // use ArrayManager default instead + + /********NOT IMPLEMENTED YET************* * Loads from a matlab structure with following format * structure(i).time =time @@ -58,32 +67,32 @@ public class HydrophoneImport extends DataImport>{ * */ public final static int MATLAB_STRUCT_FORMAT=2; - + /** * Everything seems fine */ public final static int DATA_OK=3; - + /** * The data is far in the past or in the future */ public final static int ERROR_YEARS=4; - + /** * Something has gone wrong getting the csv file */ public final static int ERROR_LOADING_CSV=5; - + /** * Something has gone wrong loading the matlab structure */ public final static int ERROR_LOADING_MATLAB_STRUCT=6; - + /** - * The number of hydrophones is not the same as the number of hydrophones in the curretn array manager. + * The number of hydrophones is not the same as the number of hydrophones in the current array manager. */ public final static int ERROR_NUMBER_OF_HYDROPHONES_ARRAY=7; - + /** * The number of hydrophones is different for different times. */ @@ -95,11 +104,11 @@ public class HydrophoneImport extends DataImport>{ @Override public ArrayList loadDataIntermediate(String filePath) { - + if (filePath.endsWith(".csv")){ - + hydrophonePositions=TxtFileUtils.importCSVData(filePath); - + if (hydrophonePositions==null || hydrophonePositions.size()==0 ) errorCode=ERROR_LOADING_CSV; else{ //we now have two possibilities. either loading in a legacy file or loading in a list of hydrophones. @@ -112,77 +121,153 @@ public class HydrophoneImport extends DataImport>{ if ((hydrophonePositions.get(0).size()-1)%6==0){ return convertToHydrophoneList(hydrophonePositions); } - + } } - + if (filePath.endsWith(".mat")){ - - hydrophonePositions=importPositionsFromMatlab(filePath); - + + ArrayList hydrophonePositions = importPositionsFromMatlab(filePath); + if (hydrophonePositions==null) errorCode=ERROR_LOADING_MATLAB_STRUCT; - + + return hydrophonePositions; + } - + return null; - + } - + /** - * Converts a 2D List of hydrophones into a 1D list of hydrophones. - * @param importData. Each row of the input array must have the following format. time, x0, y0,z0, x0Error, y0Error, z0Error,x1, y1,z1, x1Error, y1Error, z1Error,,..... and so on depending on the number of hydrophones. - * @return a list of hydrophones with the following format for each row. [0]=timeMilliss [1]=x [2]=y [3]=z [4]=xErr [5]=yErr [6]=zErr [7]=streamerId [8]=hydrophoneId; + * Converts a 2D List of hydrophones into a 1D list of hydrophone objecys. + * + * @param importData. Each row of the input array must have the following + * format. time, x0, y0,z0, x0Error, y0Error, z0Error,x1, + * y1,z1, x1Error, y1Error, z1Error,,..... and so on + * depending on the number of hydrophones. + * @return a list of hydrophone objects. */ - public ArrayList> convertToHydrophoneList(ArrayList> importData){ - - ArrayList> hydrophonesAll=new ArrayList>(); + public ArrayList convertToHydrophoneList(ArrayList> importData){ + + ArrayList hydrophonesAll=new ArrayList(); ArrayList tempArray; Hydrophone hydrophone; double[] cOordinates; double [] cOordinateErrors; + double sensitivity; + double gain; for (int i=0; i(); cOordinates=new double[3]; cOordinateErrors=new double[3]; - + cOordinates[0]=importData.get(i).get(j*6+1); cOordinates[1]=importData.get(i).get(j*6+2); cOordinates[2]=importData.get(i).get(j*6+3); - + cOordinateErrors[0]=importData.get(i).get(j*6+4); cOordinateErrors[1]=importData.get(i).get(j*6+5); cOordinateErrors[2]=importData.get(i).get(j*6+6); - - tempArray.add(importData.get(i).get(0)); - tempArray.add(cOordinates[0]); - tempArray.add(cOordinates[1]); - tempArray.add(cOordinates[2]); - tempArray.add(cOordinateErrors[0]); - tempArray.add(cOordinateErrors[1]); - tempArray.add(cOordinateErrors[2]); + + sensitivity=ArrayManager.DEFAULT_HYDROPHONE_SENSITIVITY; + gain=ArrayManager.DEFAULT_PREAMP_GAIN; + + hydrophone=new Hydrophone(j, + cOordinates[0], cOordinates[1],cOordinates[2], + cOordinateErrors[0], cOordinateErrors[1],cOordinateErrors[2], + "Unknown", + sensitivity, + new double[]{0, 20000},//meh + gain); + + long timeMillis= (long) PamUtils.PamCalendar.excelSerialtoMillis(importData.get(i).get(0)); + hydrophone.setTimeMillis(timeMillis); + + // tempArray.add(importData.get(i).get(0)); + // tempArray.add(cOordinates[0]); + // tempArray.add(cOordinates[1]); + // tempArray.add(cOordinates[2]); + // tempArray.add(cOordinateErrors[0]); + // tempArray.add(cOordinateErrors[1]); + // tempArray.add(cOordinateErrors[2]); //set Streamer ID. - tempArray.add((double) defaultStreamerID); + // tempArray.add((double) defaultStreamerID); //set hydrophoneID -// System.out.println("Hydrophone iD"+j); - - tempArray.add((double) j); - hydrophonesAll.add(tempArray); -// System.out.println("TempArray: "+tempArray); + // System.out.println("Hydrophone iD"+j); + + // tempArray.add((double) j); + hydrophonesAll.add(hydrophone); + // System.out.println("TempArray: "+tempArray); } } return hydrophonesAll; } - - - private ArrayList> importPositionsFromMatlab( + + /** + * Import the hydrophone positions from a MATLAB mat file. + * @param filePath - the file path. + * @return an array of hydrophones. + */ + private static ArrayList importPositionsFromMatlab( String filePath) { - // TODO- needs to be implemented. + + try { + + ArrayList hydrophones = new ArrayList(); + Mat5File mat5 = Mat5.readFromFile(filePath); + Struct structArray = mat5.getArray("array_dimensions"); + + double sensitivity=ArrayManager.DEFAULT_HYDROPHONE_SENSITIVITY; + double gain=ArrayManager.DEFAULT_PREAMP_GAIN; + + Matrix posStruct; + Matrix errStruct; + Matrix datetime; + // System.out.println("Number of structures: " + structArray.getNumElements()); + + Hydrophone hydrophone; + for (int i=0; i>{ } @Override - public boolean isDataFormatOK(ArrayList dataLine) { + public boolean isDataFormatOK(Hydrophone hydrophone) { // TODO might need to put some extra bits and bobs here eventually. return true; } @@ -201,34 +286,43 @@ public class HydrophoneImport extends DataImport>{ * For hydrophone data imported [0]=timeMilliss [1]=x [2]=y [3]=z [4]=xErr [5]=yErr [6]=zErr [7]=streamerId [8]=hydrophoneId [9]=sensitivity [10]=gain */ @Override - public PamDataUnit createDataUnit(ArrayList dataLine) { - - double sensitivity=ArrayManager.DEFAULT_HYDROPHONE_SENSITIVITY; - double gain=ArrayManager.DEFAULT_PREAMP_GAIN; - if (dataLine.size()==10) { - gain=dataLine.get(10); - sensitivity=dataLine.get(9); - } - double[] bandwidth={0, 20000}; + public PamDataUnit createDataUnit(Hydrophone hydrophone) { - Hydrophone hydrophone=new Hydrophone(dataLine.get(8).intValue(), dataLine.get(1), dataLine.get(2), dataLine.get(3), dataLine.get(4),dataLine.get(5), dataLine.get(6), "Unknown", sensitivity, - bandwidth, gain); - //need to convert from excel serial to millis. - long timeMillis= (long) PamUtils.PamCalendar.excelSerialtoMillis(dataLine.get(0)); - hydrophone.setTimeMillis(timeMillis); + // double sensitivity=ArrayManager.DEFAULT_HYDROPHONE_SENSITIVITY; + // double gain=ArrayManager.DEFAULT_PREAMP_GAIN; + // if (dataLine.size()==10) { + // gain=dataLine.get(10); + // sensitivity=dataLine.get(9); + // } + // double[] bandwidth={0, 20000}; + // + // Hydrophone hydrophone=new Hydrophone(dataLine.get(8).intValue(), dataLine.get(1), dataLine.get(2), dataLine.get(3), dataLine.get(4),dataLine.get(5), dataLine.get(6), "Unknown", sensitivity, + // bandwidth, gain); + // //need to convert from excel serial to millis. + // long timeMillis= (long) PamUtils.PamCalendar.excelSerialtoMillis(dataLine.get(0)); + // hydrophone.setTimeMillis(timeMillis); HydrophoneDataUnit hydrophoneDataUnit=new HydrophoneDataUnit(hydrophone); return hydrophoneDataUnit; - + } @Override public PamDataBlock getDataBlock() { return hydrophoneDataBlock; } - + @Override public String getDataUnitName(){ return "Hydrophone Units"; } + public static void main(String [] args) { + String file = "/Users/au671271/Desktop/test_array_data.mat"; + ArrayList data = importPositionsFromMatlab(file); + + System.out.println("Impotred data size: " + data.size()); + System.out.println(data.get(0)); + + } + } diff --git a/src/Array/layoutFX/Array3DPane.java b/src/Array/layoutFX/Array3DPane.java new file mode 100644 index 00000000..f7b9c8b4 --- /dev/null +++ b/src/Array/layoutFX/Array3DPane.java @@ -0,0 +1,586 @@ +package Array.layoutFX; + +import pamViewFX.fxNodes.PamBorderPane; + + +import java.util.ArrayList; +import org.fxyz3d.geometry.Point3D; +import org.fxyz3d.shapes.composites.PolyLine3D; + +import Array.Hydrophone; +import Array.PamArray; +import Array.Streamer; +import javafx.event.EventHandler; +import javafx.scene.AmbientLight; +import javafx.scene.DepthTest; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.PerspectiveCamera; +import javafx.scene.SceneAntialiasing; +import javafx.scene.SubScene; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.PickResult; +import javafx.scene.input.ScrollEvent; +import javafx.scene.paint.Color; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.Box; +import javafx.scene.shape.Shape3D; +import javafx.scene.shape.Sphere; +import javafx.scene.text.Text; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +/** + * Create a 3D visualisation of the array. + *

+ PAMGUARD co-rdinate system is + *

+ * x points right + *

+ * y points north or into the screen + *

+ * z is height and points up + *

+ * This is different from the JavAFX 3D system in which + *

+ * x points right + *

+ * y points down + *

+ * z points into the screen + *

+ * Thus the source code for this class is a little bit more complex. By convention the co-ordinate system is only changed for display purposes and remains + * in PAMGUARD format throughout the rest of code. + * @author Jamie Macaulay + * + */ +public class Array3DPane extends PamBorderPane { + + public static final Color DEFAULT_HYDRO_COL = Color.RED; + + // private static final Color DEFAULT_SENSOR_COL = Color.LIMEGREEN; + + private double scaleFactor=20; + + private double axisSize=10*scaleFactor; + + //keep track of mouse positions + double mousePosX; + double mousePosY; + double mouseOldX; + double mouseOldY; + double mouseDeltaX; + double mouseDeltaY; + + /** + * This is the group which rotates + */ + Group root3D; + + /** + * Group which holds array shapes. + */ + Group arrayGroup; + + /** + * Group which holds axis and other non changing bits. + */ + Group axisGroup; + + /** + * The camera transforms + */ + private Rotate rotateY; + private Rotate rotateX; + private Translate translate; + + /** + * The size of the hydrophone for the 3D display. + */ + private double hydrophoneSize = 0.5; + + /** + * Holds a list of hydrophone spheres + */ + private ArrayList hydrophonesSpheres = new ArrayList(); + + /** + * Allow rotation + */ + private boolean allowRotate = true; + + private Text xText; + + private Text yText; + + private Text zText; + + private Box yAxis; + + private Shape3D ySphere; + + + public Array3DPane(){ + + // Create and position camera + PerspectiveCamera camera = new PerspectiveCamera(true); + camera.setFarClip(20000); + camera.setNearClip(0.1); +// camera.setDepthTest(DepthTest.ENABLE); + camera.getTransforms().addAll ( + rotateY=new Rotate(-45, Rotate.Y_AXIS), + rotateX=new Rotate(-45, Rotate.X_AXIS), + translate=new Translate(0, 0, -2000)); + + //create main 3D group + root3D=new Group(); + axisGroup=buildAxes(axisSize); //create axis group + arrayGroup=new Group(); + + root3D.getChildren().add(arrayGroup); + root3D.getChildren().add(axisGroup); + + + AmbientLight light = new AmbientLight(); + light.setColor(Color.WHITE); + Group lightGroup = new Group(); + lightGroup.getChildren().add(light); + root3D.getChildren().add(lightGroup); + + //Use a SubScene to mix 3D and 2D stuff. + //note- make sure depth buffer in sub scene is enabled. + SubScene subScene = new SubScene(root3D, 500,400, true, SceneAntialiasing.BALANCED); + subScene.widthProperty().bind(this.widthProperty()); + subScene.heightProperty().bind(this.heightProperty()); + subScene.setDepthTest(DepthTest.ENABLE); + + subScene.setOnMouseClicked((MouseEvent me) -> { + mousePosX = me.getSceneX(); + mousePosY = me.getSceneY(); + PickResult pr = me.getPickResult(); + // System.out.println("Picked something sphere: " + pr); + + //clear selected radius + for (HydrophoneSphere sphere : hydrophonesSpheres) { + sphere.setRadius(hydrophoneSize*scaleFactor); + } + + if(pr!=null && pr.getIntersectedNode() != null && pr.getIntersectedNode() instanceof Sphere){ + + //make the selected sphere slightly larger + HydrophoneSphere s = (HydrophoneSphere) pr.getIntersectedNode(); + s.setRadius(hydrophoneSize*scaleFactor*1.2); + + hydrophoneSelected(s.getHydrophone()); + + // System.out.println("Picked a sphere: " + pr); + // distance=pr.getIntersectedDistance(); + // s = (Sphere) pr.getIntersectedNode(); + // isPicking=true; + // vecIni = unProjectDirection(mousePosX, mousePosY, scene.getWidth(),scene.getHeight()); + } + }); + + //note the fill is actually quite important because if you don't have it mouse rotations etc + //onyl work if you select a 3D shape + subScene.setFill(Color.TRANSPARENT); + subScene.setCamera(camera); + + //handle mouse events for sub scene + handleMouse(subScene); + + resetView(); + + //create new group to add sub scene to + Group group = new Group(); + group.getChildren().add(subScene); + + //add group to window. + this.setCenter(group); + this.setDepthTest(DepthTest.ENABLE); + + } + + /** + * Called whenever a hydrophone is selected. + * @param hydrophone - the selected hydrophone. + */ + public void hydrophoneSelected(Hydrophone hydrophone) { + // TODO Auto-generated method stub + } + + /** + * Create a 3D axis with default colours set. + * @param- size of the axis + */ + public Group buildAxes(double axisSize) { + return buildAxes( axisSize,Color.RED, Color.RED, + Color.GREEN, Color.GREEN, + Color.BLUE, Color.BLUE, + Color.WHITE); + } + + + /** + * Create a 3D axis. + * @param- size of the axis + */ + public Group buildAxes(double axisSize, Color xAxisDiffuse, Color xAxisSpectacular, + Color yAxisDiffuse, Color yAxisSpectacular, + Color zAxisDiffuse, Color zAxisSpectacular, + Color textColour) { + + Group axisGroup=new Group(); + + double length = 2d*axisSize; + double width = axisSize/100d; + double radius = 2d*axisSize/100d; + + + final PhongMaterial redMaterial = new PhongMaterial(); + redMaterial.setDiffuseColor(xAxisDiffuse); + redMaterial.setSpecularColor(xAxisSpectacular); + final PhongMaterial greenMaterial = new PhongMaterial(); + greenMaterial.setDiffuseColor(yAxisDiffuse); + greenMaterial.setSpecularColor( yAxisSpectacular); + final PhongMaterial blueMaterial = new PhongMaterial(); + blueMaterial.setDiffuseColor(zAxisDiffuse); + blueMaterial.setSpecularColor(zAxisSpectacular); + + xText=new Text("x"); + xText.setStyle("-fx-font: 20px Tahoma; -fx-fill: white;"); + xText.setFill(textColour); + xText.setStroke(textColour); + + yText=new Text("z"); + yText.setStyle("-fx-font: 20px Tahoma; -fx-fill: white;"); + yText.setFill(textColour); + yText.setStroke(textColour); + + zText=new Text("y"); + zText.setStyle("-fx-font: 20px Tahoma; -fx-fill: white;"); + zText.setFill(textColour); + zText.setStroke(textColour); + + xText.setTranslateX(axisSize+5); + xText.setTranslateZ(1); //dunno why but shifting a little in z is required to see colour + + yText.setTranslateY(-(axisSize+5)); + yText.setTranslateZ(1); //dunno why but shifting a little in z is required to see colour + + zText.setTranslateZ(axisSize+5); + + Sphere xSphere = new Sphere(radius); + ySphere = new Sphere(radius); + Sphere zSphere = new Sphere(radius); + xSphere.setMaterial(redMaterial); + ySphere.setMaterial(greenMaterial); + zSphere.setMaterial(blueMaterial); + + xSphere.setTranslateX(axisSize); + ySphere.setTranslateY(-axisSize); + zSphere.setTranslateZ(axisSize); + + Box xAxis = new Box(length, width, width); + yAxis = new Box(width, length, width); + Box zAxis = new Box(width, width, length); + xAxis.setMaterial(redMaterial); + yAxis.setMaterial(greenMaterial); + zAxis.setMaterial(blueMaterial); + + axisGroup.getChildren().addAll(xAxis, yAxis, zAxis); + axisGroup.getChildren().addAll(xText, yText, zText); + axisGroup.getChildren().addAll(xSphere, ySphere, zSphere); + + + return axisGroup; + } + + + private void handleMouse(SubScene scene) { + + scene.setOnMousePressed(new EventHandler() { + + @Override public void handle(MouseEvent me) { + mousePosX = me.getSceneX(); + mousePosY = me.getSceneY(); + mouseOldX = me.getSceneX(); + mouseOldY = me.getSceneY(); + } + }); + + scene.setOnScroll(new EventHandler() { + @Override public void handle(ScrollEvent event) { + // System.out.println("Scroll Event: "+event.getDeltaX() + " "+event.getDeltaY()); + translate.setZ(translate.getZ()+ event.getDeltaY() *0.001*translate.getZ()); // + + } + }); + + + scene.setOnMouseDragged(new EventHandler() { + + + @Override + public void handle(MouseEvent me) { + mouseOldX = mousePosX; + mouseOldY = mousePosY; + mousePosX = me.getSceneX(); + mousePosY = me.getSceneY(); + mouseDeltaX = (mousePosX - mouseOldX); + mouseDeltaY = (mousePosY - mouseOldY); + + double modifier = 1.0; + double modifierFactor = 0.1; + + if (me.isControlDown()) { + modifier = 0.1; + } + if (me.isShiftDown()) { + modifier = 10.0; + } + if (me.isPrimaryButtonDown() && allowRotate) { + rotateY.setAngle(rotateY.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0); // + + rotateX.setAngle(rotateX.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0); // - + +// System.out.println("Rotation: x: " + rotateX.getAngle() + " y: " + rotateY.getAngle()); + } + if (me.isSecondaryButtonDown()) { + translate.setX(translate.getX() -mouseDeltaX * modifierFactor * modifier * 5); + translate.setY(translate.getY() - mouseDeltaY * modifierFactor * modifier * 5); // + + } + + + } + }); + } + + /** + * Draw the hydrophone array. + * @param array - the hydrophone array to draw. + */ + public void drawArray(PamArray array) { + + // System.out.println("DRAW ARRAY: " + array); + + //clear the array + arrayGroup.getChildren().removeAll(arrayGroup.getChildren()); + + ArrayList hydrophones = array.getHydrophoneArray(); + + //draw hydrophones + HydrophoneSphere sphere; + Streamer streamer; + hydrophonesSpheres.clear(); + for (int i=0; i streamerPoints=new ArrayList(); + //now plot a line from the streamer to the hydrophone + Point3D newPoint; + + newPoint=new Point3D(x*scaleFactor, z*scaleFactor, y*scaleFactor); + streamerPoints.add(newPoint); + + newPoint =new Point3D(streamer.getCoordinate(0)*scaleFactor, -streamer.getCoordinate(2)*scaleFactor, streamer.getCoordinate(1)*scaleFactor); + streamerPoints.add(newPoint); + + //System.out.println("Streamer points: " + streamerPoints.size()); + + PolyLine3D polyLine3D=new PolyLine3D(streamerPoints, 4f, Color.DODGERBLUE); + arrayGroup.getChildren().add(polyLine3D); + } + } + + + private class HydrophoneSphere extends Sphere { + + Hydrophone hydrophone; + + public Hydrophone getHydrophone() { + return hydrophone; + } + + + + public void setHydrophone(Hydrophone hydrophone) { + this.hydrophone = hydrophone; + } + + public HydrophoneSphere() { + super(); + } + + public HydrophoneSphere(double radius) { + super(radius); + } + + } + + /** + * Sets the pane to show hydrophones in 2D or 3D. + * @param set3D - true to set to 3D + */ + public void set3D(boolean set3D) { + + double textRotation =0; + + if (set3D) { + allowRotate=true; + xText.setRotate(0); + rotateY.setAngle(-45); + rotateX.setAngle(-45); + } + else { + allowRotate=false; + rotateY.setAngle(0); + rotateX.setAngle(270); + textRotation=-90; + } + + //confusing because the yaxis is the z axis in JavaFX... + yText.setVisible(set3D); + yAxis.setVisible(set3D); + ySphere.setVisible(set3D); + + + xText.setRotate(textRotation); + xText.setRotationAxis(new javafx.geometry.Point3D(1,0,0)); + + yText.setRotationAxis(new javafx.geometry.Point3D(1,0,0)); + yText.setRotate(textRotation); + + zText.setRotationAxis(new javafx.geometry.Point3D(1,0,0)); + zText.setRotate(textRotation); + + + } + + /** + * Reset to the defulat view. + */ + public void resetView() { + + translate.setX(0); + translate.setY(0); + translate.setZ( -2000); + + if (allowRotate) { + rotateY.setAngle(-45); + rotateX.setAngle(-45); + } + } + + // /** + // * Draw the entire array + // * @param pos - hydrophone and streamer positions in the same co-ordinate frame as the reference frame. + // */ + // public void drawArrays(ArrayList> pos){ + // + // arrayGroup.getChildren().removeAll(arrayGroup.getChildren()); + // + // if (pos==null){ + // System.err.println("Array3DPane: Hydrophone positions are null"); + // return; + // } + // + // for (int i=0; i< pos.size(); i++){ + // for (int j=0; j streamerPoints; + // + // for (int i=0; i(); + // for (int j=0; j + * + */ public class ArraySettingsPane extends SettingsPane{ + /** + * Minimum size of the 3D pane. + */ + private static final double MIN_3D_WIDTH = 450; + + /** + * Minimum height of the 3D pane. + */ + private static final double MIN_3D_HEIGHT = 600; + + /** + * Pane for adding or removing streamers. + */ + private StreamersPane streamerPane; + + private PamBorderPane mainPane; + + /** + * Pane for adding or removing hydrophones. + */ + private HydrophonesPane hydrophonePane; + + // private Pane holder; + + private Label hydrophoneLabel; + + /** + * Pane which shows a 3D representation of the hydrophone array. + */ + private Array3DPane array3DPane; + + private PropogationPane propogationPane; + + private Label recivierDiagramLabel; + + /** + * Pane with controls to change speed of sound. + */ + private SettingsPane environmentalPane; + + private FileChooser fileChooser; + + //a copy of the current array + private PamArray currentArray; + public ArraySettingsPane() { super(null); - // TODO Auto-generated constructor stub + + mainPane=new PamBorderPane(); + + mainPane.setCenter(createArrayPane()); + // mainPane.setStyle("-fx-background-color: red;"); + mainPane.setMaxWidth(Double.MAX_VALUE); + mainPane.setMinWidth(1100); + mainPane.setStyle("-fx-padding: 0,0,0,0"); + + + recivierDiagramLabel = new Label("Hydrophone Diagram"); + PamGuiManagerFX.titleFont1style(recivierDiagramLabel); + recivierDiagramLabel.setPadding(new Insets(5,5,5,5)); + + Label environmentLabel = new Label("Environment"); + PamGuiManagerFX.titleFont1style(environmentLabel); + environmentLabel.setPadding(new Insets(0,0,5,0)); //little more space under this label + + environmentalPane = createEnvironmentPane(); + + PamVBox rightPane = new PamVBox(); + rightPane.setSpacing(5); + + Pane hydrophone3DPane = create3DPane(); + rightPane.getChildren().addAll(recivierDiagramLabel, hydrophone3DPane, environmentLabel, new PamBorderPane(environmentalPane.getContentNode())); + VBox.setVgrow(hydrophone3DPane, Priority.ALWAYS); + + mainPane.setRight(rightPane); + + // streamerPane.getStreamerTable().getItems().addListener((ListChangeListener) c->{ + // //the streamer table has changed and so the streamer needs changed + // System.out.println("Streamer Changed!!!"); + // }); + + streamerPane.addStreamerListener((x,y)->{ + PamArray array = getParams(new PamArray("temp_array: ", null)) ; + System.out.println("Streamer changed!"); + array3DPane.drawArray(array); + }); + + hydrophonePane.addStreamerListener((x,y)->{ + PamArray array = getParams(new PamArray("temp_array: ", null)) ; + System.out.println("Hydrophone changed!" + array.getHydrophoneCount()); + array3DPane.drawArray(array); + }); + + // mainPane.setMinWidth(800); + + // mainPane.setCenter(createArrayPane()); + // + // mainPane.getAdvPane().setCenter(new Label("Advanced Settings")); + + + // //mainPane.getFront().setStyle("-fx-background-color: grey;"); + // mainPane.setStyle("-fx-background-color: red;"); + // + // FlipPane aflipPane = new FlipPane(); + // aflipPane.setStyle("-fx-background-color: red;"); + // + // PamHBox stackPane = new PamHBox(); + // stackPane.setStyle("-fx-background-color: red;"); + // + // Button button = new Button(); + // button.setOnAction((action)->{ + // System.out.println(" 1 " + stackPane.getPadding()); + // System.out.println(" 2 " +PamBorderPane.getMargin(stackPane)); + // System.out.println(" 3 " + holder.getPadding()); + // }); + // + // stackPane.getChildren().add(button); + // + // + // mainPane.setPadding(new Insets(0,0,0,0)); + + + // holder = new StackPane(); + // holder.getChildren().add(mainPane); + // holder.setStyle("-fx-padding: 0,0,0,0"); + } + private Pane create3DPane() { + + StackPane stackPane = new StackPane(); + this.array3DPane = new HydrophoneArray3DPane(); + + //important because the 3D pane has not default size + stackPane.setMinWidth(MIN_3D_WIDTH); + stackPane.setMinHeight(MIN_3D_HEIGHT); + + // stackPane.prefHeightProperty().bind(mainPane.heightProperty().subtract(100)); + + stackPane.getChildren().add(array3DPane); + + + //add buttons + ToggleButton b1 = new ToggleButton("2D"); + ToggleButton b2 = new ToggleButton("3D"); + + b1.setOnAction((action)->{ + array3DPane.set3D(false); + }); + + b2.setOnAction((action)->{ + array3DPane.set3D(true); + }); + + + SegmentedButton segmentedButton = new SegmentedButton(); + segmentedButton.getButtons().addAll(b1, b2); + segmentedButton.setPadding(new Insets(5,5,5,5)); + segmentedButton.setMinWidth(100); + + StackPane.setAlignment(segmentedButton, Pos.TOP_RIGHT); + stackPane.getChildren().add(segmentedButton); + + + final ContextMenu contextMenu = new ContextMenu(); + final MenuItem item1 = new MenuItem("Reset"); + item1.setOnAction((action)->{ + array3DPane.resetView(); + }); + contextMenu.getItems().add(item1); + segmentedButton.setContextMenu(contextMenu); + +// stackPane.setOnContextMenuRequested(e -> +// contextMenu.show(stackPane, e.getScreenX(), e.getScreenY())); + + + b2.setSelected(true); + + return stackPane; + } + + /** + * Create the environment pane. + * @return the environment pane. + */ + private SettingsPane createEnvironmentPane() { + this.propogationPane = new PropogationPane(); + return propogationPane; + } + + /** + * Create the main pane. + * @return the main array pane. + */ + private Pane createArrayPane() { + + Label arrayLabel = new Label("Array"); + arrayLabel.setPadding(new Insets(5,5,5,5)); + PamGuiManagerFX.titleFont1style(arrayLabel); + + //holds the array label and also some button for import and export. + PamHBox arrayImportExportBox = new PamHBox(); + arrayImportExportBox.setSpacing(5); + arrayImportExportBox.setAlignment(Pos.CENTER_LEFT); + arrayImportExportBox.setPadding(new Insets(5,0,0,0)); + + fileChooser = new FileChooser(); + fileChooser.setTitle("Open Resource File"); + fileChooser.getExtensionFilters().addAll( + new ExtensionFilter("PAMGuard Array Files", "*.paf")); + + PamButton importButton = new PamButton("Import..."); + importButton.setOnAction((action)->{ + importArray(); + }); + importButton.setGraphic(PamGlyphDude.createPamIcon("mdi2f-file-import", PamGuiManagerFX.iconSize)); + importButton.setTooltip(new Tooltip("Import array settings from a .pgaf file")); + + PamButton exportButton = new PamButton("Export..."); + exportButton.setOnAction((action)->{ + exportArray(); + }); + exportButton.setGraphic(PamGlyphDude.createPamIcon("mdi2f-file-export", PamGuiManagerFX.iconSize)); + exportButton.setTooltip(new Tooltip("Export array settings to a .pgaf file")); + + //balnk region to make it look nicer + Region blank = new Region(); + blank.setPrefWidth(70); + + arrayImportExportBox.getChildren().addAll(importButton, exportButton, blank); + + PamBorderPane titleHolder = new PamBorderPane(); + titleHolder.setLeft(arrayLabel); + titleHolder.setRight(arrayImportExportBox); + + //the streamer pane for changing streamer settings. + streamerPane = new StreamersPane(); + streamerPane.setMaxWidth(Double.MAX_VALUE); + + hydrophoneLabel = new Label("Hydrophones"); + PamGuiManagerFX.titleFont1style(hydrophoneLabel); + hydrophoneLabel.setPadding(new Insets(5,5,5,5)); + + hydrophonePane = new HydrophonesPane(); + hydrophonePane.setMaxWidth(Double.MAX_VALUE); + + // PamButton advancedButton = new PamButton(); + // advancedButton.setOnAction((action)->{ + // mainPane.flipToBack(); + // }); + // advancedButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-cog")); + + // PamHBox advancedPane = new PamHBox(); + // advancedPane.setSpacing(5); + // advancedPane.setAlignment(Pos.CENTER_RIGHT); + // advancedPane.getChildren().addAll(new Label("Advanced"), advancedButton); + + PamVBox vBox = new PamVBox(); + vBox.setSpacing(5); + vBox.getChildren().addAll(titleHolder, streamerPane, hydrophoneLabel, + hydrophonePane); + + return vBox; + } + + /** + * Select a file to export array settings to. + */ + private void exportArray() { + File file = fileChooser.showSaveDialog(getFXWindow()); + if (file==null) return; + PamArray pamArray = getParams(new PamArray("saved_array: ", null)); + boolean isSaved = ArrayManager.saveArrayToFile(pamArray); + if (isSaved==false) { + PamDialogFX.showError("Unable to save the array file to \n" + file.toString()); + } + } + + /** + * Select a file to import array settings + */ + private void importArray() { + File file = fileChooser.showOpenDialog(getFXWindow()); + if (file==null) return; + PamArray pamArray = ArrayManager.loadArrayFromFile(file.getPath()); + this.setParams(pamArray); + } + + /** + * Set correct text for the receiver in the current medium (e.g. air or water); + */ + private void setReceieverLabels() { + hydrophonePane.setRecieverLabels(); + streamerPane.setRecieverLabels(); + + hydrophoneLabel.setText(PamController.getInstance().getGlobalMediumManager().getRecieverString(true) + "s"); + recivierDiagramLabel.setText(PamController.getInstance().getGlobalMediumManager().getRecieverString(true) + " diagram"); + // if (singleInstance!=null) { + // singleInstance.setTitle("Pamguard "+ PamController.getInstance().getGlobalMediumManager().getRecieverString(false) +" array"); + // } + } + + @Override - public PamArray getParams(PamArray currParams) { - // TODO Auto-generated method stub - return null; + public PamArray getParams(PamArray currParams) { + + if (currParams==null) currParams = this.currentArray; + + currParams = streamerPane.getParams(currParams); + currParams = hydrophonePane.getParams(currParams); + currParams.setHydrophoneInterpolation(hydrophonePane.getHydrophoneInterp()); + currParams = environmentalPane.getParams(currParams); + // System.out.println("Array settings pane: No. streamers: " + currParams.getStreamerCount()); + return currParams; } @Override public void setParams(PamArray input) { - // TODO Auto-generated method stub - + this.currentArray = input.clone(); + // System.out.println("Hydrophone array is: "+ input); + setReceieverLabels(); + hydrophonePane.setParams(input); + streamerPane.setParams(input); + environmentalPane.setParams(input); + + //draw the array + array3DPane.drawArray(input); } @Override public String getName() { - // TODO Auto-generated method stub return "Array Parameters"; } @Override public Node getContentNode() { - // TODO Auto-generated method stub - return new Label("TODO: The Array Manager needs an FX GUI"); + + return mainPane; } @Override public void paneInitialized() { // TODO Auto-generated method stub - + + } + + private class HydrophoneArray3DPane extends Array3DPane { + + @Override + public void hydrophoneSelected(Hydrophone hydrophone) { + hydrophonePane.selectHydrophone(hydrophone); + } + } } diff --git a/src/Array/layoutFX/DefaultHydrophone.java b/src/Array/layoutFX/DefaultHydrophone.java new file mode 100644 index 00000000..5d571775 --- /dev/null +++ b/src/Array/layoutFX/DefaultHydrophone.java @@ -0,0 +1,58 @@ +package Array.layoutFX; + +/** + * Default hydrophone parameters. + * + * @author Jamie Macaulay + * + */ +public enum DefaultHydrophone { + + SoundTrap600HF("SoundTrap 600 HF", -176., 0), SoundTrap300HF("SoundTrap 300 HF", -176., 0), HydroMoth_1_0_0("HydroMoth 1.0.0", -180., 0); + + /** + * The name of the hydrophones. + */ + private String name; + + + /** + * The sensitivity of the hydrophone in dB re 1V/uPa. + */ + private double sens; + + /** + * The gain in dB. + */ + private double gain; + + /** + * The name of the hydrophone. + * @param name - the name of the hydrophone. + * @param sens - the sensitivity of the hydrophone. + * @param gain - the gain of the hydrophone. + */ + DefaultHydrophone(String name, double sens, double gain) { + this.name = name; + this.sens = sens; + this.gain = gain; + + } + + /** + * The sensitivity of the hydrophone in dB re 1V/uPa. + */ + public double getSens() { + return sens; + } + + + /** + * The gain in dB. + */ + public double getGain() { + return gain; + } + + +} diff --git a/src/Array/layoutFX/HydrophoneProperty.java b/src/Array/layoutFX/HydrophoneProperty.java new file mode 100644 index 00000000..72679010 --- /dev/null +++ b/src/Array/layoutFX/HydrophoneProperty.java @@ -0,0 +1,116 @@ +package Array.layoutFX; + +import Array.Hydrophone; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; + + +/** + * Property class for a hydrophone object. + * + * @author Jamie Macaulay + * + */ +public class HydrophoneProperty { + + SimpleDoubleProperty x = new SimpleDoubleProperty(); + + SimpleDoubleProperty y = new SimpleDoubleProperty(); + + SimpleDoubleProperty z = new SimpleDoubleProperty(); + + SimpleDoubleProperty xErr = new SimpleDoubleProperty(); + + SimpleDoubleProperty yErr = new SimpleDoubleProperty(); + + SimpleDoubleProperty zErr = new SimpleDoubleProperty(); + + SimpleIntegerProperty id = new SimpleIntegerProperty(); + + private Hydrophone hydrophone; + + public HydrophoneProperty(Hydrophone hydrophone) { + setHydrophone(hydrophone); + } + + public void setHydrophone(Hydrophone hydrophone) { + this.hydrophone = hydrophone; + + + x .set(hydrophone.getX()); + y .set(hydrophone.getY()); + z .set(hydrophone.getZ()); + + xErr .set(hydrophone.getdX()); + yErr .set(hydrophone.getdY()); + zErr .set(hydrophone.getdZ()); + + id.set(hydrophone.getID()); + } + + /** + * The x-coordinate property. + * @return the x coordintae property. + */ + public SimpleDoubleProperty getX() { + return x; + } + + /** + * The y-coordinate property. + * @return the y coordintae property. + */ + public SimpleDoubleProperty getY() { + return y; + } + + /** + * The z-coordinate property. + * @return the z coordintae property. + */ + public SimpleDoubleProperty getZ() { + return z; + } + + public SimpleIntegerProperty getID() { + return id; + } + + public Hydrophone getHydrophone() { + //incase table data changes. + this.hydrophone.setID(this.id.get()); + this.hydrophone.setX(x.get()); + this.hydrophone.setY(y.get()); + this.hydrophone.setZ(z.get()); + this.hydrophone.setdX(xErr.get()); + this.hydrophone.setdY(yErr.get()); + this.hydrophone.setdZ(xErr.get()); + + return hydrophone; + } + + /** + * The x-coordinate property. + * @return the x coordintae property. + */ + public SimpleDoubleProperty getXErr() { + return xErr; + } + + /** + * The y-coordinate property. + * @return the y coordintae property. + */ + public SimpleDoubleProperty getYErr() { + return yErr; + } + + /** + * The z-coordinate property. + * @return the z coordintae property. + */ + public SimpleDoubleProperty getZErr() { + return zErr; + } + +} diff --git a/src/Array/layoutFX/HydrophoneSettingsPane.java b/src/Array/layoutFX/HydrophoneSettingsPane.java new file mode 100644 index 00000000..0e8f4507 --- /dev/null +++ b/src/Array/layoutFX/HydrophoneSettingsPane.java @@ -0,0 +1,589 @@ +package Array.layoutFX; + +import Array.Hydrophone; +import javafx.scene.Node; +import javafx.scene.control.Label; +import Array.PamArray; +import Array.Streamer; +import PamController.PamController; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Labeled; +import javafx.scene.control.TextField; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import net.synedra.validatorfx.Validator; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxSettingsPanes.DynamicSettingsPane; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamGridPane; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamSpinner; +import pamViewFX.validator.PamValidator; + +/** + * The settings pane for a single hydrophones. + * + * @author Jamie Macaulay + * + */ +public class HydrophoneSettingsPane extends DynamicSettingsPane { + + private static final double COLUMN_0_WIDTH = 120; + + + private final static double MAX_TEXTFIELD_WIDTH = 80; + + /** + * + * Check inputs in real time + */ + PamValidator validator = new PamValidator(); + + + @Override + public String getName() { + return "Hydrophone Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + private TextField iD; + + private TextField yPos; + private TextField xPos; + private TextField zPos; + + private TextField yPosErr; + private TextField xPosErr; + private TextField zPosErr; + + private PamSpinner hSens; + private PamSpinner preampGain; + + private ComboBox streamers; + private ChoiceBox defaultArrays; + private TextField nameField; + + private PamArray currentArray; + + ///Labels which might change name if in air or water (i.e. hydrophone or microphone). + + private Labeled depthLabel; + + private Labeled depthLabel2; + + private Label recieverIDLabel; + + private Label recieverTypeLabel; + + private Label recieverSensLabel; + + private Label dBSensLabel; + + private boolean ressetHydrophoneType = false; + + /** + * The main holder pane. + */ + private PamBorderPane mainPane; + + private InterpChoicePane interpPane; + + private ComboBox defaultHydro; + + //create the dialog + public HydrophoneSettingsPane() { + super(null); + + PamVBox holderPane = new PamVBox(); + holderPane.setSpacing(5); + + + recieverIDLabel = new Label("General"); + PamGuiManagerFX.titleFont2style(recieverIDLabel); + + Label coOrdLabel = new Label("Coordinates"); + PamGuiManagerFX.titleFont2style(coOrdLabel); + + Label interpLabel = new Label("Interpolation"); + PamGuiManagerFX.titleFont2style(interpLabel); + + interpPane = new InterpChoicePane(); + + PamHBox interpHolder = new PamHBox(); + interpHolder.setSpacing(5); + interpHolder.setAlignment(Pos.CENTER_LEFT); + interpHolder.getChildren().addAll(new Label("Method"), interpPane); + + holderPane.getChildren().addAll(recieverIDLabel, createGeneralPane(), coOrdLabel, createPositionPane(), interpLabel, interpHolder); + + mainPane = new PamBorderPane(); + mainPane.setCenter(holderPane); + } + + // + // public Boolean getParams(){ + // array.nameProperty().setValue(nameField.getText()); + // array.hArrayTypeProperty().setValue(arrayType.getValue()); + // try { + // array.xPosProperty().setValue(Double.valueOf(xPos.getText())); + // array.yPosProperty().setValue(Double.valueOf(yPos.getText())); + // array.zPosProperty().setValue(Double.valueOf(zPos.getText())); + // } + // catch (Exception e){ + // System.err.println("Invalid field in Array Dialog"); + // return false; + // } + // return true; + // } + // + // public void setParams(Hydrophone hydrophone){ + // + // iD.setText(String.format("%d", hydrophone.getID())); + // streamers.getItems().clear(); + // + // //set thre text values for the recieevrs. + //// setRecieverLabelText(); + // if (currentArray != null) { + // Streamer s; + // for (int i = 0; i < currentArray.getNumStreamers(); i++) { + // s = currentArray.getStreamer(i); + // streamers.getItems().add(String.format("Streamer %d, x=%3.1f", i, s.getX())); + // } + // } + // if (hydrophone.getStreamerId() < currentArray.getNumStreamers()) { + // streamers.getSelectionModel().select(hydrophone.getStreamerId()); + // } + // hSens.setText(String.format("%.1f", hydrophone.getSensitivity()-PamController.getInstance().getGlobalMediumManager().getdBSensOffset())); + // preampGain.setText(String.format("%.1f", hydrophone.getPreampGain())); + // // bandwidth0.setText(String.format("%.1f", hydrophone.getBandwidth()[0])); + // // bandwidth1.setText(String.format("%.1f", hydrophone.getBandwidth()[1])); + // + // + //// this.array=array; + //// nameField.setText(hydrophone.getType()); + // parentArrayComboBox.setValue(array.get); + // + // //attachmentComboBox.setItems(ArrayModelControl.getInstance().getArrays()); + // parentArrayComboBox.setValue(array.parentHArrayProperty().getValue()); + // + // xPos.setText(Double.toString(hydrophone.); + // yPos.setText(Double.toString(array.yPosProperty().get())); + // zPos.setText(Double.toString(array.zPosProperty().get())); + // + // createArrayPane(array); + // + // } + + + /** + * Set the receiver labels depending on whether air or water is being used. + */ + private void setGeneralInfoLabelText() { + String recieverString = PamController.getInstance().getGlobalMediumManager().getRecieverString(); + String dbSens = PamController.getInstance().getGlobalMediumManager().getdBSensString(); + + recieverIDLabel.setText(recieverString+ " ID Info"); + recieverTypeLabel.setText(recieverString + " type "); + recieverSensLabel.setText(recieverString + " sens "); + dBSensLabel.setText(dbSens); + } + + + /** + * Set the receiver labels depending on whether air or water is being used. + */ + private void setCoordsText() { + String recieverDepthString = PamController.getInstance().getGlobalMediumManager().getZString(); + + depthLabel.setText(recieverDepthString + " "); + + switch (PamController.getInstance().getGlobalMediumManager().getCurrentMedium()) { + case Air: + depthLabel2.setText(" m (height above streamer)"); + break; + case Water: + depthLabel2.setText(" m (depth below streamer)"); + break; + } + + } + + + + + /** + * Create the pane to allow users to change the position of hydrophones + */ + private Pane createGeneralPane() { + + PamGridPane mainControls=new PamGridPane(); + mainControls.setHgap(5); + mainControls.setVgap(5); + + + int gridy = 0; + Label parentArrayLabel = new Label("Parent Array"); + parentArrayLabel.setAlignment(Pos.CENTER_LEFT); + mainControls.add(parentArrayLabel, 0, gridy); + streamers = new ComboBox(); + mainControls.add(streamers, 1, gridy); + + gridy++; + mainControls.add(recieverTypeLabel = new Label(""), 0, gridy); + recieverTypeLabel.setAlignment(Pos.CENTER_LEFT); + defaultHydro = new ComboBox(); + + for (int i=0; i{ + //don't want to trigger this if we are programtically setting it back + if (defaultHydro.getSelectionModel().getSelectedIndex() <= 0 || ressetHydrophoneType) { + //do nothing. + return; + } + ressetHydrophoneType=true; + hSens.getValueFactory().setValue(Double.valueOf(DefaultHydrophone.values()[defaultHydro.getSelectionModel().getSelectedIndex()-1].getSens())); + preampGain.getValueFactory().setValue(Double.valueOf(DefaultHydrophone.values()[defaultHydro.getSelectionModel().getSelectedIndex()-1].getGain())); + ressetHydrophoneType=false; + }); + + mainControls.add(defaultHydro, 1, gridy); + + gridy++; + mainControls.add(recieverSensLabel = new Label(""), 0, gridy); + recieverSensLabel.setAlignment(Pos.CENTER_LEFT); + hSens = new PamSpinner(-Double.MAX_VALUE, Double.MAX_VALUE, -200., 1.); + hSens.setEditable(true); + + hSens.valueProperty().addListener((obs, oldval, newVal)->{ + if (ressetHydrophoneType) return; + ressetHydrophoneType = true; //make sure we don't trigger anything when resetting the combo box + defaultHydro.getSelectionModel().select(0); + ressetHydrophoneType= false; + }); + + mainControls.add(hSens, 1, gridy); + mainControls.add(dBSensLabel = new Label(""), 2, gridy); + + + gridy++; + Label preAmpLabel = new Label("Preamplifier gain"); + mainControls.add(preAmpLabel, 0, gridy); + preAmpLabel.setAlignment(Pos.CENTER_LEFT); + preampGain =new PamSpinner(-Double.MAX_VALUE, Double.MAX_VALUE, 0., 1.); + preampGain.valueProperty().addListener((obs, oldval, newVal)->{ + if (ressetHydrophoneType) return; + ressetHydrophoneType = true;//make sure we don't trigger anything when resetting the combo box + defaultHydro.getSelectionModel().select(0); + ressetHydrophoneType= false; + }); + preampGain.setEditable(true); + + + mainControls.add(preampGain, 1, gridy); + mainControls.add(new Label("dB"), 2, gridy); + + ColumnConstraints col1 = new ColumnConstraints(); + col1.setMinWidth(COLUMN_0_WIDTH); + col1.setMaxWidth(COLUMN_0_WIDTH); + mainControls.getColumnConstraints().addAll(col1); + + setGeneralInfoLabelText(); + + + return mainControls; + } + + /** + * Create the pane to allow users to change the position of hydrophones + */ + private Pane createPositionPane(){ + + double sectionPadding=15; + + + PamVBox mainControls=new PamVBox(); + mainControls.setSpacing(5); + + Insets h; + + Label nameLabel=new Label("Array Name"); + nameLabel.setPadding(new Insets(5,0,0,0)); + nameField=new TextField(); + + //parent array. + Label parentArrayLabel=new Label("Parent Streamer"); + parentArrayLabel.setPadding(new Insets(sectionPadding,0,0,0)); + + PamGridPane positionPane = new PamGridPane(); + positionPane.setHgap(5); + positionPane.setVgap(5); + + ColumnConstraints rc = new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, MAX_TEXTFIELD_WIDTH); + //this sets all text fields to the correct width - but of naff hack but what grid pane needs to work. + for (int i=1; i<5; i++) { + positionPane.getColumnConstraints().add(rc); + } + + double maxWidth =10; + + xPos=new TextField(); + xPos.textProperty().addListener((obsVal, oldVal, newVal)->{ + notifySettingsListeners(); + }); + xPos.setMaxWidth(maxWidth); + addTextValidator(xPos, "x position", validator); + + yPos=new TextField(); + yPos.setMaxWidth(maxWidth); + addTextValidator(yPos, "y position", validator); + yPos.textProperty().addListener((obsVal, oldVal, newVal)->{ + notifySettingsListeners(); + }); + + zPos=new TextField(); + zPos.setMaxWidth(maxWidth); + zPos.textProperty().addListener((obsVal, oldVal, newVal)->{ + notifySettingsListeners(); + }); + + addTextValidator(zPos, "z position", validator); + depthLabel = new Label("Depth"); + depthLabel.setAlignment(Pos.CENTER); + + xPosErr=new TextField(); + xPosErr.setMaxWidth(50); + addTextValidator(xPosErr, "x error",validator); + yPosErr=new TextField(); + yPosErr.setMaxWidth(50); + addTextValidator(yPosErr, "y error",validator); + zPosErr=new TextField(); + zPosErr.setMaxWidth(50); + depthLabel2 = new Label(""); //changes with air or water mode. + depthLabel2.setAlignment(Pos.CENTER); + addTextValidator(zPosErr, "z error", validator); + + int col=0; + int row =0; + + Label xLabel = new Label("x"); + xLabel.setAlignment(Pos.CENTER); + + Label yLabel = new Label("y"); + yLabel.setAlignment(Pos.CENTER); + + col=1; + positionPane.add(xLabel, col++, row); + positionPane.add(yLabel, col++, row); + positionPane.add(depthLabel, col++, row); + + col=0; + row++; + + Label positionLabel = new Label("Position"); + positionPane.add(positionLabel, col++, row); + positionPane.add(xPos, col++, row); + positionPane.add(yPos, col++, row); + positionPane.add(zPos, col++, row); + positionPane.add(new Label("(m)"), col++, row); + + col=0; + row++; + + Label errLabel = new Label("Error"); + positionPane.add(errLabel, col++, row); + positionPane.add(xPosErr, col++, row); + positionPane.add(yPosErr, col++, row); + positionPane.add(zPosErr, col++, row); + positionPane.add(new Label("(m)"), col++, row); + +// positionPane.add(new Label("\u00B1"), col, 2); +// positionPane.add(xPosErr, col, 3); +// positionPane.add(new Label("m (right of streamer)"), col, 5); + + col++; + +// Label yLabel = new Label("y"); +// yLabel.setAlignment(Pos.CENTER); +// positionPane.add(yLabel, col, 0); +// positionPane.add(yPos, col, 1); +// positionPane.add(new Label("\u00B1"), col, 2); +// positionPane.add(yPosErr, col, 3); +// positionPane.add(new Label("m (ahead of streamer)"), col, 4); +// col++; +// +// +// positionPane.add(depthLabel, col, 0); +// positionPane.add(zPos, col, 1); +// positionPane.add(new Label("\u00B1"), col, 2); +// positionPane.add(zPosErr, col, 3); +// positionPane.add(depthLabel2, col, 4); + + // ColumnConstraints col1 = new ColumnConstraints(); + // col1.setHgrow(Priority.ALWAYS); + // positionPane.getColumnConstraints().add(col1); + + // Label positionLabel = new Label("Coordinates"); + // PamGuiManagerFX.titleFont2style(positionLabel); + + mainControls.getChildren().addAll(positionPane); + + ColumnConstraints col1 = new ColumnConstraints(); + col1.setMinWidth(COLUMN_0_WIDTH); + col1.setMaxWidth(COLUMN_0_WIDTH); + positionPane.getColumnConstraints().addAll(col1); + + + setCoordsText(); + + return mainControls; + + } + + + + /** + * Creates a text filed and adds a validator to check that the input is OK. + * @return + */ + protected static void addTextValidator(TextField userTextField, String description, Validator validator) { + //userTextField.setPrefColumnCount(8); + + validator.createCheck() + .dependsOn(description, userTextField.textProperty()) + .withMethod(c -> { + String posVal = c.get(description); + + /** + * Ok, this is weird. So if the c.error is called then somehow it messes up + * the sizing of the pane i.e. it does not resize.. + */ + + try { + if (posVal.isEmpty() || Double.valueOf(posVal)==null) { + c.error("The input for " + description + " is invalid"); + } + } + catch (Exception e) { + c.error("The input for " + description + " is invalid"); + } + }) + .decorates(userTextField).immediate(); + } + + + + @Override + public void setParams(Hydrophone hydrophone) { + + //parent array stuff. + + //iD.setText(String.format("%d", hydrophone.getID())); + + streamers.getItems().clear(); + + //set thre text values for the recieevrs. + setGeneralInfoLabelText(); + if (currentArray != null) { + Streamer s; + for (int i = 0; i < currentArray.getNumStreamers(); i++) { + s = currentArray.getStreamer(i); + streamers.getItems().add(String.format("Streamer %d, x=%3.1f", i, s.getX())); + } + } + if (hydrophone.getStreamerId() < currentArray.getNumStreamers()) { + streamers.getSelectionModel().select(hydrophone.getStreamerId()); + } + + //hydrophone stuff + hSens.getValueFactory().setValue(hydrophone.getSensitivity()-PamController.getInstance().getGlobalMediumManager().getdBSensOffset()); + preampGain.getValueFactory().setValue(hydrophone.getPreampGain()); + + double zCoeff = PamController.getInstance().getGlobalMediumManager().getZCoeff(); + setCoordsText(); + + interpPane.setSelection(currentArray.getHydrophoneInterpolation()); + + xPos.setText(Double.toString(hydrophone.getX())); + yPos.setText(Double.toString(hydrophone.getY())); + zPos.setText(Double.toString(zCoeff*hydrophone.getZ())); + xPosErr.setText(Double.toString(hydrophone.getdX())); + yPosErr.setText(Double.toString(hydrophone.getdY())); + zPosErr.setText(Double.toString(hydrophone.getdZ())); + + } + + + @Override + public Hydrophone getParams(Hydrophone hydrophone) { + double zCoeff = PamController.getInstance().getGlobalMediumManager().getZCoeff(); + + try { + //hydrophone.setID(Integer.valueOf(iD.getText())); + //hydrophone.setType(type.getText()); + hydrophone.setStreamerId(streamers.getSelectionModel().getSelectedIndex()); + hydrophone.setSensitivity(hSens.getValue()+PamController.getInstance().getGlobalMediumManager().getdBSensOffset()); + hydrophone.setPreampGain(preampGain.getValue()); +// double[] bw = new double[2]; + // bw[0] = Double.valueOf(bandwidth0.getText()); + // bw[1] = Double.valueOf(bandwidth1.getText()); + // hydrophone.setBandwidth(bw); + + hydrophone.setX(Double.valueOf(xPos.getText())); + hydrophone.setY(Double.valueOf(yPos.getText())); + hydrophone.setZ(zCoeff*Double.valueOf(zPos.getText())); + hydrophone.setdX(Double.valueOf(xPosErr.getText())); + hydrophone.setdY(Double.valueOf(yPosErr.getText())); + hydrophone.setdZ(Double.valueOf(zPosErr.getText())); + + int hi = interpPane.getSelection(); + if (hi >= 0) { + this.currentArray.setHydrophoneInterpolation(interpPane.getSelectedInterpType()); + } + } + catch (Exception Ex) { + System.err.println("There is a problem with one of the parameters in the hydrophone panel"); + return null; + } + return hydrophone; + } + + /** + * Set the current array associated with the hydrophone. + * @param currentArray - the current array. + */ + public void setCurrentArray(PamArray currentArray) { + this.currentArray= currentArray; + + } + + public void setRecieverLabels() { + setGeneralInfoLabelText(); + + } + + + + + + +} diff --git a/src/Array/layoutFX/HydrophonesPane.java b/src/Array/layoutFX/HydrophonesPane.java new file mode 100644 index 00000000..4d574fa4 --- /dev/null +++ b/src/Array/layoutFX/HydrophonesPane.java @@ -0,0 +1,412 @@ +package Array.layoutFX; + +import java.util.ArrayList; +import java.util.List; + +import Array.Hydrophone; +import Array.PamArray; +import PamController.PamController; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Dialog; +import javafx.scene.control.TableCell; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.util.Callback; +import javafx.scene.control.TableColumn; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.util.converter.IntegerStringConverter; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamColorsFX; +import pamViewFX.fxNodes.flipPane.PamFlipPane; +import pamViewFX.fxNodes.table.TableSettingsPane; + +/** + * Table which allows users to add and edit hydrophones. + * + * @author Jamie Macaulay + * + */ +public class HydrophonesPane extends PamBorderPane { + + + static final double defaultx = 0.; + static final double defaulty = 0.; + static final double defaultz = 0.; + static final double defaultxErr = 0.; + static final double defaultyErr = 0.; + static final double defaultzErr = 0.; + static final String defaulttype = "Unknown"; + static final double defaultsensitivity = -201; + + /** + * Reference to the current array + */ + protected PamArray currentArray; + + /** + * The current hydrophone data. + */ + private HydrophoneProperty currentHydrophoneData; + + + /** + * A list of all the current hydrophones. + */ + ObservableList hydrophoneList = FXCollections.observableArrayList(); + + + /** + * The hydrophone array table. + */ + private HydrophoneTable tableArrayPane; + + private PamFlipPane pamFlipePane; + + /** + * Settings pane for a single hydrophone. + */ + private HydrophoneSettingsPane hydrophonePane = new HydrophoneSettingsPane(); + + /** + * A list of listeners which are called whenever a hydrophone is added removed or changed. + */ + public ArrayList hydrophoneChangeListeners = new ArrayList(); + + public HydrophonesPane() { + + tableArrayPane = new HydrophoneTable(hydrophoneList); + + tableArrayPane.setPadding(new Insets(5,5,5,5)); + + pamFlipePane = new PamFlipPane(); + pamFlipePane.getAdvLabel().setText(PamController.getInstance().getGlobalMediumManager().getRecieverString()); + // pamFlipePane.minWidthProperty().bind(this.widthProperty()); + // pamFlipePane.setStyle("-fx-background-color: green;"); + + + ((Pane) hydrophonePane.getContentNode()).setPadding(new Insets(5,5,5,15)); + + pamFlipePane.setAdvPaneContent(hydrophonePane.getContentNode()); + pamFlipePane.setFrontContent(tableArrayPane); + + pamFlipePane.getFront().setPadding(new Insets(5,5,5,10)); + + pamFlipePane.backButtonProperty().addListener((obsval, oldVal, newVal)->{ + + // System.out.println("Hello back button pressed: " + newVal.intValue()); + //the flip pane + if (newVal.intValue()==PamFlipPane.OK_BACK_BUTTON) { + + Hydrophone hydro = hydrophonePane.getParams(currentHydrophoneData.getHydrophone()); + + if (hydro==null) { + //the warning dialog is shown in the streamer settings pane + return; + } + + // System.out.println("Hydro: " + currentHydrophoneData.getX().get()+ " " + currentHydrophoneData.getY().get() + " " + currentHydrophoneData.getZ().get() + " ID: " +hydro.getID()); + // System.out.println("Hydro err: " + currentHydrophoneData.getXErr().get()+ " " + currentHydrophoneData.getYErr().get() + " " + currentHydrophoneData.getZErr().get()); + + currentHydrophoneData.setHydrophone(hydro); + + notifyHydrophoneListeners(currentHydrophoneData); + + //need to refresh table to show symbol. + tableArrayPane.getTableView().refresh(); + // + // System.out.println("Table size: " + tableArrayPane.getTableView().getItems().size()); + // for (int i=0; i { + + + /** + * The z table + */ + private TableColumn z; + + public HydrophoneTable(ObservableList hydrophoneData) { + super(hydrophoneData); + + z = new TableColumn("depth"); + z.setCellValueFactory(cellData -> cellData.getValue().getZ().multiply(PamController.getInstance().getGlobalMediumManager().getZCoeff())); + z.setEditable(true); + + //need to set up all the rows. + TableColumn hydroID = new TableColumn("ID"); + hydroID.setCellValueFactory(cellData -> cellData.getValue().getID().asObject()); + hydroID.setEditable(false); + + // Default cell factory provides text field for editing and converts text in text field to int. + Callback, TableCell> defaultCellFactory = + TextFieldTableCell.forTableColumn(new IntegerStringConverter()); + + // Cell factory implementation that uses default cell factory above, and augments the implementation + // by updating the value of the looked-up color cell-selection-color for the cell when the item changes: + Callback, TableCell> cellFactory = col -> { + TableCell cell = defaultCellFactory.call(col); + cell.itemProperty().addListener((obs, oldValue, newValue) -> { +// System.out.println("Hello set colour: " + newValue); + if (newValue == null) { + cell.setStyle("cell-selection-color: -fx-selection-bar ;"); + } else { + Color color = createColor(newValue.intValue()); + String formattedColor = formatColor(color); +// cell.setStyle("cell-selection-color: "+ formattedColor + " ;"); + cell.setStyle("-fx-background: "+ formattedColor + " ;"); + cell.setStyle("-fx-background-color: "+ formattedColor + " ;"); +// System.out.println("Hello set style: " + formattedColor); + } + }); + return cell; + }; + + hydroID.setCellFactory(cellFactory); + + + TableColumn x = new TableColumn("x"); + x.setCellValueFactory(cellData -> cellData.getValue().getX()); + x.setEditable(true); + + TableColumn y = new TableColumn("y"); + y.setCellValueFactory(cellData -> cellData.getValue().getY()); + y.setEditable(true); + + + TableColumn posColumn=new TableColumn("Position (m)"); + posColumn.getColumns().addAll(x, y, z); + + TableColumn xErr = new TableColumn("x"); + xErr.setCellValueFactory(cellData -> cellData.getValue().getXErr()); + xErr.setEditable(true); + + TableColumn yErr = new TableColumn("y"); + yErr.setCellValueFactory(cellData -> cellData.getValue().getYErr()); + yErr.setEditable(true); + + TableColumn zErr = new TableColumn("z"); + zErr.setCellValueFactory(cellData -> cellData.getValue().getZErr()); + zErr.setEditable(true); + + TableColumn errorColumn=new TableColumn("Errors (m)"); + errorColumn.getColumns().addAll(xErr, yErr, zErr); + + getTableView().getColumns().addAll(hydroID, posColumn, errorColumn); + + } + + + // Create color based on int value. Just use value as hue, full saturation and brightness: + private Color createColor(int i) { + //get channel colour and add a bit of transparancy to make less abnoxious + return PamColorsFX.getInstance().getChannelColor(i).deriveColor(1, 1, 1, 0.5); + +// return Color.hsb(x, 1.0, 1.0); + } + + // Format color as string for CSS (#rrggbb format, values in hex). + private String formatColor(Color c) { + int r = (int) (255 * c.getRed()); + int g = (int) (255 * c.getGreen()); + int b = (int) (255 * c.getBlue()); + return String.format("#%02x%02x%02x", r, g, b); + } + + @Override + public void dialogClosed(HydrophoneProperty data) { + System.out.println("Get hydrophone paramters"); + Hydrophone hydro = hydrophonePane.getParams(data.getHydrophone()); + data.setHydrophone(hydro); + } + + @Override + public Dialog createSettingsDialog(HydrophoneProperty data) { + //we do not use dialogs here- sliding pane instead. + // setClassifierPane(data); + pamFlipePane.flipToBack(); + return null; + } + + @Override + public void editData(HydrophoneProperty data){ + // setClassifierPane(data); + + pamFlipePane.getAdvLabel().setText("Hydrophone " + data.getID().get() + " Settings"); + + hydrophonePane.setCurrentArray(currentArray); + hydrophonePane.setParams(data.getHydrophone()); + + currentHydrophoneData = data; + + pamFlipePane.flipToBack(); + } + + + private PamArray getCurrentArray() { + return currentArray; + } + + /** + * Get the button which closes the hiding pane. + * @return button which closes the hiding pane. + */ + public Button getFlipPaneCloseButton() { + return pamFlipePane.getBackButton(); + } + + @Override + public void createNewData(){ + HydrophoneProperty hydrophone = createDefaultHydrophoneProperty(hydrophoneList.size()); + //create a new classifier. + hydrophoneList.add(hydrophone); + notifyHydrophoneListeners(hydrophone); + } + + + @Override + public void deleteData(HydrophoneProperty data){ + super.deleteData(data); + //the ID number for hydrophone sis actually important for where they are in the list. Bit a legacy issue but no + //point in messes everything up to fix. So, when a hydrophone is deleted must update all the ID numbers. + + updateIDNumbers(); + + notifyHydrophoneListeners(data); + } + + /** + * Update the ID numbers. + */ + private void updateIDNumbers() { + for (int i=0; i getZColumn() { + return z; + } + + /** + * Get the current streamers. + * @return the current streamers. + */ + public ObservableList getHydrophones() { + return getData(); + } + + + } + + public void setParams(PamArray currentArray) { + this.currentArray=currentArray; + + tableArrayPane.getHydrophones().clear(); + + for (int i=0; i getHydrophoneList() { + return hydrophoneList; + } + + public void setHydrophoneList(ObservableList hydrophoneList) { + this.hydrophoneList = hydrophoneList; + } + + public void setRecieverLabels() { + tableArrayPane.getZColumn().setText(PamController.getInstance().getGlobalMediumManager().getZString()); + hydrophonePane.setRecieverLabels(); + } + + /** + * Add a listener which is called whenever a hydrophone is added, removed or changed. + * @param e - the listener to add + */ + public void addStreamerListener(ArrayChangeListener e) { + hydrophoneChangeListeners.add(e); + + } + + /** + * Select the current hydrophone in table. + */ + public void selectHydrophone(Hydrophone hydrophone) { + //select the current hydrophone in the table + tableArrayPane.getTableView().getSelectionModel().select(hydrophone.getID()); + } + + public void setCurrentArray(PamArray currentArray) { + this.currentArray=currentArray; + + } + + /** + * Get the hydrophone interpolation. Note that this is stored in the + * currentArray because the interpolator must be the same for all hydrophones. + * + * @return the inteprolation selection. + */ + public int getHydrophoneInterp() { + return currentArray.getHydrophoneInterpolation(); + } + +} diff --git a/src/Array/layoutFX/InterpChoicePane.java b/src/Array/layoutFX/InterpChoicePane.java new file mode 100644 index 00000000..b5e7613b --- /dev/null +++ b/src/Array/layoutFX/InterpChoicePane.java @@ -0,0 +1,104 @@ +package Array.layoutFX; + +import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; +import javafx.scene.control.ChoiceBox; +import javafx.util.StringConverter; + + + +/** + * Choice box which allows selection of interpolation options. + * + * @author Jamie Macaulay + * + */ +public class InterpChoicePane extends InterpSettingsPane { + + /** + * Interp choice box. + */ + private ChoiceBox interpChoiceBox; + + public InterpChoicePane() { + + interpChoiceBox = new ChoiceBox(); + interpChoiceBox.getItems().addAll(interpChoice); + interpChoiceBox.setMaxWidth(Double.MAX_VALUE); + + interpChoiceBox.setConverter(new StringConverter<>() { + @Override + public String toString(Integer item) { + if (item ==null) return "null"; + return getInterpString(item); + } + + @Override + public Integer fromString(String unused) { + throw new UnsupportedOperationException(); + } + }); + + this.setCenter(interpChoiceBox); + + } + + public void setSelection(int option) { + + System.out.println("Select interp option: " + option); + + interpChoiceBox.getSelectionModel().select(Integer.valueOf(option)); + + // useLatest.setSelected(option == PamArray.ORIGIN_USE_LATEST); + // useInterpolate.setSelected(option == PamArray.ORIGIN_INTERPOLATE); + // usePrevious.setSelected(option == PamArray.ORIGIN_USE_PRECEEDING); + } + + public int getSelection() { + int sel = getSelectedInterpType(); + if (((1< { + + Pane mainPane; + + private TextField speedOfSound; + + private TextField speedOfSoundError; + + private PamValidator validator; + + + public PropogationPane() { + super(null); + validator= new PamValidator(); + mainPane = createEnvironmentPane(); + } + + /** + * Create the pane for setting propogation conditions. + * @return the pane with controls to change environmental variables. + */ + private Pane createEnvironmentPane() { + speedOfSound = new TextField(); + speedOfSound.setPrefColumnCount(6); + + validator.createCheck() + .dependsOn("speed_of_sound", speedOfSound.textProperty()) + .withMethod(c -> { + try { + String posVal = c.get("speed_of_sound"); + if (posVal.isEmpty() || Double.valueOf(posVal)==null) { + c.error("The input for speed of sound is invalid"); + } + } + catch (Exception e) { + c.error("The input for speed of sound is invalid"); + } + }) + .decorates(speedOfSound).immediate(); + + + speedOfSoundError = new TextField(); + speedOfSoundError.setPrefColumnCount(4); + + validator.createCheck() + .dependsOn("speed_of_sound_error", speedOfSoundError.textProperty()) + .withMethod(c -> { + try { + String posVal = c.get("speed_of_sound_error"); + if (posVal.isEmpty() || Double.valueOf(posVal)==null) { + c.error("The input for speed of sound error is invalid"); + } + } + catch (Exception e) { + c.error("The input for speed of sound is invalid"); + } + }) + .decorates(speedOfSoundError).immediate(); + + PamHBox hBox = new PamHBox(); + hBox.setSpacing(5); + hBox.setAlignment(Pos.CENTER); + + + hBox.getChildren().addAll(new Label("Speed of sound"), speedOfSound, new Label("\u00B1"), speedOfSoundError, new Label("m/s")); + + return hBox; + } + + @Override + public PamArray getParams(PamArray currParams) { + if (validator.containsErrors()) return null; + + currParams.setSpeedOfSound(Double.valueOf(speedOfSound.getText())); + currParams.setSpeedOfSoundError(Double.valueOf(speedOfSoundError.getText())); + return currParams; + } + + @Override + public void setParams(PamArray input) { + //set the current params. + speedOfSound.setText(String.valueOf(input.getSpeedOfSound())); + speedOfSoundError.setText(String.valueOf(input.getSpeedOfSoundError())); + } + + @Override + public String getName() { + return "Propogation pane"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + + +} diff --git a/src/Array/layoutFX/SensorSourcePane.java b/src/Array/layoutFX/SensorSourcePane.java new file mode 100644 index 00000000..9c6f61f0 --- /dev/null +++ b/src/Array/layoutFX/SensorSourcePane.java @@ -0,0 +1,162 @@ +package Array.layoutFX; + +import java.util.ArrayList; + +import Array.sensors.ArrayParameterType; +import Array.sensors.ArraySensorDataBlock; +import Array.sensors.ArraySensorDataUnit; +import Array.sensors.ArraySensorFieldType; +import PamController.PamController; +import PamguardMVC.PamDataBlock; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Tooltip; + +public class SensorSourcePane { + + private ArraySensorFieldType sensorType; + + private ComboBox sensorDropDown; + + private ArrayList currentBlocks; + + private boolean defaultOption; + + private boolean fixedOption; + + private int nSpecials; + + private ArrayParameterType[] specialTypes = new ArrayParameterType[2]; + + public SensorSourcePane(ArraySensorFieldType sensorType, boolean fixedOption, boolean defaultOption) { + this.sensorType = sensorType; + this.fixedOption = fixedOption; + this.defaultOption = defaultOption; + sensorDropDown = new ComboBox<>(); + fillDropDown(); + sensorDropDown.setTooltip(new Tooltip("Sensor updates for " + sensorType.toString())); + } + + /** + * Get the type of sensor. + * @return the type of sensor. + */ + public ArraySensorFieldType getSensorType() { + return sensorType; + } + + public void setOnAction(EventHandler e) { + sensorDropDown.setOnAction(e); + } + + public void fillDropDown() { + currentBlocks = getDataBlocks(); + sensorDropDown.getItems().clear(); + nSpecials = 0; + if (fixedOption) { + sensorDropDown.getItems().add("Fixed Value"); + specialTypes[nSpecials++] = ArrayParameterType.FIXED; + } + if (defaultOption) { + sensorDropDown.getItems().add("Default value"); + specialTypes[nSpecials++] = ArrayParameterType.DEFAULT; + } + for (PamDataBlock aBlock : currentBlocks) { + sensorDropDown.getItems().add(aBlock.getDataName()); + } + } + + /** + * Set the type of parameter being used, fixed, default or sensor + * @param paramType + */ + public void setParameterType(ArrayParameterType paramType) { + for (int i = 0; i < nSpecials; i++) { + if (paramType == specialTypes[i]) { + sensorDropDown.getSelectionModel().select(i); + return; + } + } + } + + /** + * Get the type of parameter being used, fixed, default or sensor + * @return + */ + public ArrayParameterType getParameterType() { + int ind = sensorDropDown.getSelectionModel().getSelectedIndex(); + if (ind < 0) { + return null; + } + if (ind < nSpecials) { + return specialTypes[ind]; + } + return ArrayParameterType.SENSOR; + } + + /** + * Set the selected datablock for sensor data. Before calling this, you should call + * fillDropDown to make sure list of blocks is up to date. + * @param aDataBlock datablock to select + * @return true if that block was selected OK, i.e. it was in the list. + */ + public boolean setDataBlock(PamDataBlock aDataBlock) { + if (currentBlocks == null) { + return false; + } + int ind = currentBlocks.indexOf(aDataBlock); + if (ind < 0) { + return false; + } + sensorDropDown.getSelectionModel().select(ind+nSpecials); // offset by 1 to allow for null option. + return true; + } + + /** + * + * @return Currently selected datablock for this sensor (can be null) + */ + public PamDataBlock getDataBlock() { + int ind = sensorDropDown.getSelectionModel().getSelectedIndex(); + if (ind <= 0 || currentBlocks == null) { + return null; + } + else { + return currentBlocks.get(ind-nSpecials); + } + } + + /** + * Get the sensor pane. + * @return the sensor pane + */ + public Node getPane() { + return sensorDropDown; + } + + /** + * Get a list of datablocks that might be able to provide info on this + * sensor field type + * @return + */ + private ArrayList getDataBlocks() { + ArrayList allDataBlocks = PamController.getInstance().getDataBlocks(ArraySensorDataUnit.class, true); + ArrayList sensBlocks = new ArrayList<>(); + if (allDataBlocks == null) { + return sensBlocks; + } + // go through take out the ones that support this sensor. + for (PamDataBlock aBlock : allDataBlocks) { + if (aBlock instanceof ArraySensorDataBlock == false) { + continue; + } + ArraySensorDataBlock sensBlock = (ArraySensorDataBlock) aBlock; + if (sensBlock.hasSensorField(sensorType)) { + sensBlocks.add(aBlock); + } + } + return sensBlocks; + } +} diff --git a/src/Array/layoutFX/StreamerProperty.java b/src/Array/layoutFX/StreamerProperty.java new file mode 100644 index 00000000..eb82adc5 --- /dev/null +++ b/src/Array/layoutFX/StreamerProperty.java @@ -0,0 +1,121 @@ +package Array.layoutFX; + +import Array.Streamer; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; + + +/** + * Property class for a Streamer. Create property bindings for certain Streamer values which allows + * for much easier integration into UI components in JavaFX. + * + * @author Jamie Macaulay + * + */ +public class StreamerProperty { + + /** + * The simple name property. + */ + private SimpleStringProperty name = new SimpleStringProperty(); + + private SimpleStringProperty reference = new SimpleStringProperty(); + + private SimpleStringProperty origin = new SimpleStringProperty(); + + /** + * Get the x, y and z. + */ + private SimpleDoubleProperty x = new SimpleDoubleProperty(); + + private SimpleDoubleProperty y = new SimpleDoubleProperty(); + + private SimpleDoubleProperty z = new SimpleDoubleProperty(); + + + private Streamer streamer; + + private SimpleIntegerProperty streamerIDProperty = new SimpleIntegerProperty(); + + public StreamerProperty(Streamer streamer) { + setStreamer( streamer); + } + + public void setStreamer(Streamer streamer) { + this.streamer = streamer; + name.setValue(streamer.getStreamerName()); + x.setValue(streamer.getX()); + y.setValue(streamer.getY()); + z.setValue(streamer.getZ()); + streamerIDProperty.setValue(streamer.getStreamerIndex()); + reference.setValue(streamer.getHydrophoneLocator() != null ? streamer.getHydrophoneLocator().getName() : "null"); + origin.setValue(streamer.getHydrophoneOrigin() != null ? streamer.getHydrophoneOrigin().getName() : "null"); + + } + + public SimpleStringProperty getName() { + return name; + } + + public void setName(SimpleStringProperty name) { + this.name = name; + } + + public SimpleDoubleProperty getX() { + return x; + } + + public void setX(SimpleDoubleProperty x) { + this.x = x; + } + + public SimpleDoubleProperty getY() { + return y; + } + + public void setY(SimpleDoubleProperty y) { + this.y = y; + } + + public SimpleDoubleProperty getZ() { + return z; + } + + public void setZ(SimpleDoubleProperty z) { + this.z = z; + } + + + public Streamer getStreamer() { + return streamer; + } + + /** + * Get the index property of the streamer. + * @return the streamer index. + */ + public SimpleIntegerProperty getID() { + return streamerIDProperty; + } + + /** + * Get the reference property. + * @return the reference property. + */ + public SimpleStringProperty getHydrophineLocator() { + return reference; + } + + + /** + * Get the origin string property. + * @return the origin string property. + */ + public SimpleStringProperty getHydrophoneOrigin() { + return origin; + } + + + +} diff --git a/src/Array/layoutFX/StreamerSettingsPane.java b/src/Array/layoutFX/StreamerSettingsPane.java new file mode 100644 index 00000000..f2880f52 --- /dev/null +++ b/src/Array/layoutFX/StreamerSettingsPane.java @@ -0,0 +1,752 @@ +package Array.layoutFX; + +import org.controlsfx.control.PopOver; + +import Array.HydrophoneLocator; +import Array.HydrophoneLocators; +import Array.PamArray; +import Array.Streamer; +import Array.sensors.ArrayParameterType; +import Array.sensors.ArraySensorFieldType; +import Array.streamerOrigin.HydrophoneOriginMethod; +import Array.streamerOrigin.HydrophoneOriginMethods; +import Array.streamerOrigin.HydrophoneOriginSystem; +import Array.streamerOrigin.OriginDialogComponent; +import Array.streamerOrigin.OriginSettings; +import PamController.PamController; +import PamController.SettingsPane; +import PamUtils.LatLong; +import PamguardMVC.PamDataBlock; +import javafx.geometry.Pos; +import javafx.scene.Node; +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.PamVBox; +import pamViewFX.validator.PamValidator; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import net.synedra.validatorfx.Validator; + + +/** + * A JavaFX settings pane for a streamer. + * + * @author Jamie Macaulay + * + */ +public class StreamerSettingsPane extends SettingsPane { + + private final static double MAX_TEXTFIELD_WIDTH = 80; + + + public PamBorderPane mainPane; + + /** + * Combo Box which shows which origin methods are available. + */ + private ComboBox originMethod; + + /** + * The origin pane; + */ + private PamBorderPane originPane; + + /** + * The default streamer + */ + public Streamer defaultStreamer; + + + /** + * The current array + */ + private PamArray currentArray; + + /** + * The current origin methods + */ + private HydrophoneOriginMethod currentOriginMethod; + + /* + * The current origin method pane. + */ + private Pane currentOriginComponent; + + /** + * Interpolation panel + */ + private InterpChoicePane interpPane; + + + private TextField xPos; + + + private Validator validator = new PamValidator(); + + + private TextField yPos; + + + private TextField zPos; + + + private TextField zPosErr; + + + private TextField xPosErr; + + + private Label depthLabel; + + + private TextField yPosErr; + + + private Label depthLabel2; + + + private TextField heading; + + + private TextField roll; + + + private TextField pitch; + + + private ComboBox localiserMethod; + + + private SensorSourcePane[] sensorComponents; + + + private Label depthSensorLabel; + + + /** + * Button for extra origin parameters. + */ + private PamButton originButton; + + + + public StreamerSettingsPane() { + super(null); + + mainPane = new PamBorderPane(); + mainPane.setCenter(getStreamerPane()); + + } + + /** + * Create the streamer pane + * @return get the pane. + */ + private Pane getStreamerPane(){ + + String reciever = PamController.getInstance().getGlobalMediumManager().getRecieverString(); + + Label label = new Label("Geo-reference Position"); + PamGuiManagerFX.titleFont2style(label); + + //holds advanced setings for new origin methods. + originPane = new PamBorderPane(); + PopOver popOver = new PopOver(); + popOver.setContentNode(originPane); + + originMethod = new ComboBox(); + originButton = new PamButton(); + originButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-crosshairs-gps")); + originButton.setOnAction((a)->{ + popOver.show(originButton); + }); + originMethod.setMaxWidth(Double.MAX_VALUE); + + PamHBox originHolder = new PamHBox(); + originHolder.setSpacing(5); + originHolder.setAlignment(Pos.CENTER_LEFT); + originHolder.getChildren().addAll(originMethod,originButton); + originHolder.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(originMethod, Priority.ALWAYS); + + int n = HydrophoneOriginMethods.getInstance().getCount(); + for (int i = 0; i < n; i++) { + originMethod.getItems().add(HydrophoneOriginMethods.getInstance().getMethod(i)); + } + + Label hydroMovementLabel = new Label(reciever +" Movement Model"); + + //listener for when a new origin method is called. + originMethod.setOnAction((action)->{ + newOriginMethod(); + }); + + interpPane = new InterpChoicePane(); + Label inteprlabel = new Label("Interpolation"); + PamGuiManagerFX.titleFont2style(inteprlabel); + + PamHBox interpBox = new PamHBox(); + interpBox.setSpacing(5); + Label interpMethodLabel = new Label("Method"); + + Region spacer = new Region(); + spacer.prefWidthProperty().bind(originButton.widthProperty()); + interpBox.getChildren().addAll(interpMethodLabel, interpPane, spacer); + interpBox.setAlignment(Pos.CENTER_LEFT); + interpBox.setMaxWidth(Double.MAX_VALUE); + interpPane.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(interpPane, Priority.ALWAYS); + + //add all stuff to the holder + PamVBox holder = new PamVBox(); + holder.getChildren().addAll(label, originHolder, hydroMovementLabel, createLocatorPane(), inteprlabel, interpBox); + holder.setSpacing(5); + + + return holder; + + } + + /** + * Create the locator pane. + * @return the pane containing controls. + */ + public Pane createLocatorPane() { + + localiserMethod = new ComboBox<>(); + int n = HydrophoneLocators.getInstance().getCount(); + for (int i = 0; i < n; i++) { + localiserMethod.getItems().add(HydrophoneLocators.getInstance().getSystem(i)); + } + localiserMethod.setMaxWidth(Double.MAX_VALUE); + + PamHBox loclaiserMethodHolder = new PamHBox(); + loclaiserMethodHolder.setSpacing(5); + loclaiserMethodHolder.setAlignment(Pos.CENTER_LEFT); + Label spacer = new Label(); + spacer.prefWidthProperty().bind(originButton.widthProperty()); + loclaiserMethodHolder.getChildren().addAll(localiserMethod, spacer); + loclaiserMethodHolder.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(localiserMethod, Priority.ALWAYS); + + //hydrophone position and + PamGridPane positionPane = new PamGridPane(); + positionPane.setHgap(5); + positionPane.setVgap(5); + + ColumnConstraints rc = new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, MAX_TEXTFIELD_WIDTH); + + //Orientation pane. + //create data sources for sensors. + ArraySensorFieldType[] sensorFields = ArraySensorFieldType.values(); + sensorComponents = new SensorSourcePane[sensorFields.length]; + //EnableOrientation eo = new EnableOrientation(); + for (int i = 0; i < sensorFields.length; i++) { + sensorComponents[i] = new SensorSourcePane(sensorFields[i], true, sensorFields[i] != ArraySensorFieldType.HEIGHT); + sensorComponents[i].setOnAction((e)->{ + enableOrientationPane(); + }); + } + PamButton button = new PamButton("Sensors"); + button.setGraphic(PamGlyphDude.createPamIcon("mdi2c-compass-outline", PamGuiManagerFX.iconSize)); + + PopOver popOver = new PopOver(createSensorPane()); + popOver.setDetachable(true); + + button.setOnAction((a)->{ + popOver.show(button); + }); + + + //this sets all text fields to the correct width - but of naff hack but what grid pane needs to work. + for (int i=1; i<5; i++) { + positionPane.getColumnConstraints().add(rc); + } + + xPos=new TextField(); + xPos.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(xPos, "x position", validator); + yPos=new TextField(); + yPos.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(yPos, "y position", validator); + zPos=new TextField(); + zPos.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(zPos, "z position", validator); + + + depthLabel = new Label("Depth"); + depthLabel.setAlignment(Pos.CENTER); + + depthSensorLabel = new Label("Depth Sensor"); + depthSensorLabel.setAlignment(Pos.CENTER); + + + xPosErr=new TextField(); + xPosErr.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(xPosErr, "x error", validator); + yPosErr=new TextField(); + yPosErr.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(yPosErr, "y error", validator); + zPosErr=new TextField(); + zPosErr.setMaxWidth(MAX_TEXTFIELD_WIDTH); + depthLabel2 = new Label(""); //changes with air or water mode. + depthLabel2.setAlignment(Pos.CENTER); + HydrophoneSettingsPane.addTextValidator(zPosErr, "z error", validator); + + int col=0; + int row=0; + + + Label xLabel = new Label("x"); + xLabel.setAlignment(Pos.CENTER); + + Label yLabel = new Label("y"); + yLabel.setAlignment(Pos.CENTER); + + //Orientations + + String degsLab = LatLong.deg + " "; + + + col=1; + positionPane.add(xLabel, col++, row); + positionPane.add(yLabel, col++, row); + positionPane.add(depthLabel, col++, row); + col++; + positionPane.add(depthSensorLabel, col++, row); + + col=0; + row++; + + Label positionLabel = new Label("Position"); + positionPane.add(positionLabel, col++, row); + positionPane.add(xPos, col++, row); + positionPane.add(yPos, col++, row); + positionPane.add(zPos, col++, row); + positionPane.add(new Label("(m)"), col++, row); + positionPane.add(sensorComponents[ArraySensorFieldType.HEIGHT.ordinal()].getPane(), col++, row); + + col=0; + row++; + + Label errLabel = new Label("Error"); + positionPane.add(errLabel, col++, row); + positionPane.add(xPosErr, col++, row); + positionPane.add(yPosErr, col++, row); + positionPane.add(zPosErr, col++, row); + positionPane.add(new Label("(m)"), col++, row); + + //Orientation + col=1; + row++; + + Label headingLabel = new Label("Heading"); + headingLabel.setAlignment(Pos.CENTER); + + Label pitchLabel = new Label("Pitch"); + pitchLabel.setAlignment(Pos.CENTER); + + Label rolllabel = new Label("Roll"); + rolllabel.setAlignment(Pos.CENTER); + + + positionPane.add(headingLabel, col++, row); + positionPane.add(pitchLabel, col++, row); + positionPane.add(rolllabel, col++, row); + + row++; + + heading = new TextField(); + heading.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(heading, "heading", validator); + + pitch = new TextField(); + pitch.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(pitch, "pitch", validator); + + roll = new TextField(); + roll.setMaxWidth(MAX_TEXTFIELD_WIDTH); + HydrophoneSettingsPane.addTextValidator(roll, "roll", validator); + + col=0; + + Label orientation = new Label("Orientation"); + positionPane.add(orientation, col++, row); + positionPane.add(heading, col++, row); + positionPane.add(pitch, col++, row); + positionPane.add(roll, col++, row); + positionPane.add(new Label(degsLab), col++, row); + + + positionPane.add(button, col++, row); + + PamVBox holder= new PamVBox(); + holder.setSpacing(5); + holder.getChildren().addAll(loclaiserMethodHolder, positionPane); + + return holder; + } + + /** + * Enables or disables controls in the orientation pane. + */ + private void enableOrientationPane() { + for (int i=0; i=currentOriginMethod.getAllowedInterpolationMethods()) { + System.err.println("Streamer setParams: Origin interpolator value of " + currentArray.getOriginInterpolation() + " not allowed for origin method - setting to first allowed method:"); + interpPane.setSelection(0); + } + else { + interpPane.setSelection(currentArray.getOriginInterpolation()); + } + + System.out.println("Streamer setParams: selected interp: " + interpPane.getSelectedInterpType()); + + + ArraySensorFieldType[] sensorFields = ArraySensorFieldType.values(); + for (int i = 0; i < sensorFields.length; i++) { + ArrayParameterType fieldType = defaultStreamer.getOrientationTypes(sensorFields[i]); + String fieldDataBlock = defaultStreamer.getSensorDataBlocks(sensorFields[i]); + sensorComponents[i].setParameterType(fieldType); + if (fieldType == ArrayParameterType.SENSOR && fieldDataBlock != null) { + sensorComponents[i].setDataBlock(PamController.getInstance().getDataBlockByLongName(fieldDataBlock)); + } + } + + setRecieverLabels() ; + enableControls(); + + } + + private String orientation2Text(Double ang) { + if (ang == null) return String.valueOf(0.0); + else return String.format("%3.1f", ang); + } + + @Override + public String getName() { + return "Streamer Pane"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + + } + + public void setRecieverLabels() { + String recieverDepthString = PamController.getInstance().getGlobalMediumManager().getZString(); + + depthLabel.setText(recieverDepthString ); + depthSensorLabel.setText(recieverDepthString + " Sensor"); + + } + + private Double getDoubleValue(TextField textField) { + String txt = textField.getText(); + if (txt == null || txt.length() == 0) { + return null; + } + Double val; + try { + val = Double.valueOf(txt); + return val; + } + catch (NumberFormatException e) { + System.err.println("Invalid orientation information: " + txt); + return null; + } + } + + public void setCurrentArray(PamArray currentArray2) { + this.currentArray=currentArray2; + + } +} diff --git a/src/Array/layoutFX/StreamersPane.java b/src/Array/layoutFX/StreamersPane.java new file mode 100644 index 00000000..b5adf047 --- /dev/null +++ b/src/Array/layoutFX/StreamersPane.java @@ -0,0 +1,315 @@ +package Array.layoutFX; + +import java.util.ArrayList; + +import Array.PamArray; +import Array.Streamer; +import PamController.PamController; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Dialog; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.flipPane.PamFlipPane; +import pamViewFX.fxNodes.table.TableSettingsPane; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.Pane; +import javafx.geometry.Insets; + +/** + * A pane for setting up hydrophones. Note that this is entirely separate from PAMGuard so can be used in + * other projects. + * + * @author Jamie Macaulay + * + */ +public class StreamersPane extends PamBorderPane { + + BasicArrayTable tableArrayPane; + + ObservableList streamerData = FXCollections.observableArrayList(); + + /** + * The current hydrophone array + */ + private PamArray currentArray; + + /** + * The pam flip pane. + */ + private PamFlipPane pamFlipePane; + + /** + * The current streamer data. + */ + private StreamerProperty currentStreamerData; + + /** + * Settings pane for a single hydrophone. + */ + private StreamerSettingsPane streamerPane = new StreamerSettingsPane(); + + /** + * A list of listeners which are called whenever a streamer is added removed or changed. + */ + public ArrayList streamerChangeListeners = new ArrayList(); + + + public StreamersPane() { + + tableArrayPane = new BasicArrayTable(streamerData); + + tableArrayPane.setPadding(new Insets(5,5,5,5)); + this.setCenter(tableArrayPane); + + pamFlipePane = new PamFlipPane(); + pamFlipePane.getAdvLabel().setText("Streamer"); + pamFlipePane.setMaxWidth(Double.MAX_VALUE); + + ((Pane) streamerPane.getContentNode()).setPadding(new Insets(5,5,5,15)); + + pamFlipePane.setAdvPaneContent(streamerPane.getContentNode()); + pamFlipePane.setFrontContent(tableArrayPane); + + pamFlipePane.getFront().setPadding(new Insets(5,5,5,10)); + pamFlipePane.setAdvLabelEditable(true); + pamFlipePane.getPostAdvLabel().setText("Settings"); + + + pamFlipePane.backButtonProperty().addListener((obsval, oldVal, newVal)->{ + +// System.out.println("Hello back button pressed: " + newVal.intValue()); + + //the flip pane + if (newVal.intValue()==PamFlipPane.OK_BACK_BUTTON) { + + Streamer streamer = streamerPane.getParams(currentStreamerData.getStreamer()); + + if (streamer==null) { + //the warning dialog is shown in the streamer settings pane + return; + } + + streamer.setStreamerName(pamFlipePane.getAdvLabel().getText()); + + currentStreamerData.setStreamer(streamer); + + notifyStreamerListeners(currentStreamerData); +// System.out.println("Update streamer: " + tableArrayPane.getStreamers().indexOf(currentStreamerData) + " no. streamers: " + currentArray.getNumStreamers()); + + //need to refresh table to show symbol. + tableArrayPane.getTableView().refresh(); + + if (streamer != null) { + streamer.setupLocator(currentArray); + streamer.makeStreamerDataUnit(); + //update the streamer in the current array + } + } + }); + + this.setCenter(pamFlipePane); + + } + + /** + * Notify the streamer listeners of a change + * @param streamer - the changed streamer + */ + public void notifyStreamerListeners(StreamerProperty streamer) { + for (ArrayChangeListener listener: streamerChangeListeners) { + listener.arrayChanged(ArrayChangeListener.STREAMER_CHANGE, streamer); + } + } + + /** + * Class which extends TableSettingsPane and creates a sliding pane instead of a dialog when an item is added. + * @author Jamie Macaulay + * + */ + class BasicArrayTable extends TableSettingsPane { + + private TableColumn z; + + + public BasicArrayTable(ObservableList data) { + super(data); + //need to set up all the rows. + TableColumn streamerID = new TableColumn("ID"); + streamerID.setCellValueFactory(cellData -> cellData.getValue().getID()); + streamerID.setEditable(false); + + TableColumn name = new TableColumn("Name"); + name.setCellValueFactory(cellData -> cellData.getValue().getName()); + name.setEditable(true); + + + TableColumn x = new TableColumn("x"); + x.setCellValueFactory(cellData -> cellData.getValue().getX()); + x.setEditable(false); + + TableColumn y = new TableColumn("y"); + y.setCellValueFactory(cellData -> cellData.getValue().getY()); + y.setEditable(false); + + z = new TableColumn("depth"); + z.setCellValueFactory(cellData -> cellData.getValue().getZ().multiply(PamController.getInstance().getGlobalMediumManager().getZCoeff())); + z.setEditable(false); + + TableColumn posColumn=new TableColumn("Position (m)"); + posColumn.getColumns().addAll(x, y, z); + + TableColumn reference = new TableColumn("Reference"); + reference.setCellValueFactory(cellData -> cellData.getValue().getHydrophoneOrigin()); + reference.setEditable(true); + + TableColumn locator = new TableColumn("Locator"); + locator.setCellValueFactory(cellData -> cellData.getValue().getHydrophineLocator()); + locator.setEditable(true); + + TableColumn geoColumn=new TableColumn("Geo-reference"); + geoColumn.getColumns().addAll(reference, locator); + + + getTableView().getColumns().addAll(streamerID, name, posColumn, geoColumn); + + } + + /** + * Get the current streamers. + * @return the current streamers. + */ + public ObservableList getStreamers() { + return getData(); + } + + @Override + public void dialogClosed(StreamerProperty data) { + Streamer hydro = streamerPane.getParams(data.getStreamer()); + data.setStreamer(hydro); + } + + @Override + public Dialog createSettingsDialog(StreamerProperty data) { + //we do not use dialogs here- sliding pane instead. + // setClassifierPane(data); + // showFlipPane(true); + pamFlipePane.flipToBack(); + return null; + } + + @Override + public void editData(StreamerProperty data){ + //edit streamer data. + if (data.getName() == null){ + pamFlipePane.getAdvLabel().setText("Streamer " + data.getID().get()); + } + + streamerPane.setCurrentArray(currentArray); + streamerPane.setParams(data.getStreamer()); + + currentStreamerData = data; + + pamFlipePane.flipToBack(); + } + + @Override + public void createNewData(){ + StreamerProperty newStreamer = createDefaultStreamerProperty(); + //create a new classifier. + streamerData.add(newStreamer); + //add to the current array. + currentArray.addStreamer(newStreamer.getStreamer()); + System.out.println("Create new streamer: " + currentArray.getNumStreamers()); + + notifyStreamerListeners(newStreamer); + + } + + @Override + public void deleteData(StreamerProperty data){ + super.deleteData(data); + notifyStreamerListeners(null); + } + + private StreamerProperty createDefaultStreamerProperty() { + Streamer streamer = new Streamer(1, 0.,0.,0.,0.,0.,0.); + return new StreamerProperty(streamer); + } + + public TableColumn getZColumn() { + return z; + } + + + } + + /** + * Set the parameters for the streamer pane. + * @param currentArray - the current array. + */ + public void setParams(PamArray currentArray) { + + this.currentArray=currentArray.clone(); + + System.out.println("Set params streamer: " + currentArray.getNumStreamers()); + + tableArrayPane.getStreamers().clear(); + + for (int i=0; itableArrayPane.getStreamers().size()) { + currParams.removeStreamer(currParams.getStreamerCount()-1); + } + +// currentArray.updateStreamer(tableArrayPane.getStreamers().indexOf(currentStreamerData), streamer); + + return currParams; + } + + public void setRecieverLabels() { + tableArrayPane.getZColumn().setText(PamController.getInstance().getGlobalMediumManager().getZString()); + streamerPane.setRecieverLabels(); + } + + public TableView getStreamerTable() { + return tableArrayPane.getTableView(); + } + + /** + * Add a listener which is called whenever a streamer is added, removed or changed. + * @param e - the listener to add + */ + public void addStreamerListener(ArrayChangeListener e) { + this.streamerChangeListeners.add(e); + } + + public void setCurrentArray(PamArray currentArray) { + this.currentArray=currentArray; + + } + + +} diff --git a/src/Array/streamerOrigin/OriginDialogComponent.java b/src/Array/streamerOrigin/OriginDialogComponent.java index f6ab058b..26dbcb79 100644 --- a/src/Array/streamerOrigin/OriginDialogComponent.java +++ b/src/Array/streamerOrigin/OriginDialogComponent.java @@ -1,7 +1,15 @@ package Array.streamerOrigin; import PamView.dialog.DialogComponent; +import javafx.scene.layout.Pane; public abstract class OriginDialogComponent implements DialogComponent{ + /** + * Get a JavaFX pane for the origin method. + * @return the JavaFX pane. + */ + public abstract Pane getSettingsPane(); + + } diff --git a/src/Array/streamerOrigin/StaticHydrophonePane.java b/src/Array/streamerOrigin/StaticHydrophonePane.java new file mode 100644 index 00000000..fc85a583 --- /dev/null +++ b/src/Array/streamerOrigin/StaticHydrophonePane.java @@ -0,0 +1,97 @@ +package Array.streamerOrigin; + +import GPS.GpsData; +import GPS.GpsDataUnit; +import PamUtils.LatLong; +import javafx.scene.control.Label; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.utilityPanes.LatLongPane; + +/** + * JavaFX settings pane for a static hydrophones. + */ +public class StaticHydrophonePane extends PamBorderPane { + + /** + * Reference to static origin mwthod. + */ + private StaticOriginMethod staticOriginMethod; + + LatLongPane latLongPane; + + public StaticHydrophonePane(StaticOriginMethod staticOriginMethod) { + + this.staticOriginMethod=staticOriginMethod; + + latLongPane = new LatLongPane("Static streamer position"); + this.setCenter(latLongPane.getContentNode()); + + } + + + public void setParams() { + GpsDataUnit dataUnit = getStaticOriginSettings().getStaticPosition(); + if (dataUnit == null) { + setLatLong(null); + } + else { + GpsData gpsData = dataUnit.getGpsData(); + setLatLong(gpsData); + } + +// if (gpsData == null) { +// return; +// } +// else { +// setLatLong(gpsData); +// } + + } + + private StaticOriginSettings getStaticOriginSettings() { + return ((StaticOriginSettings) staticOriginMethod.getOriginSettings()); + } + + + public boolean getParams() { + + LatLong latLong = latLongPane.getParams(null); + + if (latLong==null) { + System.err.println("StaticHydrophonePane: latitude and longitude is null"); + return false; + } + + if (getStaticOriginSettings()==null) { + System.err.println("StaticHydrophonePane: static origin is null"); + return false; + } + + //set + getStaticOriginSettings().setStaticPosition(staticOriginMethod.getStreamer(), new GpsData(latLong)); +// +// boolean ok = getStaticOriginSettings()!= null && getStaticOriginSettings() .getStaticPosition() != null; +// +// System.out.println("StaticHydrophonePane: Get params from static origin 1 : " + getStaticOriginSettings()); +// +// System.out.println("StaticHydrophonePane: Get params from static origin 2: " + getStaticOriginSettings() .getStaticPosition()); + + return true; + } + + /** + * Just set the lat long without resetting the heading. + * @param latLong + */ + private void setLatLong(LatLong latLong) { + if (latLong==null) { + //create a default latitude and longitude - Rockall (why not). + LatLong latLongdefault = new LatLong(); + latLongdefault.setLatitude(57.595833333333); + latLongdefault.setLongitude(-13.686944444444); + latLongPane.setParams(latLongdefault); + } + else latLongPane.setParams(latLong); + } + +} diff --git a/src/Array/streamerOrigin/StaticOriginMethod.java b/src/Array/streamerOrigin/StaticOriginMethod.java index 8c413c9c..4f42c922 100644 --- a/src/Array/streamerOrigin/StaticOriginMethod.java +++ b/src/Array/streamerOrigin/StaticOriginMethod.java @@ -29,10 +29,11 @@ import PamUtils.LatLongDialog; import PamUtils.PamCalendar; import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; +import javafx.scene.layout.Pane; public class StaticOriginMethod extends HydrophoneOriginMethod { - private StaticHydrophoneDialogComponent staticHydrophoneDialogComponent; + private StaticHydrophoneComponent staticHydrophoneDialogComponent; private StaticOriginSettings staticOriginSettings = new StaticOriginSettings(); @@ -48,12 +49,107 @@ public class StaticOriginMethod extends HydrophoneOriginMethod { @Override public OriginDialogComponent getDialogComponent() { if (staticHydrophoneDialogComponent == null) { - staticHydrophoneDialogComponent = new StaticHydrophoneDialogComponent(); + staticHydrophoneDialogComponent = new StaticHydrophoneComponent(); } return staticHydrophoneDialogComponent; } + @Override + public OriginSettings getOriginSettings() { + return staticOriginSettings; + } + + @Override + public void setOriginSettings(OriginSettings originSettings) { + staticOriginSettings = (StaticOriginSettings) originSettings; + } + + @Override + public boolean prepare() { + return true; + } + + @Override + public StreamerDataUnit getLastStreamerData() { + StreamerDataBlock streamerDataBlock = ArrayManager.getArrayManager().getStreamerDatabBlock(); + StreamerDataUnit sdu = streamerDataBlock.getLastUnit(1< iter = getGpsDataBlock().getListIterator(timeMilliseconds, 0, PamDataBlock.MATCH_BEFORE, PamDataBlock.POSITION_BEFORE); + if (iter == null) { + return null; + } if (iter.hasNext()) { pointBefore = iter.next(); } diff --git a/src/Localiser/LocaliserModel.java b/src/Localiser/LocaliserModel.java index c0f7ae13..58f3a298 100644 --- a/src/Localiser/LocaliserModel.java +++ b/src/Localiser/LocaliserModel.java @@ -27,7 +27,7 @@ public interface LocaliserModel { public String getToolTipText(); /** - * The type of localisation information the localizer can accept. e.g. bearings, time delays etc. The types are + * The type of localisation information the localiser can accept. e.g. bearings, time delays etc. The types are * defined in the AbstractLocalisation class. * @return integer bitmap of the type of localisation information the localiser can use. */ @@ -35,20 +35,20 @@ public interface LocaliserModel { /** * Get the settings pane for the localiser. Allows users to change localiser settings. - * @return the settings pane for the loclaiser. + * @return the settings pane for the localiser. */ - public LocaliserPane getSettingsPane(); + public LocaliserPane getAlgorithmSettingsPane(); /** - * True if the model has paramaters to set. If has the loclaiser has a settings pane it will have + * True if the model has parameters to set. If has the localiser has a settings pane it will have * parameters. This generally puts an extra button onto a display panel. */ public boolean hasParams(); /** * Run the localisation model. Once completed the results are added to the AbstractLoclaisation class of the input PamDataUnit. - * Note that algorithms may run on a separate thread. Once processing has finished the notifyModelFinished function is called with + *Note that algorithms may run on a separate thread. Once processing has finished the notifyModelFinished function is called with * a progress of 1;. * @param pamDataUnit the pamDataUnit. This can be a super unit if multiple detections are required. * @param addLoc automatically add the localisation result to the data unit, replacing it's current localisation info. @@ -56,7 +56,7 @@ public interface LocaliserModel { public AbstractLocalisation runModel(T pamDataUnit, DetectionGroupOptions detectionGroupOptions, boolean addLoc); /** - * This should be called whenever the localiser has finished processing and, if the loclaisation process is long, then updates progress. + * This should be called whenever the localiser has finished processing and, if the localisation process is long, then updates progress. */ public void notifyModelProgress(double progress); diff --git a/src/Localiser/LocaliserPane.java b/src/Localiser/LocaliserPane.java index 4ca335cf..e275a3f8 100644 --- a/src/Localiser/LocaliserPane.java +++ b/src/Localiser/LocaliserPane.java @@ -1,5 +1,17 @@ package Localiser; -public interface LocaliserPane { +import PamController.SettingsPane; + +public abstract class LocaliserPane extends SettingsPane { + + public LocaliserPane() { + super(null); + // TODO Auto-generated constructor stub + } + + + + + } diff --git a/src/Localiser/ModelControlPanel.java b/src/Localiser/ModelControlPanel.java index 21172a14..2b63bfa6 100644 --- a/src/Localiser/ModelControlPanel.java +++ b/src/Localiser/ModelControlPanel.java @@ -152,8 +152,7 @@ public class ModelControlPanel { @Override //settings panel public void actionPerformed(ActionEvent arg0) { - model.getSettingsPane(); - + model.getAlgorithmSettingsPane(); //AWT implementation. } } diff --git a/src/Localiser/algorithms/Correlations.java b/src/Localiser/algorithms/Correlations.java index 7b726338..294ad138 100644 --- a/src/Localiser/algorithms/Correlations.java +++ b/src/Localiser/algorithms/Correlations.java @@ -258,6 +258,8 @@ public class Correlations { correlationValue = newPeak[1]; return newPeak[0]; } + + /** * Measure the time delay between pulses on two channels. Inputs in this case are the * spectrum data (most of the cross correlation is done in the frequency domain)

diff --git a/src/Localiser/algorithms/genericLocaliser/Chi2TimeDelays.java b/src/Localiser/algorithms/genericLocaliser/Chi2TimeDelays.java index 093d8fd7..c757c009 100644 --- a/src/Localiser/algorithms/genericLocaliser/Chi2TimeDelays.java +++ b/src/Localiser/algorithms/genericLocaliser/Chi2TimeDelays.java @@ -185,7 +185,7 @@ public class Chi2TimeDelays implements MinimisationFunction { } /** - * Set the time delays. Each row is a set fo delays from N synchronised hydrophones. Different rows + * Set the time delays. Each row is a set for delays from N synchronised hydrophones. Different rows * can have different numbers of synchronised hydrophones. * @param timeDelays - a set of time delays in seconds. */ diff --git a/src/Localiser/algorithms/genericLocaliser/MCMC/MCMC.java b/src/Localiser/algorithms/genericLocaliser/MCMC/MCMC.java index a1ebc80b..1413e943 100644 --- a/src/Localiser/algorithms/genericLocaliser/MCMC/MCMC.java +++ b/src/Localiser/algorithms/genericLocaliser/MCMC/MCMC.java @@ -18,17 +18,19 @@ import PamUtils.CoordUtils; import org.apache.commons.math3.ml.clustering.Cluster; import org.apache.commons.math3.ml.clustering.DoublePoint; import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer; + + /** * Markov chain Monte Carlo (MCMC) is a minimisation technique used widely in a variety of field, from finding exo planets, * to solving complex intergals. *

-* This is an advanced and highly computationally intensive localisation algorithm based on MCMC methods. For a good description see; +* This is a highly computationally intensive localisation algorithm based on MCMC methods. For a good description see; * The Transit Light Curve (TLC) Project.I. Four Consecutive Transits of the Exoplanet XO-1b Matthew J. Holman1 *

-* This is an abstract class and requires a chi2 function to operate. +* A chi2 function is required to define the minimisation problem. *

-* Multiple MCMC chains can and should be run. These are executed on different threads to take advantage of multi-core processing as much as possible. -* Even so a large number of chains or large observation set can result in significant processing times. +* Multiple MCMC chains can and should be run. These are executed on different threads to take advantage of multi-core processing if possible. +* Even so, a large number of chains or large observation set can result in significant processing times. *

* Results are analysed for convergence and final locations packed into an MCMCTDResults class. * @@ -117,7 +119,7 @@ public class MCMC implements MinimisationAlgorithm { double newChi; ArrayList successChi=new ArrayList(settings.numberOfJumps/5); - ArrayList successJump=new ArrayList(settings.numberOfJumps/5); + ArrayList successJump=new ArrayList(settings.numberOfJumps/5); // System.out.println("Start MCMC milliseconds: "+ System.currentTimeMillis()); @@ -143,7 +145,7 @@ public class MCMC implements MinimisationAlgorithm { chainPos=potentialNewJump; currentChi=newChi; successChi.add(newChi); - successJump.add(chainPos); + successJump.add(PamArrayUtils.double2Float(chainPos)); //System.out.println(ChainPos); //System.out.println(NewChi); //System.out.println(ObservedTimeDelays); @@ -153,7 +155,7 @@ public class MCMC implements MinimisationAlgorithm { chainPos=potentialNewJump; currentChi=newChi; successChi.add(newChi); - successJump.add(chainPos); + successJump.add(PamArrayUtils.double2Float(chainPos)); //System.out.println(ChainPos); //System.out.println(NewChi); //System.out.println(ObservedTimeDelays); @@ -164,7 +166,7 @@ public class MCMC implements MinimisationAlgorithm { ChainResult chainResult=new ChainResult(successJump, successChi); chainResult= analyseChain( chainResult); - chainResult.nDim=this.chi2.getDim(); + chainResult.nDim=chi2.getDim(); // System.out.println("Chain results is: " + chainResult.mean[0] + " " + chainResult.mean[1] + " " + chainResult.mean[2]); // System.out.println("End MCMC millis: "+ System.currentTimeMillis()); @@ -184,7 +186,7 @@ public class MCMC implements MinimisationAlgorithm { * @param successJump - list of successful jumps * @param successChi - list of successful chi2 values. */ - public ChainResult(ArrayList successJump, ArrayList successChi) { + public ChainResult(ArrayList successJump, ArrayList successChi) { this.successJump=successJump; this.successChi=successChi; } @@ -197,7 +199,7 @@ public class MCMC implements MinimisationAlgorithm { /** * A list of points of the successful jumps. */ - public ArrayList successJump; + public ArrayList successJump; /** * The number of dimensions. @@ -584,7 +586,7 @@ public class MCMC implements MinimisationAlgorithm { //find min value int minIndex = chainResult.successChi.indexOf(Collections.min(chainResult.successChi)); minChi2=chainResult.successChi.get(minIndex); - minChi2Pos=chainResult.successJump.get(minIndex); + minChi2Pos=PamArrayUtils.float2Double(chainResult.successJump.get(minIndex)); break; } @@ -728,19 +730,19 @@ public class MCMC implements MinimisationAlgorithm { */ private EllipticalError getLocError(ArrayList data) { - ArrayList successJumpAll = new ArrayList(); - List successJump; + ArrayList successJumpAll = new ArrayList(); + List successJump; for (int i=0; i> getJumps() { ArrayList> jumps=new ArrayList>(); ArrayList chainJumps; - double[] ajump; + float[] ajump; for (int i=0; i(); for (int j=0; j0) return null; + + if (errors2D==null) return null; - //System.out.println("EllipseLocErrorDraw: draw ellipse:"+errors2D[0]+" "+errors2D[1]+" "+Math.toDegrees(errors2D[2])); + + if (errors2D[0] > PamConstants.EARTH_RADIUS_METERS || errors2D[1] > PamConstants.EARTH_RADIUS_METERS) { + return null; //don't draw infintie stuff - causes nasty errors. + } +// System.out.println("Draw ovals on map"); +// System.out.println("EllipseLocErrorDraw: draw ellipse:"+errors2D[0]+" "+errors2D[1]+" "+Math.toDegrees(errors2D[2])); //System.out.println("Plot errors: perp: "+ perpError+ " horz: "+horzError+ " " + errorDirection); Graphics2D g2d = (Graphics2D)g; - if (errors2D==null) return null; - //draw oval // //need to work out the size of the horizontal error. // perpError=Math.max(perpError, 100); @@ -140,7 +156,7 @@ public class EllipseLocErrorDraw implements LocErrorGraphics { //draw the ellipse and rotate. Ellipse2D oval=new Ellipse2D.Double(errorOriginXY.getX()-horzErrPix/2, errorOriginXY.getY()-perpErrPix/2, horzErrPix, perpErrPix); - g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 5 * 0.1f)); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 5 * 0.05f)); g2d.setPaint(ellipseColor.brighter()); if (!Double.isNaN(errorDirection)) g2d.rotate(-paintAngle, errorOriginXY.getX(), errorOriginXY.getY()); @@ -158,4 +174,8 @@ public class EllipseLocErrorDraw implements LocErrorGraphics { } + public int getDrawType() { + return drawType; + } + } diff --git a/src/Localiser/algorithms/locErrors/EllipticalError.java b/src/Localiser/algorithms/locErrors/EllipticalError.java index 501cbeb2..9fc25df5 100644 --- a/src/Localiser/algorithms/locErrors/EllipticalError.java +++ b/src/Localiser/algorithms/locErrors/EllipticalError.java @@ -51,6 +51,8 @@ public class EllipticalError implements LocaliserError { errorEllipse = new ErrorEllipse(errors, angles); } + + @Override public double getError(PamVector errorDirection) { return errorEllipse.getErrorMagnitude(errorDirection.getUnitVector().getVector()); @@ -78,7 +80,7 @@ public class EllipticalError implements LocaliserError { /** * Get the 2D elliptical error. - * @param planeXy - the plane on whihc to find the ellipse for. + * @param planeXy - the plane on which to find the ellipse for. * @return the elliptical error. */ public double[] getErrorEllipse2D(int planeXy) { @@ -111,6 +113,9 @@ public class EllipticalError implements LocaliserError { @Override public String getStringResult() { String description=""; + if (errorEllipse==null) { + return "Elliptical error is null"; + } if (!errorEllipse.is3D()){ description+=String.format("&ensp 2D Elliptical error:
&ensp Radii: "); for (int i=0; imax){ max=curvatureError; ind=i; //record index of max value; } } - double[] dim={max, getLLCurvature(chi2, point, PamVector.fromHeadAndSlant(Math.toDegrees(angleBin*ind)+90, 0)),-1 }; + double[] dim={max, getLLCurvature(point, PamVector.fromHeadAndSlant(Math.toDegrees(angleBin*ind)+90, 0)),-1 }; double[] angles={angleBin*ind, 0,0}; for (int i=0; imax){ max=curvatureError; @@ -171,7 +171,7 @@ public class LikilihoodError extends EllipticalError { Vector3D location3D=plane.getPointAt(vector2D, 0); // get the max error in this plane. - curvatureError=getLLCurvature(chi2, point, new PamVector(location3D.toArray())); + curvatureError=getLLCurvature(point, new PamVector(location3D.toArray())); if (curvatureError>maxPlane){ maxPlane=curvatureError; indPlane=n; //record index of max value; @@ -203,7 +203,7 @@ public class LikilihoodError extends EllipticalError { ArrayList vectors=new ArrayList(); vectors.add(firstEigenvector.scalarMultiply(max)); //have already calculated error in previous steps. vectors.add(secondVector.scalarMultiply(maxPlane)); //have already calculated error in previous steps. - vectors.add(thirdVector.scalarMultiply(getLLCurvature( chi2, point, new PamVector(thirdVector.toArray())))); + vectors.add(thirdVector.scalarMultiply(getLLCurvature( point, new PamVector(thirdVector.toArray())))); //create an error ellipse. ErrorEllipse errorEllipse=new ErrorEllipse(vectors); @@ -240,7 +240,7 @@ public class LikilihoodError extends EllipticalError { * direction specified and in the opposite direction. Curvature is expressed as 1 standard deviation * error */ - private double getLLCurvature(MinimisationFunction chi2, double[] point, PamVector errorVector) { + private double getLLCurvature(double[] point, PamVector errorVector) { double dis = 10; //the jump along the chi2 surface. double err = 0; diff --git a/src/Localiser/algorithms/locErrors/SimpleError.java b/src/Localiser/algorithms/locErrors/SimpleError.java index 02b64d88..2b00cb4a 100644 --- a/src/Localiser/algorithms/locErrors/SimpleError.java +++ b/src/Localiser/algorithms/locErrors/SimpleError.java @@ -42,10 +42,7 @@ public class SimpleError implements LocaliserError { * @param xyzError - an array of the x/perpindicular y/parallel and z error in meters {x y z} */ public SimpleError(Double xyzError[]) { - simpleErrorDraw=new SimpleLocErrorDraw(this); - perpindiuclarError=xyzError[0]; - parallelError=xyzError[1]; - zError=xyzError[2]; + this(xyzError, 0.); } /** diff --git a/src/Localiser/algorithms/locErrors/SimpleErrorXMLData.java b/src/Localiser/algorithms/locErrors/SimpleErrorJSONData.java similarity index 78% rename from src/Localiser/algorithms/locErrors/SimpleErrorXMLData.java rename to src/Localiser/algorithms/locErrors/SimpleErrorJSONData.java index b2942829..afffd260 100644 --- a/src/Localiser/algorithms/locErrors/SimpleErrorXMLData.java +++ b/src/Localiser/algorithms/locErrors/SimpleErrorJSONData.java @@ -1,14 +1,13 @@ package Localiser.algorithms.locErrors; -import java.text.DecimalFormat; -public class SimpleErrorXMLData extends ErrorXMLData { +public class SimpleErrorJSONData extends ErrorJSONData { private String errorType; private double[] errorData; - public SimpleErrorXMLData(String errorType, double[] errorData) { + public SimpleErrorJSONData(String errorType, double[] errorData) { this.errorType = errorType; this.errorData = errorData; } diff --git a/src/Localiser/algorithms/locErrors/SimpleLocErrorDraw.java b/src/Localiser/algorithms/locErrors/SimpleLocErrorDraw.java index 48c433be..148177ee 100644 --- a/src/Localiser/algorithms/locErrors/SimpleLocErrorDraw.java +++ b/src/Localiser/algorithms/locErrors/SimpleLocErrorDraw.java @@ -27,6 +27,7 @@ public class SimpleLocErrorDraw implements LocErrorGraphics { @Override public TransformShape drawOnMap(Graphics g, PamDataUnit pamDetection, LatLong errorOrigin, GeneralProjector generalProjector, Color ellipseColor) { + g.setColor(ellipseColor); if (simpleError == null) { return null; diff --git a/src/Localiser/algorithms/timeDelayLocalisers/bearingLoc/PairBearingLocaliser.java b/src/Localiser/algorithms/timeDelayLocalisers/bearingLoc/PairBearingLocaliser.java index d6908eca..59c166fd 100644 --- a/src/Localiser/algorithms/timeDelayLocalisers/bearingLoc/PairBearingLocaliser.java +++ b/src/Localiser/algorithms/timeDelayLocalisers/bearingLoc/PairBearingLocaliser.java @@ -214,7 +214,7 @@ public class PairBearingLocaliser implements BearingLocaliser { private boolean resetArray(long timeMillis){ if (this.timeMillis!=timeMillis && currentArray.getHydrophoneLocator().isChangeable()){ - System.out.println("Reset PairBearingLocaliser"); +// System.out.println("Reset PairBearingLocaliser"); prepare(this.arrayElements, timeMillis, this.timingError); return true; } diff --git a/src/Localiser/algorithms/timeDelayLocalisers/hyperbolic/Hyperbolic.java b/src/Localiser/algorithms/timeDelayLocalisers/hyperbolic/Hyperbolic.java index 6aa2eaf7..4875aefa 100644 --- a/src/Localiser/algorithms/timeDelayLocalisers/hyperbolic/Hyperbolic.java +++ b/src/Localiser/algorithms/timeDelayLocalisers/hyperbolic/Hyperbolic.java @@ -17,6 +17,7 @@ import PamUtils.PamUtils; *

* The hyperbolic localiser also includes an error estimation by sampling time delays from a distribution of errors. As a localisation has to * occur for each sample, this drastically increases the computational time for the hyperbolic localiser. + * * @author Jamie Macaulay * */ @@ -62,7 +63,7 @@ public class Hyperbolic implements TimeDelayLocaliserModel { * @param timeDelays - time delay values in seconds. Each is a list of time delays using to indexM1 and indexM2 conventions and corresponding to hydrophones in hydrophoneArray list. * @param timeDelayErrors - time delay error values in seconds. Each is a list of time delay errors using to indexM1 and indexM2 conventions and corresponding to hydrophones in hydrophoneArray list. * @param speedOfSound - the speed of sound in m/s - * @param hyperbolicParams - hyperbolic paramaters to use for this instance of the localiser. + * @param hyperbolicParams - hyperbolic parameters to use for this instance of the localiser. */ public Hyperbolic(ArrayList> hydrophoneArray ,ArrayList> timeDelays, ArrayList< ArrayList> timeDelayErrors, float speedOfSound, HyperbolicParams hyperbolicParams){ this.speedOfSound=speedOfSound; diff --git a/src/Localiser/controls/MCMCPane.java b/src/Localiser/controls/MCMCPane.java new file mode 100644 index 00000000..8674a419 --- /dev/null +++ b/src/Localiser/controls/MCMCPane.java @@ -0,0 +1,305 @@ +package Localiser.controls; + +import Localiser.algorithms.genericLocaliser.MCMC.MCMCParams2; +import PamController.SettingsPane; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.control.ComboBox; + +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamGridPane; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamSpinner; +import pamViewFX.fxNodes.PamVBox; + +public class MCMCPane extends SettingsPane { + + private PamBorderPane mainPane; + + private double JUMP_SPINNER_WIDTH = 60; + + + //controls + private PamSpinner numJumps; + + + private PamSpinner jumpXSpinner; + private PamSpinner jumpYSpinner; + private PamSpinner jumpZSpinner; + + private PamSpinner startDispersion; + private PamSpinner numChains; + private ComboBox clustering; + private PamSpinner numkMeans; + private PamSpinner maxClustDist; + + public MCMCPane() { + super(null); + mainPane = new PamBorderPane(); + mainPane.setTop(createMCMCPane()); + + } + + private Pane createMCMCPane() { + PamVBox vBox = new PamVBox(); + + PamGridPane gridPane = new PamGridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + + int row = 0; + int col = 0; + + Label chainTitleLabel = new Label("Markov chain settings"); +// PamGuiManagerFX.titleFont2style(chainTitleLabel); + chainTitleLabel.setFont(Font.font(null,FontWeight.BOLD, 11)); + + gridPane.add(chainTitleLabel, col, row); + GridPane.setColumnSpan(chainTitleLabel, 7); + row++; + + + col=0; + PamHBox chainHolder = new PamHBox(); + chainHolder.setSpacing(5); + chainHolder.setAlignment(Pos.CENTER_LEFT); + + Label label3 = new Label("Start"); + label3.setAlignment(Pos.CENTER_RIGHT); + gridPane.add(label3, col, row); + col++; + + chainHolder.getChildren().add(numChains = new PamSpinner(1,Integer.MAX_VALUE, 20, 1)); + numChains.setEditable(true); + numChains.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + numChains.setMaxWidth(JUMP_SPINNER_WIDTH); + + chainHolder.getChildren().add(new Label("chains seperated by")); + + chainHolder.getChildren().add(startDispersion = new PamSpinner(0.,Double.MAX_VALUE, 100., 1.)); + startDispersion.setEditable(true); + startDispersion.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + startDispersion.setMaxWidth(JUMP_SPINNER_WIDTH+10); + + chainHolder.getChildren().add(new Label("m")); + + gridPane.add(chainHolder, col, row); + GridPane.setColumnSpan(chainHolder, 7); + + row++; + col=0; + + + //chain propertires + + Label label = new Label("No. jumps"); + label.setAlignment(Pos.CENTER_RIGHT); + + gridPane.add(label, col, row); + col++; + gridPane.add(numJumps = new PamSpinner(10,Integer.MAX_VALUE, 2500000, 10000), col, row); + numJumps.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + GridPane.setColumnSpan(numJumps, 3); + numJumps.setEditable(true); + //mcmJumpSpinner.setMaxWidth(JUMP_SPINNER_WIDTH*2); + + col+=col+2; + Label chainLabel = new Label("per chain"); + gridPane.add(chainLabel, col, row); + GridPane.setColumnSpan(chainLabel, 3); + + row++; + col=0; + Label label2 = new Label("Jump size x"); + label2.setAlignment(Pos.CENTER_RIGHT); + + gridPane.add(label2, col, row); + col++; + + + gridPane.add(jumpXSpinner = new PamSpinner(0.,Double.MAX_VALUE, 1., 0.5), col, row); + jumpXSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + jumpXSpinner.setMaxWidth(JUMP_SPINNER_WIDTH); + jumpXSpinner.setEditable(true); + col++; + + gridPane.add(new Label("y"), col, row); + col++; + + gridPane.add(jumpYSpinner = new PamSpinner(0.,Double.MAX_VALUE, 1., 0.5), col, row); + jumpYSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + jumpYSpinner.setMaxWidth(JUMP_SPINNER_WIDTH); + jumpYSpinner.setEditable(true); + col++; + + gridPane.add(new Label("z"), col, row); + col++; + + gridPane.add(jumpZSpinner = new PamSpinner(0.,Double.MAX_VALUE, 1., 0.5), col, row); + jumpZSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + jumpZSpinner.setMaxWidth(JUMP_SPINNER_WIDTH); + jumpZSpinner.setEditable(true); + col++; + + gridPane.add(new Label("m"), col, row); + row++; + col=0; + + + + //chain clustering + Label clusterTitleLabel = new Label("Result clustering"); + //PamGuiManagerFX.titleFont2style(clusterTitleLabel); + clusterTitleLabel.setFont(Font.font(null,FontWeight.BOLD, 11)); + + gridPane.add(clusterTitleLabel, col, row); + GridPane.setColumnSpan(clusterTitleLabel, 7); + row++; + + + row++; + col=0; + Label label5 = new Label("Clustering"); + label5.setAlignment(Pos.CENTER_RIGHT); + gridPane.add(label5, col, row); + col++; + clustering = new ComboBox(); + clustering.getItems().addAll("None", "kMeans"); + clustering.getSelectionModel().select(1); + gridPane.add(clustering, col, row); + + GridPane.setColumnSpan(clustering, 3); + + clustering.setOnAction((action)->{ + enableControls(); + }); + + + row++; + col=0; + + + //kmeans settings - TODO - would be better to have a custom pane for each clustering algorithm + //but not worth the effort until more clustering algorithms are implemented. + Label label6 = new Label("Start"); + label6.setAlignment(Pos.CENTER_RIGHT); + gridPane.add(label6, col, row); + col++; + + + PamHBox kMeansHolder = new PamHBox(); + kMeansHolder.setSpacing(5); + kMeansHolder.setAlignment(Pos.CENTER_LEFT); + + kMeansHolder.getChildren().add(numkMeans = new PamSpinner(1,Integer.MAX_VALUE, 20, 1)); + numkMeans.setEditable(true); + numkMeans.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + numkMeans.setMaxWidth(JUMP_SPINNER_WIDTH); + + + kMeansHolder.getChildren().add(new Label("k-means and merge at <")); + kMeansHolder.getChildren().add(maxClustDist = new PamSpinner(0.,Double.MAX_VALUE, 5., 1)); + maxClustDist.setEditable(true); + maxClustDist.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + maxClustDist.setMaxWidth(JUMP_SPINNER_WIDTH); + kMeansHolder.getChildren().add(new Label("m")); + + gridPane.add(kMeansHolder, col, row); + GridPane.setColumnSpan(kMeansHolder, 7); + + vBox.getChildren().add(gridPane); + + enableControls(); + + return vBox; + } + + private void enableControls() { + //clustering + numkMeans.setDisable(true); + maxClustDist.setDisable(true); + switch (clustering.getSelectionModel().getSelectedIndex()) { + case MCMCParams2.K_MEANS: + numkMeans.setDisable(false); + maxClustDist.setDisable(false); + break; + case MCMCParams2.NONE: + break; + } + } + + @Override + public MCMCParams2 getParams(MCMCParams2 currParams) { + + //chain settings. + currParams.numberOfChains = numChains.getValue(); + currParams.numberOfJumps = numJumps.getValue(); + + double[] jumpSize = new double[3]; + + jumpSize[0] = jumpXSpinner.getValue(); + jumpSize[1] = jumpYSpinner.getValue(); + jumpSize[2] = jumpZSpinner.getValue(); + + currParams.jumpSize = jumpSize; + + //bit messy but works... + currParams.setChainDispersion(startDispersion.getValue(), 3); + + //cluster settings + currParams.clusterAnalysis = clustering.getSelectionModel().getSelectedIndex(); + currParams.kmeanAttempts = numkMeans.getValue(); + currParams.maxClusterSize = maxClustDist.getValue(); + + return currParams; + } + + @Override + public void setParams(MCMCParams2 currParams) { + + //chain settings. + numJumps.getValueFactory().setValue(currParams.numberOfJumps); + + jumpXSpinner.getValueFactory().setValue(currParams.jumpSize[0]); + jumpYSpinner.getValueFactory().setValue(currParams.jumpSize[1]); + jumpZSpinner.getValueFactory().setValue(currParams.jumpSize[2]); + + numChains.getValueFactory().setValue(currParams.numberOfChains); + + //bit messy but works... + startDispersion.getValueFactory().setValue(Math.abs(currParams.chainStartDispersion[0][0])); + + + //cluster settings + clustering.getSelectionModel().select(currParams.clusterAnalysis); + numkMeans.getValueFactory().setValue(currParams.kmeanAttempts); + maxClustDist.getValueFactory().setValue(currParams.maxClusterSize); + + //enable the controls. + enableControls(); + } + + @Override + public String getName() { + return "MCMC Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + +} diff --git a/src/Localiser/detectionGroupLocaliser/DetectionGroupLocaliser2.java b/src/Localiser/detectionGroupLocaliser/DetectionGroupLocaliser2.java index 1420233f..964d83ef 100644 --- a/src/Localiser/detectionGroupLocaliser/DetectionGroupLocaliser2.java +++ b/src/Localiser/detectionGroupLocaliser/DetectionGroupLocaliser2.java @@ -132,7 +132,7 @@ public class DetectionGroupLocaliser2 implements Local } @Override - public LocaliserPane getSettingsPane() { + public LocaliserPane getAlgorithmSettingsPane() { return locAlgorithm.getSettingsPane(); } diff --git a/src/Localiser/detectionGroupLocaliser/GroupLocResult.java b/src/Localiser/detectionGroupLocaliser/GroupLocResult.java index 2b6b1c24..4b361768 100644 --- a/src/Localiser/detectionGroupLocaliser/GroupLocResult.java +++ b/src/Localiser/detectionGroupLocaliser/GroupLocResult.java @@ -16,6 +16,7 @@ import targetMotionOld.TargetMotionModel; * * Result class for the a group localiser. Usually this will be a target motion localisation, however could also be a group of DIFAR buoys * or other systems which uses a set of detections to localise animals. + * * @author Doug Gillespie * */ @@ -75,8 +76,6 @@ public class GroupLocResult implements Comparable, LocalisationC */ private int dim=3; - private ArrayList> MCMCJumpResults; - /** * @param latLong * @param chi2 @@ -132,16 +131,7 @@ public class GroupLocResult implements Comparable, LocalisationC return side; } - /**Not the correct place to store**/ - @Deprecated - public void setMCMCJumps(ArrayList> MCMCResults){ - this.MCMCJumpResults=MCMCResults; - } - @Deprecated - public ArrayList> getMCMCJumps(){ - return MCMCJumpResults; - } /** diff --git a/src/Map/MapDetectionsDialog.java b/src/Map/MapDetectionsDialog.java index bb85bdb9..ed79530d 100644 --- a/src/Map/MapDetectionsDialog.java +++ b/src/Map/MapDetectionsDialog.java @@ -1,6 +1,7 @@ package Map; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -16,7 +17,11 @@ import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.border.TitledBorder; +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; @@ -85,7 +90,8 @@ public class MapDetectionsDialog extends PamDialog { MapDetectionData md; - ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); + 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/NMEA/AcquireNmeaData.java b/src/NMEA/AcquireNmeaData.java index e86d0ba2..0402017c 100644 --- a/src/NMEA/AcquireNmeaData.java +++ b/src/NMEA/AcquireNmeaData.java @@ -40,8 +40,6 @@ import java.util.Random; import javax.swing.SwingUtilities; import javax.swing.Timer; -import org.apache.commons.math3.analysis.function.Signum; - import serialComms.jserialcomm.PJSerialComm; import serialComms.jserialcomm.PJSerialException; import serialComms.jserialcomm.PJSerialLineListener; diff --git a/src/PamController/DataIntegrityChecker.java b/src/PamController/DataIntegrityChecker.java new file mode 100644 index 00000000..566298e4 --- /dev/null +++ b/src/PamController/DataIntegrityChecker.java @@ -0,0 +1,12 @@ +package PamController; + +/** + * Provides a set of functions that can check and repair data. + * @author dg50 + * + */ +public interface DataIntegrityChecker { + + public boolean checkDataStore(); + +} diff --git a/src/PamController/DataOutputStore.java b/src/PamController/DataOutputStore.java index a3536410..05c88d66 100644 --- a/src/PamController/DataOutputStore.java +++ b/src/PamController/DataOutputStore.java @@ -29,4 +29,11 @@ public interface DataOutputStore extends OfflineDataStore { */ public boolean deleteDataFrom(long timeMillis); + /** + * Get a data integrity checker. This can be called at startup to see if there is a problem. + * @return + */ + public DataIntegrityChecker getInegrityChecker(); + + } diff --git a/src/PamController/PamController.java b/src/PamController/PamController.java index 447c876e..981669b4 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; /** @@ -1099,7 +1100,8 @@ public class PamController implements PamControllerInterface, PamSettings { * later in the AWT event queue. */ public void startLater() { - SwingUtilities.invokeLater(new StartLater(true)); +// SwingUtilities.invokeLater(new StartLater(true)); + startLater(true); } public void startLater(boolean saveSettings) { @@ -1173,6 +1175,7 @@ public class PamController implements PamControllerInterface, PamSettings { @Override public void manualStop() { lastStartStopButton = BUTTON_STOP; + setManualStop(true); pamStop(); } @@ -1186,6 +1189,7 @@ public class PamController implements PamControllerInterface, PamSettings { @Override public boolean pamStart() { // Debug.println("PAMController: pamStart"); + setManualStop(false); return pamStart(true); } @@ -1453,13 +1457,13 @@ public class PamController implements PamControllerInterface, PamSettings { for (int iU = 0; iU < pamControlledUnits.size(); iU++) { pamControlledUnits.get(iU).pamHasStopped(); } - guiFrameManager.pamEnded(); - long stopTime = PamCalendar.getTimeInMillis(); saveEndSettings(stopTime); setPamStatus(PAM_IDLE); + guiFrameManager.pamEnded(); + // no good having this here since it get's called at the end of every file. // if (GlobalArguments.getParam(PamController.AUTOEXIT) != null) { //// can exit here, since we've auto started, can auto exit. @@ -2688,13 +2692,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 @@ -2937,4 +2951,6 @@ public class PamController implements PamControllerInterface, PamSettings { return pamConfiguration; } + + } diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index 12814aa5..b40c4afa 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -16,7 +16,7 @@ public class PamguardVersionInfo { * @return release type */ static public ReleaseType getReleaseType() { - return ReleaseType.OTHER; + return ReleaseType.BETA; } /** @@ -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.10ad"; + static public final String version = "2.02.11d"; /** * Release date */ - static public final String date = "13 March 2024"; + static public final String date = "30 May 2024"; // /** // * Release type - Beta or Core diff --git a/src/PamController/fileprocessing/ReprocessStoreChoice.java b/src/PamController/fileprocessing/ReprocessStoreChoice.java index 6a6bd019..ec41f6b4 100644 --- a/src/PamController/fileprocessing/ReprocessStoreChoice.java +++ b/src/PamController/fileprocessing/ReprocessStoreChoice.java @@ -24,7 +24,7 @@ public enum ReprocessStoreChoice { case DONTSSTART: return "Don't start processing"; case OVERWRITEALL: - return "Overwrite existing output data"; + return "Overwrite all existing output data"; default: break; } @@ -42,7 +42,7 @@ public enum ReprocessStoreChoice { case DONTSSTART: return "Processing will not start. Select alternative storage locations / databases and try again"; case OVERWRITEALL: - return "Overwrite existing output data. Existing data will be deleted"; + return "Overwrite existing output data. All existing data will be deleted"; default: break; } diff --git a/src/PamController/settings/output/xml/PamguardXMLWriter.java b/src/PamController/settings/output/xml/PamguardXMLWriter.java index f74edce0..2a71a250 100644 --- a/src/PamController/settings/output/xml/PamguardXMLWriter.java +++ b/src/PamController/settings/output/xml/PamguardXMLWriter.java @@ -570,6 +570,9 @@ public class PamguardXMLWriter implements PamSettings { */ private Element writeSettings(Document doc, PamSettings pamSettings, Object data, ArrayList objectHierarchy) { + if (data == null) { + return null; + } Element el = doc.createElement("SETTINGS"); el.setAttribute("Type", pamSettings.getUnitType()); el.setAttribute("Name", pamSettings.getUnitName()); @@ -580,6 +583,28 @@ public class PamguardXMLWriter implements PamSettings { // } return el; } + + /** + * Need to use a modified function here since some of Jamies DL params + * classes cast to their own type before the have checked type in the + * equals functoins he's written. + * @param objectHierarchy + * @return + */ + private boolean hasData(ArrayList objectHierarchy, Object object) { + if (objectHierarchy == null) { + return false; + } + for (Object o : objectHierarchy) { + if (object.getClass() != o.getClass()) { + return false; + } + if (o.equals(object)) { + return true; + } + } + return false; + } public Element writeObjectData(Document doc, Element el, Object data, ArrayList objectHierarchy) { if (data == null) { @@ -588,7 +613,7 @@ public class PamguardXMLWriter implements PamSettings { if (objectHierarchy == null) { objectHierarchy = new ArrayList<>(); } - if (objectHierarchy.contains(data)) { + if (hasData(objectHierarchy, data)) { // just write the reference, but nothing else or we'll end up in an infinite loop of objects. Element e = doc.createElement("Object"); e.setAttribute("Class", data.getClass().getName()); @@ -791,6 +816,8 @@ public class PamguardXMLWriter implements PamSettings { } catch (Exception e) { System.out.println("Error in PamguardXMLWriter.writeObjectArray: " + e.getMessage()); + + e.printStackTrace(); } return null; } diff --git a/src/PamController/soundMedium/GlobalMediumManager.java b/src/PamController/soundMedium/GlobalMediumManager.java index f546caea..2fff40d5 100644 --- a/src/PamController/soundMedium/GlobalMediumManager.java +++ b/src/PamController/soundMedium/GlobalMediumManager.java @@ -27,7 +27,7 @@ public class GlobalMediumManager implements PamSettings { private GlobalMediumParams globalMediumParams = new GlobalMediumParams(); - private String warning = " Changing to between air and water requires a PAMGuard restart\n" + public String warning = " Changing to between air and water requires a PAMGuard restart\n" + " for some display changes to take effect. Settings such as\n" + " sound speed, reciever sensitivity values and data unit amplitudes\n" + " will be recalculated or set to new default values.\n" @@ -74,6 +74,7 @@ public class GlobalMediumManager implements PamSettings { subMenu.add(rbMenuItem); } } + /** diff --git a/src/PamModel/PamModel.java b/src/PamModel/PamModel.java index d2bcb919..b0776c48 100644 --- a/src/PamModel/PamModel.java +++ b/src/PamModel/PamModel.java @@ -45,7 +45,6 @@ import whistlesAndMoans.AbstractWhistleDataUnit; import fftManager.FFTDataUnit; import fftManager.PamFFTControl; import group3dlocaliser.Group3DLocaliserControl; -import metadata.MetaDataContol; import meygenturbine.MeygenTurbine; import printscreen.PrintScreenControl; import rockBlock.RockBlockControl; @@ -472,7 +471,8 @@ final public class PamModel implements PamSettings { mi.setToolTipText("Interface to Tethys Database"); mi.setModulesMenuGroup(utilitiesGroup); mi.setMaxNumber(1); - mi.setHidden(SMRUEnable.isEnable() == false); + //mi.addGUICompatabilityFlag(PamGUIManager.FX); //has FX enabled GUI. +// mi.setHidden(SMRUEnable.isEnable() == false); } /* @@ -522,7 +522,8 @@ final public class PamModel implements PamSettings { mi.setModulesMenuGroup(sensorsGroup); mi.setToolTipText("Imports CPOD data"); mi.setHidden(SMRUEnable.isEnable() == false); - + mi.addGUICompatabilityFlag(PamGUIManager.FX); //has FX enabled GUI. + /* * ************* Start Displays Group ******************* */ @@ -591,6 +592,7 @@ final public class PamModel implements PamSettings { mi.addDependency(new PamDependency(RawDataUnit.class, "Acquisition.AcquisitionControl")); mi.setToolTipText("Decimates (reduces the frequency of) audio data"); mi.setModulesMenuGroup(processingGroup); + mi.addGUICompatabilityFlag(PamGUIManager.FX); //has FX enabled GUI. mi = PamModuleInfo.registerControlledUnit(CepstrumControl.class.getName(), CepstrumControl.unitType); mi.addDependency(new PamDependency(FFTDataUnit.class, PamFFTControl.class.getName())); @@ -875,7 +877,7 @@ final public class PamModel implements PamSettings { mi.setModulesMenuGroup(displaysGroup); mi.addGUICompatabilityFlag(PamGUIManager.FX); - mi = PamModuleInfo.registerControlledUnit("detectionPlotFX.DetectionDisplayControl", "Detection Display" ); + mi = PamModuleInfo.registerControlledUnit("detectionPlotFX.DetectionDisplayControl2", "Detection Display" ); mi.setToolTipText("Display detection data"); mi.setModulesMenuGroup(displaysGroup); mi.addGUICompatabilityFlag(PamGUIManager.FX); @@ -1195,7 +1197,13 @@ final public class PamModel implements PamSettings { // Save the name of the class to the global pluginBeingLoaded variable, and load the class. this.setPluginBeingLoaded(className); // Class c = cl.loadClass(className); - Class c = Class.forName(className, true, classLoader); + /* + * Was Failing here if a plugin is loaded before a plugin that has classes + * this one is dependent on. Seems that if we set the second parameter to + * false then it doesn't fully initialize the class, so will be OK, get past + * this stage and fully load the class when it's used. + */ + Class c = Class.forName(className, false, classLoader); if (getPluginBeingLoaded()==null) { continue; } @@ -1271,12 +1279,14 @@ final public class PamModel implements PamSettings { // if there were any errors while accessing the plugin, let the user know and then move // on to the next plugin. } catch (Throwable e1) { + e1.printStackTrace(); String title = "Error accessing plug-in module"; String msg = "There is an error with the plug-in module " + className + ".

" + "This may have been caused by an incompatibility between " + "the plug-in and this version of PAMGuard. Please check the developer's website " + "for help.

" + - "This plug-in will not be available for loading"; + "This plug-in will not be available for loading

" + + e1.getClass().getName() + ": " + e1.getLocalizedMessage(); String help = null; int ans = WarnOnce.showWarning(PamController.getMainFrame(), title, msg, WarnOnce.WARNING_MESSAGE, help, e1); System.err.println("Exception while loading " + className); @@ -1286,12 +1296,14 @@ final public class PamModel implements PamSettings { } } } catch (Throwable ex) { + ex.printStackTrace(); String title = "Error accessing plug-in module"; String msg = "There is an error with the plug-in module " + jarList.get(i).getName() + ".

" + "This may have been caused by an incompatibility between " + "the plug-in and this version of PAMGuard. Please check the developer's website " + "for help.

" + - "This plug-in will not be available for loading"; + "This plug-in will not be available for loading

" + + ex.getClass().getName() + ": " + ex.getLocalizedMessage(); String help = null; int ans = WarnOnce.showWarning(PamController.getMainFrame(), title, msg, WarnOnce.WARNING_MESSAGE, help, ex); System.err.println("Exception while loading " + jarList.get(i).getName()); diff --git a/src/PamUtils/FileParts.java b/src/PamUtils/FileParts.java index ff2c8aba..ef185556 100644 --- a/src/PamUtils/FileParts.java +++ b/src/PamUtils/FileParts.java @@ -6,6 +6,7 @@ import java.net.URI; /** * Class for breaking a file name down into it's constituent * parts. + * * @author Doug Gillespie * */ diff --git a/src/PamUtils/PamArrayUtils.java b/src/PamUtils/PamArrayUtils.java index e6467abd..37909be0 100644 --- a/src/PamUtils/PamArrayUtils.java +++ b/src/PamUtils/PamArrayUtils.java @@ -3,13 +3,18 @@ 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; + /** * Some math and utility functions for arrays and Lists. * @@ -22,19 +27,19 @@ public class PamArrayUtils { /** * Calculate the mean of one dimension within a list of points. e.g. the points might be a list of [x y z] co-ordinates in * which case the dim=0 would return the mean of all x points. - * @param successJump - a list of points + * @param array - a list of points * @param InitialtoIgnorePercentage: ignore the first percentage of results * @param dim - the dimension of the point to calculate the average for * @return the mean of one dimension of the list of the points. */ - public static double mean(ArrayList successJump, double InitialtoIgnorePercentage, int dim){ + public static double mean(ArrayList array, double InitialtoIgnorePercentage, int dim){ double meanTotal=0; int n=0; - int forStart=(int) Math.round((InitialtoIgnorePercentage)*successJump.size()); + int forStart=(int) Math.round((InitialtoIgnorePercentage)*array.size()); - for (int i=forStart; i successJump, double initialtoIgnorePercentage, int dim){ + public static double std(ArrayList array, double initialtoIgnorePercentage, int dim){ double std=0.0; int n=0; - int forStart=(int) Math.round((initialtoIgnorePercentage)*successJump.size()); + int forStart=(int) Math.round((initialtoIgnorePercentage)*array.size()); - double meanTotal= mean(successJump, initialtoIgnorePercentage, dim); + double meanTotal= mean(array, initialtoIgnorePercentage, dim); //calculate standard deviation - for (int k=forStart;k dataUnits) { + // then + PamDataUnit minByTime = dataUnits + .stream() + .min(Comparator.comparing(PamDataUnit::getTimeMilliseconds)) + .orElseThrow(NoSuchElementException::new); + + return minByTime; + } /** * Calculate the median value of an array @@ -183,10 +204,18 @@ public class PamArrayUtils { public static double median(double[] numArray) { Arrays.sort(numArray); double median; - if (numArray.length % 2 == 0) - median = ((double)numArray[numArray.length/2] + (double) numArray[numArray.length/2 - 1])/2; - else - median = (double) numArray[numArray.length/2]; + int n = numArray.length; + if (n == 0) { + return 0; + } + if (n % 2 == 0) { + n/=2; + median = ((double)numArray[n] + (double) numArray[n - 1])/2; + } + else { + n/=2; + median = (double) numArray[n]; + } return median; } @@ -409,6 +438,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= 360) { angle -= 360; } @@ -369,6 +372,9 @@ public class PamUtils { * @return output angle (radians) */ static public double constrainedAngleR(double angle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle >= 2*Math.PI) { angle -= 2*Math.PI; } @@ -384,6 +390,9 @@ public class PamUtils { * @return output angle (degrees) */ static public double constrainedAngle(double angle, double maxAngle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle >= maxAngle) { angle -= 360; } @@ -401,6 +410,9 @@ public class PamUtils { * @return output angle (radians) */ static public double constrainedAngleR(double angle, double maxAngle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle > maxAngle) { angle -= 2*Math.PI; } diff --git a/src/PamUtils/TxtFileUtils.java b/src/PamUtils/TxtFileUtils.java index 6dae44af..d0a80d06 100644 --- a/src/PamUtils/TxtFileUtils.java +++ b/src/PamUtils/TxtFileUtils.java @@ -14,7 +14,8 @@ import java.util.Collection; import java.util.List; /** - * A series of classes to load and import data stored in text files (includes .csv files) + * Some functions to load and import data stored in text files (includes .csv files) + * * @author Jamie Macaulay * */ diff --git a/src/PamUtils/worker/PamWorkDialog.java b/src/PamUtils/worker/PamWorkDialog.java index b4a41d3f..2a3d89c8 100644 --- a/src/PamUtils/worker/PamWorkDialog.java +++ b/src/PamUtils/worker/PamWorkDialog.java @@ -1,5 +1,7 @@ package PamUtils.worker; +import java.awt.Dimension; +import java.awt.Point; import java.awt.Window; import javax.swing.BoxLayout; @@ -23,6 +25,9 @@ public class PamWorkDialog extends PamDialog { mainPanel.setBorder(new TitledBorder("Task Progress")); // GridBagConstraints c = new PamGridBagContraints(); mainPanel.add(progressBar = new JProgressBar(0, 100)); + Dimension sz = progressBar.getPreferredSize(); + sz.width = 300; + progressBar.setPreferredSize(sz); textRows = new PamTextDisplay[nTextRows]; for (int i = 0; i < nTextRows; i++) { // c.gridy++; @@ -35,6 +40,18 @@ public class PamWorkDialog extends PamDialog { getButtonPanel().setVisible(false); setDialogComponent(mainPanel); setResizable(true); + + if (parentFrame != null) { + Dimension prefSize = this.getPreferredSize(); + Point screenLoc = parentFrame.getLocationOnScreen(); + int x = (parentFrame.getWidth()-prefSize.width)/2; + int y = (parentFrame.getHeight()-prefSize.height)/2; + if (screenLoc != null) { + x += screenLoc.x; + y += screenLoc.y; + } + setLocation(x, y); + } } public void update(PamWorkProgressMessage progressMsg) { diff --git a/src/PamUtils/worker/PamWorker.java b/src/PamUtils/worker/PamWorker.java index 376993cd..7f824489 100644 --- a/src/PamUtils/worker/PamWorker.java +++ b/src/PamUtils/worker/PamWorker.java @@ -33,6 +33,7 @@ public class PamWorker { private BackgroundWorker backgroundWorker; + private volatile boolean running = false; /** @@ -82,11 +83,11 @@ public class PamWorker { */ public boolean start() { if (backgroundWorker.isCancelled() || backgroundWorker.isDone() || running) return false; - backgroundWorker.execute(); + backgroundWorker.execute(); return true; } - protected class BackgroundWorker extends SwingWorker { + public class BackgroundWorker extends SwingWorker { @Override protected T doInBackground() throws Exception { @@ -139,6 +140,12 @@ public class PamWorker { } } + + + public BackgroundWorker getBackgroundWorker() { + return backgroundWorker; + } + 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/PamDetectionOverlayGraphics.java b/src/PamView/PamDetectionOverlayGraphics.java index 78917ecc..e4667b8b 100644 --- a/src/PamView/PamDetectionOverlayGraphics.java +++ b/src/PamView/PamDetectionOverlayGraphics.java @@ -1,5 +1,6 @@ package PamView; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; @@ -9,6 +10,7 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.Window; +import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -282,7 +284,6 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw { protected Rectangle drawOnMap(Graphics g, PamDataUnit pamDetection, GeneralProjector generalProjector) { - /* * * four possibilities here. @@ -390,7 +391,12 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw { generalProjector.addHoverData(endPoint, pamDetection); //plot localisation errors. if (localisation.getLocError(i)!=null){ - TransformShape shape=localisation.getLocError(i).getErrorDraw().drawOnMap(g, pamDetection, endLatLong, generalProjector, symbol.getLineColor()); + + Color col =symbol.getLineColor(); + if (drawingOptions != null) { + col = drawingOptions.createColor(symbol.getLineColor(), drawingOptions.getLineOpacity()); + } + TransformShape shape=localisation.getLocError(i).getErrorDraw().drawOnMap(g, pamDetection, endLatLong, generalProjector, col); generalProjector.addHoverData(shape, pamDetection); } else { @@ -496,14 +502,14 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw { } - /** - * Draw a localisation symbol. This is just a circel with a specified radius. - * @param g - graphics handle - * @param pamDataUnit - the PAM data unit which holds the localisation that is being plotted/ - * @param originPoint - the origin of the localisation. - * @param locPoint - the location of the localisation - * @return a rectangle which is the bounds of the localisation symbol. - */ +// /** +// * Draw a localisation symbol. This is just a circel with a specified radius. +// * @param g - graphics handle +// * @param pamDataUnit - the PAM data unit which holds the localisation that is being plotted/ +// * @param originPoint - the origin of the localisation. +// * @param locPoint - the location of the localisation +// * @return a rectangle which is the bounds of the localisation symbol. +// */ // protected Rectangle drawLocSymbol(Graphics g, PamDataUnit pamDataUnit, Point originPoint, Point locPoint, Color color){ // // //need some better graphics options- cast to Graphics2D. @@ -527,79 +533,79 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw { // // return oval.getBounds(); // } - - - // /** - // * Plots errors as an ellipse. - // * @param g - the graphics handle. - // * @param errorOrigin - the error origin point. - // * @param errorDirection. The direction of the perpendicular error. In RADIANS - // * @param perpError - the error perpendicular to the track line or (if no track line this is simply the error in x) - // * @param horzError - the error horizontal to the track line or (if no track line this is simply the error in x) - // * @param color - // * @return - // */ - // private Rectangle drawErrors(Graphics g, PamDataUnit pamDataUnit, GeneralProjector generalProjector, LatLong errorOrigin, double errorDirection, double perpError, double horzError, Color color) { - // - // //System.out.println("Plot errors: perp: "+ perpError+ " horz: "+horzError+ " " + errorDirection); - // - // Graphics2D g2d = (Graphics2D)g; - // - // //draw oval - //// //need to work out the size of the horizontal error. - //// perpError=Math.max(perpError, 100); - //// horzError=Math.max(horzError, 50); - // - // //must work out the horizontal and perpindicular error size in pixels using the projector - // //this is a bit of round about way to do thinfs but best use of framework here. - // LatLong llperp=errorOrigin.addDistanceMeters(0, perpError); - // LatLong l2perp=errorOrigin.addDistanceMeters(horzError, 0); - // - // Point pointPerp = generalProjector.getCoord3d(llperp.getLatitude(), llperp.getLongitude(), 0).getXYPoint(); - // Point pointHorz = generalProjector.getCoord3d(l2perp.getLatitude(), l2perp.getLongitude(), 0).getXYPoint(); - // Point errorOriginXY=generalProjector.getCoord3d(errorOrigin.getLatitude(), errorOrigin.getLongitude(), 0).getXYPoint(); - // - // double perpErrPix=errorOriginXY.distance(pointPerp); - // double horzErrPix=errorOriginXY.distance(pointHorz); - // - // //draw the ellipse and rotate if possible. - // Ellipse2D oval=new Ellipse2D.Double(errorOriginXY.getX()-horzErrPix/2, errorOriginXY.getY()-perpErrPix/2, horzErrPix, perpErrPix); - // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 5 * 0.1f)); - // g2d.setPaint(color.brighter()); - // - // if (!Double.isNaN(errorDirection)) g2d.rotate(errorDirection,errorOriginXY.getX(), errorOriginXY.getY()); - // g2d.draw(oval); - // g2d.fill(oval); - // - // //reset transparency. - // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); - // //need to reset the rotation - // if (!Double.isNaN(errorDirection)) g2d.rotate(-errorDirection,errorOriginXY.getX(), errorOriginXY.getY()); - // - // - // return oval.getBounds(); - // - // - //// // refBEaring should be an angle in radians from the x axis (trig coordinates) - //// // convert this to a compass heading and get the positions of the ends. - //// g.setColor(col); - //// double compassHeading = 90 - (refAngle * 180 / Math.PI); - //// Coordinate3d centre = generalProjector.getCoord3d(refPoint.getLatitude(), refPoint.getLongitude(), 0); - //// LatLong ll1 = refPoint.travelDistanceMeters(compassHeading, err1); - //// LatLong ll2 = refPoint.travelDistanceMeters(compassHeading+90, err2); - //// Coordinate3d p1 = generalProjector.getCoord3d(ll1.getLatitude(), ll1.getLongitude(), 0); - //// Coordinate3d p2 = generalProjector.getCoord3d(ll2.getLatitude(), ll2.getLongitude(), 0); - //// int cx = (int) centre.x; - //// int cy = (int) centre.y; - //// int dx = (int) (p1.x- centre.x); - //// int dy = (int) (p1.y- centre.y); - //// g.drawLine(cx + dx, cy - dy, cx - dx, cy + dy); - //// dx = (int) (p2.x- centre.x); - //// dy = (int) (p2.y- centre.y); - //// g.drawLine(cx + dx, cy - dy, cx - dx, cy + dy); - //// - //// return null; - // } +// +// +// /** +// * Plots errors as an ellipse. +// * @param g - the graphics handle. +// * @param errorOrigin - the error origin point. +// * @param errorDirection. The direction of the perpendicular error. In RADIANS +// * @param perpError - the error perpendicular to the track line or (if no track line this is simply the error in x) +// * @param horzError - the error horizontal to the track line or (if no track line this is simply the error in x) +// * @param color +// * @return +// */ +// private Rectangle drawErrors(Graphics g, PamDataUnit pamDataUnit, GeneralProjector generalProjector, LatLong errorOrigin, double errorDirection, double perpError, double horzError, Color color) { +// +// //System.out.println("Plot errors: perp: "+ perpError+ " horz: "+horzError+ " " + errorDirection); +// +// Graphics2D g2d = (Graphics2D)g; +// +// //draw oval +// // //need to work out the size of the horizontal error. +// // perpError=Math.max(perpError, 100); +// // horzError=Math.max(horzError, 50); +// +// //must work out the horizontal and perpindicular error size in pixels using the projector +// //this is a bit of round about way to do thinfs but best use of framework here. +// LatLong llperp=errorOrigin.addDistanceMeters(0, perpError); +// LatLong l2perp=errorOrigin.addDistanceMeters(horzError, 0); +// +// Point pointPerp = generalProjector.getCoord3d(llperp.getLatitude(), llperp.getLongitude(), 0).getXYPoint(); +// Point pointHorz = generalProjector.getCoord3d(l2perp.getLatitude(), l2perp.getLongitude(), 0).getXYPoint(); +// Point errorOriginXY=generalProjector.getCoord3d(errorOrigin.getLatitude(), errorOrigin.getLongitude(), 0).getXYPoint(); +// +// double perpErrPix=errorOriginXY.distance(pointPerp); +// double horzErrPix=errorOriginXY.distance(pointHorz); +// +// //draw the ellipse and rotate if possible. +// Ellipse2D oval=new Ellipse2D.Double(errorOriginXY.getX()-horzErrPix/2, errorOriginXY.getY()-perpErrPix/2, horzErrPix, perpErrPix); +// g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 5 * 0.1f)); +// g2d.setPaint(color.brighter()); +// +// if (!Double.isNaN(errorDirection)) g2d.rotate(errorDirection,errorOriginXY.getX(), errorOriginXY.getY()); +// g2d.draw(oval); +// g2d.fill(oval); +// +// //reset transparency. +// g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); +// //need to reset the rotation +// if (!Double.isNaN(errorDirection)) g2d.rotate(-errorDirection,errorOriginXY.getX(), errorOriginXY.getY()); +// +// +// return oval.getBounds(); +// +// +// // // refBEaring should be an angle in radians from the x axis (trig coordinates) +// // // convert this to a compass heading and get the positions of the ends. +// // g.setColor(col); +// // double compassHeading = 90 - (refAngle * 180 / Math.PI); +// // Coordinate3d centre = generalProjector.getCoord3d(refPoint.getLatitude(), refPoint.getLongitude(), 0); +// // LatLong ll1 = refPoint.travelDistanceMeters(compassHeading, err1); +// // LatLong ll2 = refPoint.travelDistanceMeters(compassHeading+90, err2); +// // Coordinate3d p1 = generalProjector.getCoord3d(ll1.getLatitude(), ll1.getLongitude(), 0); +// // Coordinate3d p2 = generalProjector.getCoord3d(ll2.getLatitude(), ll2.getLongitude(), 0); +// // int cx = (int) centre.x; +// // int cy = (int) centre.y; +// // int dx = (int) (p1.x- centre.x); +// // int dy = (int) (p1.y- centre.y); +// // g.drawLine(cx + dx, cy - dy, cx - dx, cy + dy); +// // dx = (int) (p2.x- centre.x); +// // dy = (int) (p2.y- centre.y); +// // g.drawLine(cx + dx, cy - dy, cx - dx, cy + dy); +// // +// // return null; +// } protected Rectangle drawLineAndSymbol(Graphics g, PamDataUnit pamDataUnit, GeneralProjector generalProjector, LatLong LL1, LatLong LL2, PamSymbol symbol, ProjectorDrawingOptions drawingOptions) { return drawLineAndSymbol(g, pamDataUnit, generalProjector.getCoord3d(LL1.getLatitude(), LL1.getLongitude(), 0).getXYPoint(), 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/PamView/TopToolBar.java b/src/PamView/TopToolBar.java index 2844aff9..d245c78c 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,20 @@ 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.setDisabledIcon(FontIcon.of(MaterialDesignP.PLAY, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_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.setDisabledIcon(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_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.setDisabledIcon(FontIcon.of(MaterialDesignR.RECORD_CIRCLE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_GRAY)); 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.setDisabledIcon(FontIcon.of(MaterialDesignP.PAUSE, PamSettingsIconButton.NORMAL_SIZE, Color.LIGHT_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 817903a5..cfc25fd6 100644 --- a/src/PamView/component/PamSettingsIconButton.java +++ b/src/PamView/component/PamSettingsIconButton.java @@ -1,17 +1,31 @@ package PamView.component; -import javax.swing.ImageIcon; +import java.awt.Color; + 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; - private static final ImageIcon settingsIcon = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); + 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(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 d3b029f4..e734b045 100644 --- a/src/PamView/dialog/SettingsButton.java +++ b/src/PamView/dialog/SettingsButton.java @@ -1,9 +1,17 @@ package PamView.dialog; +import java.awt.Color; + import javax.swing.Action; +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; + /** * Standard settings button with the little cogwheel for use throughout Swing components. * @author dg50 @@ -20,8 +28,9 @@ public class SettingsButton extends JButton { super(makeIcon()); } - private static ImageIcon makeIcon() { - return new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); + private static Icon makeIcon() { + 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/importData/DataImport.java b/src/PamView/importData/DataImport.java index 2e881386..349c223f 100644 --- a/src/PamView/importData/DataImport.java +++ b/src/PamView/importData/DataImport.java @@ -5,66 +5,95 @@ import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; /** - * This class is used to define what data to import and how to import it. It is used in conjunction with ImportDataSystem to create a generic system to load any type of data into a data block. + * This class is used to define what data to import and how to import it. It is + * used in conjunction with ImportDataSystem to create a generic system to load + * any type of data into a data block. *

- * First getExtensionsStrings() defines which file types can be selected for import. + * First getExtensionsStrings() defines which file types can be selected for + * import. *

- * Before loading in any data we need to perform pre-checks with the function performPreChecks(). This function is called before the load thread is called. Often this will remain blank but say you needed extra user imput then this function could be used - * to create another dialog box before the data is loaded. + * Before loading in any data we need to perform pre-checks with the function + * performPreChecks(). This function is called before the load thread is called. + * Often this will remain blank but say you needed extra user imput then this + * function could be used to create another dialog box before the data is + * loaded. *

- * To create a new importer first we need to load a file into memory using the loadDataIntermediate(String filePath) function. This will create an ArrayList were T is the type of data we're trying to save. So for example if you are loading in NMEA data then - * you would use create loadDataIntermediate(String filePath) which imports the file (filepath) and converts into an ArrayList of strings io.e ArrayList such that T=String; + * To create a new importer first we need to load a file into memory using the + * loadDataIntermediate(String filePath) function. This will create an + * ArrayList were T is the type of data we're trying to save. So for example + * if you are loading in NMEA data then you would use create + * loadDataIntermediate(String filePath) which imports the file (filepath) and + * converts into an ArrayList of strings io.e ArrayList such that + * T=String; *

- * Once the data has been loaded into memory it must be converted into a data unit and saved to a data block. The function createDataUnit(T dataLine) converts data into a PamDatUnit. This function would for example, convert an NMEA strings into a GPSDataUnit. The function - * isDataFormatOK() needs to be used to check that each T is in the correct format. + * Once the data has been loaded into memory it must be converted into a data + * unit and saved to a data block. The function createDataUnit(T dataLine) + * converts data into a PamDatUnit. This function would for example, convert an + * NMEA strings into a GPSDataUnit. The function isDataFormatOK() needs to be + * used to check that each T is in the correct format. *

* * @author Jamie Macaulay * - * @param - the data type loaded from a file. This could be a String, ArrayList, ArrayList> etc. + * @param - the data type loaded from a file. This could be a String, + * ArrayList, ArrayList> etc. */ -public abstract class DataImport { +public abstract class DataImport { - public abstract ArrayList loadDataIntermediate(String filePath); - /** - * Check that a row of imported data is in the correct format. + * Loads the file into memory - each element of the output array list is then processed. + * @param filePath - the filepath. + * @return a list of imported objects. + */ + public abstract ArrayList loadDataIntermediate(String filePath); + + /** + * Check that a row of imported data is in the correct format. + * * @param dataLine-a row of data loaded from file - * @return true if the format is OK. + * @return true if the format is OK. */ public abstract boolean isDataFormatOK(T dataLine); - + /** - * Use this function to perform any pre checks on data/ bring up extra dialog boxes before laoding data. - * @return true iof pre checks are OK. + * Use this function to perform any pre checks on data/ bring up extra dialog + * boxes before loading data. + * + * @return true iof pre checks are OK. */ - public boolean performPreChecks(){ return true; } - + public boolean performPreChecks() { + return true; + } + /** - * Create a data unit from the data loaded from the imported file. + * Create a data unit from the data loaded from the imported file. + * * @param dataLine-a row of data loaded from file - * @return a data unit to be saved to the datablock. + * @return a data unit to be saved to the datablock. */ public abstract PamDataUnit createDataUnit(T dataLine); /** - * Get the data block to to save data to. + * Get the data block to to save data to. + * * @return */ @SuppressWarnings("rawtypes") public abstract PamDataBlock getDataBlock(); /** - * Return the file extensions that can be loaded. - * @return an array of file extensions that can be loaded. + * Return the file extensions that can be loaded. + * + * @return an array of file extensions that can be loaded. */ public abstract String[] getExtensionsStrings(); - + /** - * The name of the data unit to appear on the dialog boxes. - * @return name of the data unit to be imported. + * The name of the data unit to appear on the dialog boxes. + * + * @return name of the data unit to be imported. */ - public String getDataUnitName(){ + public String getDataUnitName() { return "Data Units"; } diff --git a/src/PamView/importData/ImportDataSystem.java b/src/PamView/importData/ImportDataSystem.java index 8f166883..820dbfa9 100644 --- a/src/PamView/importData/ImportDataSystem.java +++ b/src/PamView/importData/ImportDataSystem.java @@ -288,7 +288,7 @@ public class ImportDataSystem { } public void setSaveProgress(int prog){ - System.out.println("prog "+prog); + //System.out.println("prog "+prog); setProgress(prog); } diff --git a/src/PamView/panel/JPanelWithPamKey.java b/src/PamView/panel/JPanelWithPamKey.java index dc16513b..8d12955f 100644 --- a/src/PamView/panel/JPanelWithPamKey.java +++ b/src/PamView/panel/JPanelWithPamKey.java @@ -58,17 +58,23 @@ public class JPanelWithPamKey extends PamPanel implements ColorManaged { } private void replaceKeyPanel(KeyPanel newPanel) { - if (keyPanel != null) { -// synchronized (keyPanel) { + try { + if (keyPanel != null) { + // synchronized (keyPanel) { remove(keyPanel.getPanel()); -// } + // } + } + if (newPanel != null) { + // synchronized (newPanel) { + add(newPanel.getPanel(), gc); + // } + } + } + catch (Exception e) { + } keyPanel = newPanel; - if (keyPanel != null) { -// synchronized (newPanel) { - add(keyPanel.getPanel(), gc); -// } - } + this.invalidate(); this.repaint(10); drawKeyOnTop(); diff --git a/src/PamView/panel/KeyPanel.java b/src/PamView/panel/KeyPanel.java index cb6834df..b180a099 100644 --- a/src/PamView/panel/KeyPanel.java +++ b/src/PamView/panel/KeyPanel.java @@ -102,7 +102,10 @@ public class KeyPanel { } private void fillPanel() { - + JPanel panel = this.panel; + if (panel == null) { + return; + } panel.removeAll(); GridBagConstraints c = new GridBagConstraints(); diff --git a/src/PamView/panel/SeparatorBorder.java b/src/PamView/panel/SeparatorBorder.java new file mode 100644 index 00000000..f42192d4 --- /dev/null +++ b/src/PamView/panel/SeparatorBorder.java @@ -0,0 +1,27 @@ +package PamView.panel; + +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.Graphics; + +import javax.swing.border.TitledBorder; + +public class SeparatorBorder extends TitledBorder { + + private static final long serialVersionUID = 1L; + + public SeparatorBorder(String title) { + super(title); + // TODO Auto-generated constructor stub + } + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + FontMetrics fm = g.getFontMetrics(); + if (fm != null) { + height = fm.getHeight()-EDGE_SPACING*2-1; + } + else { + height = 12; + } + super.paintBorder(c, g, x, y, width, height); + } +} 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/PamView/paneloverlay/overlaymark/OverlayMark.java b/src/PamView/paneloverlay/overlaymark/OverlayMark.java index 2cadab21..5dd1439b 100644 --- a/src/PamView/paneloverlay/overlaymark/OverlayMark.java +++ b/src/PamView/paneloverlay/overlaymark/OverlayMark.java @@ -173,10 +173,10 @@ public class OverlayMark { //now must find the limits of the zoom. Can be complex shapes so a bit difficult; double minX=Double.MAX_VALUE; - double maxX=Double.MIN_VALUE; + double maxX=Double.NEGATIVE_INFINITY; double minY=Double.MAX_VALUE; - double maxY=Double.MIN_VALUE; + double maxY=Double.NEGATIVE_INFINITY; //now find those maximum, and minimum values, for (int i=0; i extends PamObservable { /** * Find a dataunit based on it's database index. If there have been no updates, - * then database indexes should be in order and a fast find canbe used. If + * then database indexes should be in order and a fast find can be used. If * however, there have been updates, then things will not be in order so it's * necessary to go through everything from start to end. * diff --git a/src/PamguardMVC/RawDataTransforms.java b/src/PamguardMVC/RawDataTransforms.java index 98ffd01b..44690ed1 100644 --- a/src/PamguardMVC/RawDataTransforms.java +++ b/src/PamguardMVC/RawDataTransforms.java @@ -161,7 +161,9 @@ public class RawDataTransforms { */ public double[] getPowerSpectrum(int channel, int minBin, int maxBin, int fftLength) { synchronized (synchObject) { + if (minBin==0 && maxBin>=this.getWaveData(0).length-1) { + return getPowerSpectrum(channel, fftLength); } if (fftLength == 0) { @@ -169,8 +171,8 @@ public class RawDataTransforms { } double[] waveformTrim = new double[maxBin-minBin]; - - //System.out.println("minBin: " + minBin + " maxBin: " + maxBin + " raw waveform: " + this.getWaveData(channel).length); + +// System.out.println("minBin: " +minBin + " maxBin: " + maxBin + " " + Math.min(this.getWaveData(channel).length, waveformTrim.length) + " " + this.getWaveData(channel).length + " " + this.getSampleDuration()); System.arraycopy(this.getWaveData(channel), minBin, waveformTrim, 0, Math.min(this.getWaveData(channel).length-minBin-1, waveformTrim.length)); @@ -305,7 +307,7 @@ public class RawDataTransforms { * Get the spectrum length * @return the spectrogram length. */ - private int getCurrentSpectrumLength() { + public int getCurrentSpectrumLength() { if (currentSpecLen<=0) { currentSpecLen = PamUtils.getMinFftLength(dataUnit.getSampleDuration()); } @@ -573,14 +575,21 @@ public class RawDataTransforms { */ public FFTFilter getFFTFilter(FFTFilterParams fftFilterParams) { if (fftFilter == null) { - fftFilter = new FFTFilter(fftFilterParams, this.dataUnit.getParentDataBlock().getSampleRate()); + fftFilter = new FFTFilter(fftFilterParams,getSampleRate()); } else { - fftFilter.setParams(fftFilterParams, this.dataUnit.getParentDataBlock().getSampleRate()); + fftFilter.setParams(fftFilterParams, getSampleRate()); } return fftFilter; } + /** + * Get the sample rate to use for transforms. + * @return the sample rate. + */ + public float getSampleRate() { + return this.dataUnit.getParentDataBlock().getSampleRate(); + } /** * Get a correction based on the slope of the waveform which diff --git a/src/PamguardMVC/dataOffline/OfflineDataLoading.java b/src/PamguardMVC/dataOffline/OfflineDataLoading.java index 0e9dbb3f..885705c1 100644 --- a/src/PamguardMVC/dataOffline/OfflineDataLoading.java +++ b/src/PamguardMVC/dataOffline/OfflineDataLoading.java @@ -189,11 +189,11 @@ public class OfflineDataLoading { switch (offlineDataInfo.getInterrupt()) { case OFFLINE_DATA_INTERRUPT: // System.out.println("Request order cancelling"); - + int giveUp = 0; if (orderData.cancelOrder()) { - while (orderData!=null || !orderData.isDone()) { + while ((orderData!=null || !orderData.isDone()) & giveUp++ < 300) { try { Thread.sleep(10); } catch (InterruptedException e) { 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/PamguardMVC/superdet/SubdetectionInfo.java b/src/PamguardMVC/superdet/SubdetectionInfo.java index 228f9ccc..396dea7d 100644 --- a/src/PamguardMVC/superdet/SubdetectionInfo.java +++ b/src/PamguardMVC/superdet/SubdetectionInfo.java @@ -63,6 +63,13 @@ public class SubdetectionInfo implements Comparable implements Comparable implements Comparable1 in a millisecond. + */ + Integer clickNo = subTableData.getClickNumber(); + if (clickNo == null) { + return 0; + } + else { + subTableData.setChildUID(dataUnit.getUID()); + dataUnit.updateDataUnit(System.currentTimeMillis()); + return (int) (clickNo - duFileInfo.getIndexInFile()); + } + } } diff --git a/src/PamguardMVC/toad/GenericTOADCalculator.java b/src/PamguardMVC/toad/GenericTOADCalculator.java index 1a3e2644..3dbc5a13 100644 --- a/src/PamguardMVC/toad/GenericTOADCalculator.java +++ b/src/PamguardMVC/toad/GenericTOADCalculator.java @@ -134,6 +134,7 @@ public class GenericTOADCalculator implements TOADCalculator, PamSettings { * to give better timing accuracy. */ FFTDataList fftData = fftDataOrganiser.createFFTDataList(dataUnit, sampleRate, dataUnit.getChannelBitmap() & channelMap); +// System.out.println("fftData: " + fftData); if (fftData == null || fftData.getMaxChannelCount() == 0) { // debug stuff ... System.out.println("No FFT Data for " + dataUnit.getSummaryString()); diff --git a/src/PamguardMVC/toad/TOADCalculator.java b/src/PamguardMVC/toad/TOADCalculator.java index 92d0ae9c..818ed1f4 100644 --- a/src/PamguardMVC/toad/TOADCalculator.java +++ b/src/PamguardMVC/toad/TOADCalculator.java @@ -1,10 +1,10 @@ package PamguardMVC.toad; import java.awt.Window; +import java.io.Serializable; import java.util.List; import Array.SnapshotGeometry; -import PamController.SettingsPane; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import group3dlocaliser.algorithm.toadbase.TOADInformation; diff --git a/src/Resources/Ishmael_Energy_Sum.svg b/src/Resources/Ishmael_Energy_Sum.svg new file mode 100644 index 00000000..91103d4c --- /dev/null +++ b/src/Resources/Ishmael_Energy_Sum.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/css/pamCSS.css b/src/Resources/css/pamCSS.css index b2b2d58d..a951f86c 100644 --- a/src/Resources/css/pamCSS.css +++ b/src/Resources/css/pamCSS.css @@ -179,7 +179,7 @@ /******************************************************************************* * * - * Hiding Tab * + * Menu button * * * ******************************************************************************/ diff --git a/src/Resources/css/pamSettingsCSS.css b/src/Resources/css/pamSettingsCSS.css index 66d99081..af9f9cf9 100644 --- a/src/Resources/css/pamSettingsCSS.css +++ b/src/Resources/css/pamSettingsCSS.css @@ -163,7 +163,7 @@ */ /** top-left, top-right, bottom-right, and bottom-left corners, in that order. */ .close-button-right{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground-trans; -fx-background-radius: 0 10 10 0; -fx-border-color: transparent; -fx-border-radius: 0 10 10 0; @@ -171,7 +171,7 @@ .close-button-left{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground-trans; -fx-background-radius: 10 0 0 10; -fx-border-color: transparent; -fx-border-radius: 10 0 0 10; @@ -179,17 +179,17 @@ .close-button-top{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground-trans; -fx-background-radius: 10 10 0 0; - -fx-border-color: transparent; + -fx-border-color: transparent; -fx-border-radius: 10 10 0 0; } .close-button-bottom{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground-trans; -fx-background-radius: 0 0 10 10; - -fx-border-color: transparent; + -fx-border-color: transparent; -fx-border-radius: 0 0 10 10; } diff --git a/src/Resources/css/primer-dark.css b/src/Resources/css/primer-dark.css index 6c1e53ca..a5f0a267 100644 --- a/src/Resources/css/primer-dark.css +++ b/src/Resources/css/primer-dark.css @@ -3666,58 +3666,41 @@ Text { } -/******************************************** -* * -* Spinner * -* * -*********************************************/ -.spinner { - -fx-pref-width: 120px; -} - - -/******************************************** -* * -* Chart * -* * -*********************************************/ - - - -.thin-chart .chart-series-line { - -fx-stroke-width: 1px; -} /******************************************************************************* * * - * Label * + * Pop over * * * ******************************************************************************/ - -#label-title1 { - -fx-font: bold 14pt -fx-font-family; +.popover { + -fx-background-color: -color-button-bg; + -fx-background-radius: 5; + -fx-border-radius: 5; } -#label-title2 { - -fx-font: bold 13pt -fx-font-family; - } - -/******************************************************************************* - * * - * Label * - * * - ******************************************************************************/ - - -.validatorfx-error { - -fx-text-fill: rgb(164, 0, 0); +.popover > .content { + -fx-background-color: -color-button-bg; + -fx-background-radius: 5; + -fx-border-radius: 5; } -.validatorfx-warning { - -fx-text-fill: rgb(196, 160, 0); +.popover > .arrow { + -fx-background-color: -color-button-bg; + -fx-background-radius: 5; + -fx-border-radius: 5; } + +/*weird way to do it but need this to colour the arrow*/ +.popover > .border { + /*-fx-stroke: linear-gradient(to bottom, rgba(0,0,0, .3), rgba(0, 0, 0, .7)) ;*/ + -fx-stroke-width: 0.5; + -fx-fill: -color-button-bg; /* instead -fx-background-color */ + -fx-background-color: -color-button-bg; + } + +} \ No newline at end of file diff --git a/src/Resources/css/primer-pamguard-dark.css b/src/Resources/css/primer-pamguard-dark.css new file mode 100644 index 00000000..ab1d4ecc --- /dev/null +++ b/src/Resources/css/primer-pamguard-dark.css @@ -0,0 +1,320 @@ +{ + + -color-dark: #010409; + -color-light: #ffffff; + -color-base-0: #f0f6fc; + -color-base-1: #c9d1d9; + -color-base-2: #b1bac4; + -color-base-3: #8b949e; + -color-base-4: #6e7681; + -color-base-5: #484f58; + -color-base-6: #30363d; + -color-base-7: #21262d; + -color-base-8: #161b22; + -color-base-9: #0d1117; + -color-accent-0: #cae8ff; + -color-accent-1: #a5d6ff; + -color-accent-2: #79c0ff; + -color-accent-3: #58a6ff; + -color-accent-4: #388bfd; + -color-accent-5: #1f6feb; + -color-accent-6: #1158c7; + -color-accent-7: #0d419d; + -color-accent-8: #0c2d6b; + -color-accent-9: #051d4d; + -color-success-0: #aff5b4; + -color-success-1: #7ee787; + -color-success-2: #56d364; + -color-success-3: #3fb950; + -color-success-4: #2ea043; + -color-success-5: #238636; + -color-success-6: #196c2e; + -color-success-7: #0f5323; + -color-success-8: #033a16; + -color-success-9: #04260f; + -color-warning-0: #f8e3a1; + -color-warning-1: #f2cc60; + -color-warning-2: #e3b341; + -color-warning-3: #d29922; + -color-warning-4: #bb8009; + -color-warning-5: #9e6a03; + -color-warning-6: #845306; + -color-warning-7: #693e00; + -color-warning-8: #4b2900; + -color-warning-9: #341a00; + -color-danger-0: #ffdcd7; + -color-danger-1: #ffc1ba; + -color-danger-2: #ffa198; + -color-danger-3: #ff7b72; + -color-danger-4: #f85149; + -color-danger-5: #da3633; + -color-danger-6: #b62324; + -color-danger-7: #8e1519; + -color-danger-8: #67060c; + -color-danger-9: #490202; + -color-fg-default: #c9d1d9; + -color-fg-muted: #8b949e; + -color-fg-subtle: #6e7681; + -color-fg-emphasis: #ffffff; + -color-bg-default: #0d1117; + -color-bg-overlay: #0d1117; + -color-bg-subtle: #161b22; + -color-bg-inset: #010409; + -color-border-default: #30363d; + -color-border-muted: #21262d; + -color-border-subtle: rgba(240, 246, 252, 0.1); + -color-shadow-default: #010409; + -color-neutral-emphasis-plus: #6e7681; + -color-neutral-emphasis: #6e7681; + -color-neutral-muted: rgba(110, 118, 129, 0.4); + -color-neutral-subtle: rgba(110, 118, 129, 0.1); + -color-accent-fg: #58a6ff; + -color-accent-emphasis: #1f6feb; + -color-accent-muted: rgba(56, 139, 253, 0.4); + -color-accent-subtle: rgba(56, 139, 253, 0.15); + -color-warning-fg: #d29922; + -color-warning-emphasis: #9e6a03; + -color-warning-muted: rgba(187, 128, 9, 0.4); + -color-warning-subtle: rgba(187, 128, 9, 0.15); + -color-success-fg: #3fb950; + -color-success-emphasis: #238636; + -color-success-muted: rgba(46, 160, 67, 0.4); + -color-success-subtle: rgba(46, 160, 67, 0.15); + -color-danger-fg: #f85149; + -color-danger-emphasis: #da3633; + -color-danger-muted: rgba(248, 81, 73, 0.4); + -color-danger-subtle: rgba(248, 81, 73, 0.15); + -color-chart-1: #f3622d; + -color-chart-2: #fba71b; + -color-chart-3: #57b757; + -color-chart-4: #41a9c9; + -color-chart-5: #4258c9; + -color-chart-6: #9a42c8; + -color-chart-7: #c84164; + -color-chart-8: #888888; + -color-chart-1-alpha70: rgba(243, 98, 45, 0.7); + -color-chart-2-alpha70: rgba(251, 167, 27, 0.7); + -color-chart-3-alpha70: rgba(87, 183, 87, 0.7); + -color-chart-4-alpha70: rgba(65, 169, 201, 0.7); + -color-chart-5-alpha70: rgba(66, 88, 201, 0.7); + -color-chart-6-alpha70: rgba(154, 66, 200, 0.7); + -color-chart-7-alpha70: rgba(200, 65, 100, 0.7); + -color-chart-8-alpha70: rgba(136, 136, 136, 0.7); + -color-chart-1-alpha20: rgba(243, 98, 45, 0.2); + -color-chart-2-alpha20: rgba(251, 167, 27, 0.2); + -color-chart-3-alpha20: rgba(87, 183, 87, 0.2); + -color-chart-4-alpha20: rgba(65, 169, 201, 0.2); + -color-chart-5-alpha20: rgba(66, 88, 201, 0.2); + -color-chart-6-alpha20: rgba(154, 66, 200, 0.2); + -color-chart-7-alpha20: rgba(200, 65, 100, 0.2); + -color-chart-8-alpha20: rgba(136, 136, 136, 0.2); + + -fx-pambackground: rgba(238,238,238); + -fx-darkbackground: -color-bg-default; + -fx-darkbackground-trans: rgba(13, 17, 23,0.90); + -fx-highlight: -color-base-6; + -fx-highlight_border: -color-button-fg; + -fx-text: -color-fg-default; + -fx-border_col: -color-border-default; + -fx-border_color: -color-border-default; + -fx-icon_col: -color-fg-default; +} + + +/******************************************************************************* + * * + * For some reason highlighting on hover goes all worng so need to fix this * + * * + ******************************************************************************/ + +.button { + -color-button-border: -color-border-default; + -color-button-bg-hover: -color-base-6; + -color-button-fg-hover: -color-button-fg; + -color-button-border-hover: -color-button-border; +} + +.menu-button, +.split-menu-button { + + -color-button-bg-hover: -color-base-6; + -color-button-fg-hover: -color-button-fg; + -color-button-border-hover: -color-button-border; + -color-button-bg-focused: -color-button-bg; + -color-button-fg-focused: -color-button-fg; + -color-button-border-focused: -color-accent-emphasis; + -color-button-bg-pressed: -color-bg-subtle; + -color-button-fg-pressed: -color-button-fg; +} + +.menu-button.accent, +.split-menu-button.accent { + -color-button-bg: -color-accent-emphasis; + -color-button-fg: -color-fg-emphasis; + -color-button-border: -color-accent-emphasis; + -color-button-bg-hover: -color-accent-emphasis; + -color-button-fg-hover: -color-fg-emphasis; + -color-button-border-hover: -color-accent-emphasis; + -color-button-bg-focused: -color-accent-6; + -color-button-fg-focused: -color-fg-emphasis; + -color-button-border-focused: -color-accent-emphasis; + -color-button-bg-pressed: -color-accent-emphasis; + -color-button-fg-pressed: -color-fg-emphasis; + -color-button-border-pressed: transparent; +} +.menu-button.accent.button-outlined, +.split-menu-button.accent.button-outlined { + -color-button-bg: -color-bg-default; + -color-button-fg: -color-accent-fg; + -color-button-bg-hover: -color-accent-emphasis; + -color-button-fg-hover: -color-fg-emphasis; +} +.menu-button.accent.flat, +.split-menu-button.accent.flat { + -color-button-fg: -color-accent-fg; + -color-button-bg-hover: -color-accent-subtle; +} +.menu-button.success, +.split-menu-button.success { + -color-button-bg: -color-success-emphasis; + -color-button-fg: -color-fg-emphasis; + -color-button-border: -color-success-emphasis; + -color-button-bg-hover: -color-success-emphasis; + -color-button-fg-hover: -color-fg-emphasis; + -color-button-border-hover: -color-success-emphasis; + -color-button-bg-focused: -color-success-6; + -color-button-fg-focused: -color-fg-emphasis; + -color-button-border-focused: -color-success-emphasis; + -color-button-bg-pressed: -color-success-emphasis; + -color-button-fg-pressed: -color-fg-emphasis; + -color-button-border-pressed: transparent; +} +.menu-button.success.button-outlined, +.split-menu-button.success.button-outlined { + -color-button-bg: -color-bg-default; + -color-button-fg: -color-success-fg; + -color-button-bg-hover: -color-success-emphasis; + -color-button-fg-hover: -color-fg-emphasis; +} +.menu-button.success.flat, +.split-menu-button.success.flat { + -color-button-fg: -color-success-fg; + -color-button-bg-hover: -color-success-subtle; +} +.menu-button.danger, +.split-menu-button.danger { + -color-button-bg: -color-danger-emphasis; + -color-button-fg: -color-fg-emphasis; + -color-button-border: -color-danger-emphasis; + -color-button-bg-hover: -color-danger-emphasis; + -color-button-fg-hover: -color-fg-emphasis; + -color-button-border-hover: -color-danger-emphasis; + -color-button-bg-focused: -color-danger-6; + -color-button-fg-focused: -color-fg-emphasis; + -color-button-border-focused: -color-danger-emphasis; + -color-button-bg-pressed: -color-danger-emphasis; + -color-button-fg-pressed: -color-fg-emphasis; + -color-button-border-pressed: transparent; +} +.menu-button.danger.button-outlined, +.split-menu-button.danger.button-outlined { + -color-button-bg: -color-bg-default; + -color-button-fg: -color-danger-fg; + -color-button-bg-hover: -color-danger-emphasis; + -color-button-fg-hover: -color-fg-emphasis; +} +.menu-button.danger.flat, +.split-menu-button.danger.flat { + -color-button-fg: -color-danger-fg; + -color-button-bg-hover: -color-danger-subtle; +} +.menu-button.flat, +.split-menu-button.flat { + -color-button-bg: transparent; + -color-button-fg: -color-fg-default; + -color-button-border: transparent; + -color-button-bg-hover: -color-bg-subtle; + -color-button-fg-hover: -color-button-fg; + -color-button-border-hover: -color-bg-subtle; + -color-button-bg-focused: -color-button-bg; + -color-button-fg-focused: -color-button-fg; + -color-button-border-focused: -color-button-bg; + -color-button-bg-pressed: -color-button-bg; + -color-button-fg-pressed: -color-button-fg; + -color-button-border-pressed: transparent; +} + + +.toggle-button { + -color-button-bg: -color-bg-subtle; + -color-button-fg: -color-fg-default; + -color-button-border: -color-border-default; + -color-button-bg-hover: -color-base-6; + -color-button-fg-hover: -color-button-fg; + -color-button-border-hover: -color-button-border; + -color-button-bg-focused: -color-button-bg; + -color-button-fg-focused: -color-button-fg; + -color-button-border-focused: -color-accent-emphasis; + -color-button-bg-pressed: -color-bg-subtle; + -color-button-fg-pressed: -color-button-fg; + -color-button-border-pressed: transparent; +} + + +.list-view { + -color-cell-bg-selected: -color-base-6; + -color-cell-fg-selected: -color-fg-default; +} + +.table-view { + -color-cell-bg-selected: -color-base-6; + -color-cell-fg-selected: -color-fg-default; +} + +.titled-pane > .title > .text { + -fx-font-size: 1em; +} + +.tree-view { + -color-cell-bg-selected: -color-base-6; + -color-cell-fg-selected: -color-fg-default; +} + +.tree-table-view { + + -color-cell-bg-selected: -color-base-6; + -color-cell-fg-selected: -color-fg-default; + +} + +.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected, +.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected, +.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected { + -color-cell-fg: -color-cell-fg-selected; + -fx-background-color: -color-cell-border, -color-cell-bg-selected; +} + +/******************************************************************************* + * * + * For some reason the table background is white and so this needs fixed. + * * + ******************************************************************************/ + + +.table-view { + -color-cell-bg: -color-bg-default; + -color-cell-fg: -color-fg-default; + -color-cell-bg-selected: -color-base-6; + -color-cell-fg-selected: -color-fg-default; + -color-cell-bg-odd: -color-bg-subtle; + -color-cell-border: -color-border-default; + -fx-border-color: -color-cell-border; + -fx-control-inner-background: -color-bg-default; + -fx-border-width: 1px; + -fx-border-radius: 0; + -color-header-bg: -color-bg-subtle; + -color-header-fg: -color-fg-default; +} + + diff --git a/src/Resources/css/primer-pamguard.css b/src/Resources/css/primer-pamguard.css index 1e28539e..1a2a7415 100644 --- a/src/Resources/css/primer-pamguard.css +++ b/src/Resources/css/primer-pamguard.css @@ -1,114 +1,5 @@ - { - -color-dark: #010409; - -color-light: #ffffff; - -color-base-0: #f0f6fc; - -color-base-1: #c9d1d9; - -color-base-2: #b1bac4; - -color-base-3: #8b949e; - -color-base-4: #6e7681; - -color-base-5: #484f58; - -color-base-6: #30363d; - -color-base-7: #21262d; - -color-base-8: #161b22; - -color-base-9: #0d1117; - -color-accent-0: #cae8ff; - -color-accent-1: #a5d6ff; - -color-accent-2: #79c0ff; - -color-accent-3: #58a6ff; - -color-accent-4: #388bfd; - -color-accent-5: #1f6feb; - -color-accent-6: #1158c7; - -color-accent-7: #0d419d; - -color-accent-8: #0c2d6b; - -color-accent-9: #051d4d; - -color-success-0: #aff5b4; - -color-success-1: #7ee787; - -color-success-2: #56d364; - -color-success-3: #3fb950; - -color-success-4: #2ea043; - -color-success-5: #238636; - -color-success-6: #196c2e; - -color-success-7: #0f5323; - -color-success-8: #033a16; - -color-success-9: #04260f; - -color-warning-0: #f8e3a1; - -color-warning-1: #f2cc60; - -color-warning-2: #e3b341; - -color-warning-3: #d29922; - -color-warning-4: #bb8009; - -color-warning-5: #9e6a03; - -color-warning-6: #845306; - -color-warning-7: #693e00; - -color-warning-8: #4b2900; - -color-warning-9: #341a00; - -color-danger-0: #ffdcd7; - -color-danger-1: #ffc1ba; - -color-danger-2: #ffa198; - -color-danger-3: #ff7b72; - -color-danger-4: #f85149; - -color-danger-5: #da3633; - -color-danger-6: #b62324; - -color-danger-7: #8e1519; - -color-danger-8: #67060c; - -color-danger-9: #490202; - -color-fg-default: #c9d1d9; - -color-fg-muted: #8b949e; - -color-fg-subtle: #6e7681; - -color-fg-emphasis: #ffffff; - -color-bg-default: #0d1117; - -color-bg-overlay: #0d1117; - -color-bg-subtle: #161b22; - -color-bg-inset: #010409; - -color-border-default: #30363d; - -color-border-muted: #21262d; - -color-border-subtle: rgba(240, 246, 252, 0.1); - -color-shadow-default: #010409; - -color-neutral-emphasis-plus: #6e7681; - -color-neutral-emphasis: #6e7681; - -color-neutral-muted: rgba(110, 118, 129, 0.4); - -color-neutral-subtle: rgba(110, 118, 129, 0.1); - -color-accent-fg: #58a6ff; - -color-accent-emphasis: #1f6feb; - -color-accent-muted: rgba(56, 139, 253, 0.4); - -color-accent-subtle: rgba(56, 139, 253, 0.15); - -color-warning-fg: #d29922; - -color-warning-emphasis: #9e6a03; - -color-warning-muted: rgba(187, 128, 9, 0.4); - -color-warning-subtle: rgba(187, 128, 9, 0.15); - -color-success-fg: #3fb950; - -color-success-emphasis: #238636; - -color-success-muted: rgba(46, 160, 67, 0.4); - -color-success-subtle: rgba(46, 160, 67, 0.15); - -color-danger-fg: #f85149; - -color-danger-emphasis: #da3633; - -color-danger-muted: rgba(248, 81, 73, 0.4); - -color-danger-subtle: rgba(248, 81, 73, 0.15); - -color-chart-1: #f3622d; - -color-chart-2: #fba71b; - -color-chart-3: #57b757; - -color-chart-4: #41a9c9; - -color-chart-5: #4258c9; - -color-chart-6: #9a42c8; - -color-chart-7: #c84164; - -color-chart-8: #888888; - -color-chart-1-alpha70: rgba(243, 98, 45, 0.7); - -color-chart-2-alpha70: rgba(251, 167, 27, 0.7); - -color-chart-3-alpha70: rgba(87, 183, 87, 0.7); - -color-chart-4-alpha70: rgba(65, 169, 201, 0.7); - -color-chart-5-alpha70: rgba(66, 88, 201, 0.7); - -color-chart-6-alpha70: rgba(154, 66, 200, 0.7); - -color-chart-7-alpha70: rgba(200, 65, 100, 0.7); - -color-chart-8-alpha70: rgba(136, 136, 136, 0.7); - -color-chart-1-alpha20: rgba(243, 98, 45, 0.2); - -color-chart-2-alpha20: rgba(251, 167, 27, 0.2); - -color-chart-3-alpha20: rgba(87, 183, 87, 0.2); - -color-chart-4-alpha20: rgba(65, 169, 201, 0.2); - -color-chart-5-alpha20: rgba(66, 88, 201, 0.2); - -color-chart-6-alpha20: rgba(154, 66, 200, 0.2); - -color-chart-7-alpha20: rgba(200, 65, 100, 0.2); - -color-chart-8-alpha20: rgba(136, 136, 136, 0.2); -fx-pambackground: rgba(238,238,238); -fx-darkbackground: -color-bg-default; @@ -117,17 +8,19 @@ -fx-highlight_border: -color-button-fg; -fx-text: -color-fg-default; -fx-border_col: -color-border-default; + -fx-border_color: -color-border-default; -fx-icon_col: -color-fg-default; + -fx-highlight_border_settings: rgba(0,204,204,1); + -fx-highlight_settings: rgba(0,204,204,1); } -.root { - -fx-background-color:-fx-darkbackground; - -fx-font-size: 12px; - -fx-background-radius: inherit; - -fx-background-insets: inherit; - -fx-padding: 5px 5px 5px 5px; + +.root{ + -fx-font-size: 10pt; + -fx-font-family: "Ubuntu"; } + /******************************************************************************* * * * Panes * @@ -151,240 +44,6 @@ -fx-background-color: -fx-plotbackground; } - -.button { - -color-button-bg: -color-bg-subtle; - -color-button-fg: -color-fg-default; - -color-button-border: -color-border-default; - -color-button-bg-hover: -color-base-6; - -color-button-fg-hover: -color-button-fg; - -color-button-border-hover: -color-button-border; - -color-button-bg-focused: -color-button-bg; - -color-button-fg-focused: -color-button-fg; - -color-button-border-focused: -color-accent-emphasis; - -color-button-bg-pressed: -color-bg-subtle; - -color-button-fg-pressed: -color-button-fg; - -color-button-border-pressed: transparent; - -fx-background-color: -color-button-border, -color-button-bg; - -fx-background-insets: 0, 1px; - -fx-background-radius: 4px; - -fx-graphic-text-gap: 6px; - -fx-text-fill: -color-button-fg; - -fx-alignment: CENTER; - -fx-padding: 8px 12px 8px 12px; -} - -.menu-button, -.split-menu-button { - -color-button-bg: -color-bg-subtle; - -color-button-fg: -color-fg-default; - -color-button-border: -color-border-default; - -color-button-bg-hover: -color-base-6; - -color-button-fg-hover: -color-button-fg; - -color-button-border-hover: -color-button-border; - -color-button-bg-focused: -color-button-bg; - -color-button-fg-focused: -color-button-fg; - -color-button-border-focused: -color-accent-emphasis; - -color-button-bg-pressed: -color-bg-subtle; - -color-button-fg-pressed: -color-button-fg; - -color-button-border-pressed: transparent; - -fx-background-color: -color-button-border, -color-button-bg; - -fx-background-insets: 0, 1px; - -fx-background-radius: 4px; - -fx-graphic-text-gap: 6px; - -fx-text-fill: -color-button-fg; - -fx-alignment: CENTER; - -fx-padding: 0; - -fx-alignment: CENTER_LEFT; -} - -.menu-button.accent, -.split-menu-button.accent { - -color-button-bg: -color-accent-emphasis; - -color-button-fg: -color-fg-emphasis; - -color-button-border: -color-accent-emphasis; - -color-button-bg-hover: -color-accent-emphasis; - -color-button-fg-hover: -color-fg-emphasis; - -color-button-border-hover: -color-accent-emphasis; - -color-button-bg-focused: -color-accent-6; - -color-button-fg-focused: -color-fg-emphasis; - -color-button-border-focused: -color-accent-emphasis; - -color-button-bg-pressed: -color-accent-emphasis; - -color-button-fg-pressed: -color-fg-emphasis; - -color-button-border-pressed: transparent; -} -.menu-button.accent.button-outlined, -.split-menu-button.accent.button-outlined { - -color-button-bg: -color-bg-default; - -color-button-fg: -color-accent-fg; - -color-button-bg-hover: -color-accent-emphasis; - -color-button-fg-hover: -color-fg-emphasis; -} -.menu-button.accent.flat, -.split-menu-button.accent.flat { - -color-button-fg: -color-accent-fg; - -color-button-bg-hover: -color-accent-subtle; -} -.menu-button.success, -.split-menu-button.success { - -color-button-bg: -color-success-emphasis; - -color-button-fg: -color-fg-emphasis; - -color-button-border: -color-success-emphasis; - -color-button-bg-hover: -color-success-emphasis; - -color-button-fg-hover: -color-fg-emphasis; - -color-button-border-hover: -color-success-emphasis; - -color-button-bg-focused: -color-success-6; - -color-button-fg-focused: -color-fg-emphasis; - -color-button-border-focused: -color-success-emphasis; - -color-button-bg-pressed: -color-success-emphasis; - -color-button-fg-pressed: -color-fg-emphasis; - -color-button-border-pressed: transparent; -} -.menu-button.success.button-outlined, -.split-menu-button.success.button-outlined { - -color-button-bg: -color-bg-default; - -color-button-fg: -color-success-fg; - -color-button-bg-hover: -color-success-emphasis; - -color-button-fg-hover: -color-fg-emphasis; -} -.menu-button.success.flat, -.split-menu-button.success.flat { - -color-button-fg: -color-success-fg; - -color-button-bg-hover: -color-success-subtle; -} -.menu-button.danger, -.split-menu-button.danger { - -color-button-bg: -color-danger-emphasis; - -color-button-fg: -color-fg-emphasis; - -color-button-border: -color-danger-emphasis; - -color-button-bg-hover: -color-danger-emphasis; - -color-button-fg-hover: -color-fg-emphasis; - -color-button-border-hover: -color-danger-emphasis; - -color-button-bg-focused: -color-danger-6; - -color-button-fg-focused: -color-fg-emphasis; - -color-button-border-focused: -color-danger-emphasis; - -color-button-bg-pressed: -color-danger-emphasis; - -color-button-fg-pressed: -color-fg-emphasis; - -color-button-border-pressed: transparent; -} -.menu-button.danger.button-outlined, -.split-menu-button.danger.button-outlined { - -color-button-bg: -color-bg-default; - -color-button-fg: -color-danger-fg; - -color-button-bg-hover: -color-danger-emphasis; - -color-button-fg-hover: -color-fg-emphasis; -} -.menu-button.danger.flat, -.split-menu-button.danger.flat { - -color-button-fg: -color-danger-fg; - -color-button-bg-hover: -color-danger-subtle; -} -.menu-button.flat, -.split-menu-button.flat { - -color-button-bg: transparent; - -color-button-fg: -color-fg-default; - -color-button-border: transparent; - -color-button-bg-hover: -color-bg-subtle; - -color-button-fg-hover: -color-button-fg; - -color-button-border-hover: -color-bg-subtle; - -color-button-bg-focused: -color-button-bg; - -color-button-fg-focused: -color-button-fg; - -color-button-border-focused: -color-button-bg; - -color-button-bg-pressed: -color-button-bg; - -color-button-fg-pressed: -color-button-fg; - -color-button-border-pressed: transparent; -} - - -.toggle-button { - -color-button-bg: -color-bg-subtle; - -color-button-fg: -color-fg-default; - -color-button-border: -color-border-default; - -color-button-bg-hover: -color-base-6; - -color-button-fg-hover: -color-button-fg; - -color-button-border-hover: -color-button-border; - -color-button-bg-focused: -color-button-bg; - -color-button-fg-focused: -color-button-fg; - -color-button-border-focused: -color-accent-emphasis; - -color-button-bg-pressed: -color-bg-subtle; - -color-button-fg-pressed: -color-button-fg; - -color-button-border-pressed: transparent; - -fx-background-color: -color-button-border, -color-button-bg; - -fx-background-insets: 0, 1px; - -fx-background-radius: 4px; - -fx-graphic-text-gap: 6px; - -fx-text-fill: -color-button-fg; - -fx-alignment: CENTER; - -color-button-bg-selected: -color-accent-emphasis; - -color-button-fg-selected: -color-fg-emphasis; - -fx-padding: 8px 12px 8px 12px; -} - - -.list-view { - -color-cell-bg: -color-bg-default; - -color-cell-fg: -color-fg-default; - -color-cell-bg-selected: -color-base-6; - -color-cell-fg-selected: -color-fg-default; - -color-cell-bg-odd: -color-bg-subtle; - -color-cell-border: -color-border-default; - -fx-border-color: -color-cell-border; - -fx-border-width: 1px; - -fx-border-radius: 0; -} - -.table-view { - -color-cell-bg: -color-bg-default; - -color-cell-fg: -color-fg-default; - -color-cell-bg-selected: -color-base-6; - -color-cell-fg-selected: -color-fg-default; - -color-cell-bg-odd: -color-bg-subtle; - -color-cell-border: -color-border-default; - -fx-border-color: -color-cell-border; - -fx-border-width: 1px; - -fx-border-radius: 0; - -color-header-bg: -color-bg-subtle; - -color-header-fg: -color-fg-default; -} - -.titled-pane > .title > .text { - -fx-font-size: 1em; -} - -.tree-view { - -color-cell-bg: -color-bg-default; - -color-cell-fg: -color-fg-default; - -color-cell-bg-selected: -color-base-6; - -color-cell-fg-selected: -color-fg-default; - -color-cell-bg-odd: -color-bg-subtle; - -color-cell-border: -color-border-default; - -fx-border-color: -color-cell-border; - -fx-border-width: 1px; - -fx-border-radius: 0; -} - -.tree-table-view { - -color-cell-bg: -color-bg-default; - -color-cell-fg: -color-fg-default; - -color-cell-bg-selected: -color-base-6; - -color-cell-fg-selected: -color-fg-default; - -color-cell-bg-odd: -color-bg-subtle; - -color-cell-border: -color-border-default; - -fx-border-color: -color-cell-border; - -fx-border-width: 1px; - -fx-border-radius: 0; - -color-header-bg: -color-bg-subtle; - -color-header-fg: -color-fg-default; -} - -.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, -.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected, -.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected, -.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected { - -color-cell-fg: -color-cell-fg-selected; - -fx-background-color: -color-cell-border, -color-cell-bg-selected; -} - /******************************************************************************* * * * ikonli icons * @@ -402,54 +61,50 @@ -fx-effect: none; } +/******************************************************************************* + * * + * Label * + * * + ******************************************************************************/ -/**************************************************************** - - ScrollPane - -****************************************************************/ -.scroll-pane { - -fx-background: #0d1117; - -fx-background-color: transparent; -} - -.scroll-pane > .viewport { - -fx-background-color: transparent; -} - -.scroll-pane-dark { - -fx-background: -fx-darkbackground; - -fx-background-color: -fx-darkbackground; + +#label-title1 { + -fx-font: bold 14pt -fx-font-family; } +#label-title2 { + -fx-font: bold 13pt -fx-font-family; + } /******************************************************************************* * * - * Hiding Tab * + * Validator * * * ******************************************************************************/ - #hide-tab { - -fx-background-color: transparent; - -fx-border-radius: 5 5 0 0; - -fx-background-radius: 5 5 0 0; - -fx-border-color: -fx-border_col; + + +.validatorfx-error { + -fx-text-fill: rgb(164, 0, 0); } - #show-tab { - -fx-background-color: transparent; - -fx-border-radius: 5 5 0 0; - -fx-background-radius: 5 5 0 0; - -fx-border-color: -fx-border_col; +.validatorfx-warning { + -fx-text-fill: rgb(196, 160, 0); } - #hide-tab-highlight { - -fx-background-color: -fx-highlight; - -fx-border-radius: 5 5 0 0; - -fx-background-radius: 5 5 0 0; - -fx-border-color: -fx-highlight_border; -} +/******************************************** +* * +* Chart * +* * +*********************************************/ + + + +.thin-chart .chart-series-line { + -fx-stroke-width: 1px; +} + /******************************************************************************* * * @@ -475,13 +130,14 @@ -fx-border-color: -fx-highlight; } */ + /** * Button for closing a hiding panel. Right indicates bottoms points towards the right, closing a hiding panel * on the right hand side of the screen. These buttons have a transparent background and rounded corners. */ /** top-left, top-right, bottom-right, and bottom-left corners, in that order. */ .close-button-right{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 0 10 10 0; -fx-border-color: transparent; -fx-border-radius: 0 10 10 0; @@ -489,7 +145,7 @@ .close-button-left{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 10 0 0 10; -fx-border-color: transparent; -fx-border-radius: 10 0 0 10; @@ -497,21 +153,22 @@ .close-button-top{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 10 10 0 0; --fx-border-color: transparent; + -fx-border-color: transparent; -fx-border-radius: 10 10 0 0; } .close-button-bottom{ - -fx-background-color: transparent; + -fx-background-color: -fx-darkbackground; -fx-background-radius: 0 0 10 10; - -fx-border-color: transparent; + -fx-border-color: transparent; -fx-border-radius: 0 0 10 10; } + .close-button-bottom-trans{ -fx-border-color: transparent; -fx-background-color: transparent; @@ -577,7 +234,8 @@ .popover > .content { -fx-background-color: -fx-darkbackground; -fx-background-radius: 5; - -fx-border-radius: 5; + -fx-border-radius: 5; + -fx-padding: 0 0 0 0; } .popover > .arrow { @@ -636,8 +294,11 @@ } -/**********/ - +/******************************************************************************* + * * + * Menu Items * + * * + ******************************************************************************/ .menu-item { -fx-background-color: -color-bg-default; @@ -671,8 +332,6 @@ * Spinner * * * *********************************************/ - - .spinner { -fx-pref-width: 120px; } @@ -682,98 +341,89 @@ } +/**************************************************************** + + ScrollPane + +****************************************************************/ +.scroll-pane { + -fx-background: #0d1117; + -fx-background-color: transparent; +} + +.scroll-pane > .viewport { + -fx-background-color: transparent; +} + +.scroll-pane-dark { + -fx-background: -fx-darkbackground; + -fx-background-color: -fx-darkbackground; +} + /******************************************************************************* * * - * Hiding Pane * + * Hiding Tab * + * * + ******************************************************************************/ + #hide-tab { + -fx-background-color: transparent; + -fx-border-radius: 5 5 0 0; + -fx-background-radius: 5 5 0 0; + -fx-border-color: -fx-border_col; +} + + #show-tab { + -fx-background-color: transparent; + -fx-border-radius: 5 5 0 0; + -fx-background-radius: 5 5 0 0; + -fx-border-color: -fx-border_col; +} + + #hide-tab-highlight { + -fx-background-color: -fx-highlight; + -fx-border-radius: 5 5 0 0; + -fx-background-radius: 5 5 0 0; + -fx-border-color: -fx-highlight_border; +} + +/******************************************************************************* + * * + * Pam Internal Pane * + * * + ******************************************************************************/ + +.circle-internal { + -fx-fill: -fx-highlight_settings; + -fx-stroke: -fx-highlight_border_settings; + -fx-stroke-width: 2; +} + +.line-internal { + -fx-fill: -fx-highlight_border_settings; + -fx-stroke: -fx-highlight_border_settings; + -fx-stroke-width: 3; + -fx-stroke-dash-array: 3.0 7.0 3.0 7.0; +} + +.button-internal { + -fx-background-color: -fx-highlight_settings; + -fx-border-color: -fx-highlight_border_settings; + -fx-background-radius: 10; + -fx-border-radius: 10; +} + +.button-internal:hover { + -fx-background-color: -fx-highlight_border_settings; +} + +/******************************************************************************* + * * + * Dialog * * * ******************************************************************************/ -/** - * Button for closing a hiding panel. Right indicates bottoms points towards the right, closing a hiding panel - * on the right hand side of the screen. These buttons have a transparent background and rounded corners. - */ - /** top-left, top-right, bottom-right, and bottom-left corners, in that order. */ -.close-button-right{ - -fx-background-color: transparent; - -fx-background-radius: 0 10 10 0; - -fx-border-radius: 0 10 10 0; -} - -.close-button-right:hover { - -fx-background-color: -fx-highlight; -} - - -.close-button-left{ - -fx-background-color: transparent; - -fx-background-radius: 10 0 0 10; - -fx-border-radius: 10 0 0 10; -} - -.close-button-left:hover { - -fx-background-color: -fx-highlight; -} - - -.close-button-top{ - -fx-background-color: transparent; - -fx-background-radius: 10 10 0 0; - -fx-border-radius: 10 10 0 0; -} - - -.close-button-bottom { - -fx-background-color: transparent; - -fx-background-radius: 0 0 10 10; - -fx-border-radius: 0 0 10 10; -} - -.close-button-bottom:hover { - -fx-background-color: -fx-highlight; -} - - -.close-button-bottom-grey{ --fx-border-color: transparent; - -fx-background-radius: 0 0 10 10; - -fx-border-radius: 0 0 10 10; -} - -.close-button-left-trans{ - -fx-border-color: transparent; - -fx-background-color: transparent; - -fx-background-radius: 10 0 0 10; - -fx-border-radius: 10 0 0 10; -} - -.close-button-left-trans:focused { - -fx-border-color: transparent; - -fx-background-color: transparent; - -fx-background-radius: 10 0 0 10; - -fx-border-radius: 10 0 0 10; -} - -.close-button-right-trans{ - -fx-border-color: transparent; - -fx-background-color: transparent; - -fx-background-radius: 0 10 10 0; - -fx-border-radius: 0 10 10 0; -} - -.close-button-right-trans:focused { - -fx-border-color: transparent; - -fx-background-color: transparent; - -fx-background-radius: 0 10 10 0; - -fx-border-radius: 0 10 10 0; -} - - -.menu-square-button { --fx-border-color: -fx_border_col; - -fx-background-color: transparent; - -fx-background-radius: 0 0 0 0; - -fx-border-radius: 0 0 0 0; -} - +.dialog-pane > .content { + -fx-padding: 0,0,0,0; +} \ No newline at end of file diff --git a/src/Resources/delphinid_logo01.svg b/src/Resources/delphinid_logo01.svg new file mode 100644 index 00000000..744670bf --- /dev/null +++ b/src/Resources/delphinid_logo01.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + delphinID + + + + + + diff --git a/src/Resources/exampleSounds/Blue_whale.wav b/src/Resources/exampleSounds/Blue_whale.wav new file mode 100644 index 00000000..8bca2058 Binary files /dev/null and b/src/Resources/exampleSounds/Blue_whale.wav differ diff --git a/src/Resources/modules/Array Icon2.svg b/src/Resources/modules/Array Icon2.svg new file mode 100644 index 00000000..2ca3e501 --- /dev/null +++ b/src/Resources/modules/Array Icon2.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/Click Detector Icon.svg b/src/Resources/modules/Click Detector Icon.svg new file mode 100644 index 00000000..25f15d3e --- /dev/null +++ b/src/Resources/modules/Click Detector Icon.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/clicktrain.svg b/src/Resources/modules/clicktrain.svg new file mode 100644 index 00000000..046aa6fe --- /dev/null +++ b/src/Resources/modules/clicktrain.svg @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/decimator.svg b/src/Resources/modules/decimator.svg new file mode 100644 index 00000000..79be1add --- /dev/null +++ b/src/Resources/modules/decimator.svg @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/detectionDisplay.svg b/src/Resources/modules/detectionDisplay.svg new file mode 100644 index 00000000..8ed70427 --- /dev/null +++ b/src/Resources/modules/detectionDisplay.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/fft.svg b/src/Resources/modules/fft.svg new file mode 100644 index 00000000..f65bbefc --- /dev/null +++ b/src/Resources/modules/fft.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/filters.svg b/src/Resources/modules/filters.svg new file mode 100644 index 00000000..8997d124 --- /dev/null +++ b/src/Resources/modules/filters.svg @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/modules/matched_click_classifier.svg b/src/Resources/modules/matched_click_classifier.svg new file mode 100644 index 00000000..9ec8e0c8 --- /dev/null +++ b/src/Resources/modules/matched_click_classifier.svg @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RightWhaleEdgeDetector/RWEControl.java b/src/RightWhaleEdgeDetector/RWEControl.java index 0153442a..39f4d3bb 100644 --- a/src/RightWhaleEdgeDetector/RWEControl.java +++ b/src/RightWhaleEdgeDetector/RWEControl.java @@ -7,22 +7,19 @@ import java.io.Serializable; import javax.swing.JMenuItem; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - import KernelSmoothing.KernelSmoothingProcess; import PamController.PamControlledUnit; import PamController.PamControlledUnitSettings; -import PamController.PamController; import PamController.PamControllerInterface; import PamController.PamSettingManager; import PamController.PamSettings; import PamUtils.PamUtils; -import PamView.GroupedDataSource; import PamView.GroupedSourceParameters; import PamView.dialog.GroupedSourcePanel; import PamguardMVC.PamDataBlock; import PamguardMVC.ProcessAnnotation; +import RightWhaleEdgeDetector.graphics.RWEDataPlotProviderFX; +import dataPlotsFX.data.TDDataProviderRegisterFX; /** * Exact implementation of the 2003 Right Whale detector I developed when I was @@ -43,6 +40,7 @@ public class RWEControl extends PamControlledUnit implements PamSettings { super("RW Edge Detector", unitName); rweProcess = new RWEProcess(this); addPamProcess(rweProcess); + PamSettingManager.getInstance().registerSettings(this); } diff --git a/src/RightWhaleEdgeDetector/RWEProcess.java b/src/RightWhaleEdgeDetector/RWEProcess.java index 081489c9..0a3dabc4 100644 --- a/src/RightWhaleEdgeDetector/RWEProcess.java +++ b/src/RightWhaleEdgeDetector/RWEProcess.java @@ -12,6 +12,7 @@ import Localiser.algorithms.timeDelayLocalisers.bearingLoc.BearingLocaliser; import Localiser.algorithms.timeDelayLocalisers.bearingLoc.BearingLocaliserSelector; import annotation.calcs.snr.SNRAnnotationType; import autecPhones.AutecGraphics; +import dataPlotsFX.data.TDDataProviderRegisterFX; import fftManager.Complex; import fftManager.FFTDataBlock; import fftManager.FFTDataUnit; @@ -33,6 +34,7 @@ import PamguardMVC.PamDataUnit; import PamguardMVC.PamObservable; import PamguardMVC.PamProcess; import PamguardMVC.debug.Debug; +import RightWhaleEdgeDetector.graphics.RWEDataPlotProviderFX; import RightWhaleEdgeDetector.graphics.RWESymbolManager; public class RWEProcess extends PamProcess { @@ -53,6 +55,7 @@ public class RWEProcess extends PamProcess { */ private boolean isPreSmoothed; private KernelSmoothing kernelSmoothing; + private RWEDataPlotProviderFX fxPlotProvider; public RWEProcess(RWEControl rweControl) { super(rweControl, null); @@ -71,6 +74,9 @@ public class RWEProcess extends PamProcess { rweDataBlock.setCanClipGenerate(true); rweDataBlock.SetLogging(new RWESQLLogging(rweControl, rweDataBlock)); rweDataBlock.addDataAnnotationType(new SNRAnnotationType()); + + fxPlotProvider = new RWEDataPlotProviderFX(this, rweDataBlock); + TDDataProviderRegisterFX.getInstance().registerDataInfo(fxPlotProvider); } @Override @@ -262,7 +268,7 @@ public class RWEProcess extends PamProcess { backgroundData[i] += (magData[i]-backgroundData[i])/updateConstant[0]; } if (Double.isNaN(backgroundData[i]) || Double.isInfinite(backgroundData[i])) { - System.out.println(String.format("Bad bg data slice %d = %3.5f", i, backgroundData[i])); +// System.out.println(String.format("Bad bg data slice %d = %3.5f", i, backgroundData[i])); backgroundData[i] = magData[i] * 10.; } } diff --git a/src/RightWhaleEdgeDetector/graphics/RWEDataPlotProviderFX.java b/src/RightWhaleEdgeDetector/graphics/RWEDataPlotProviderFX.java new file mode 100644 index 00000000..7f9e9fe9 --- /dev/null +++ b/src/RightWhaleEdgeDetector/graphics/RWEDataPlotProviderFX.java @@ -0,0 +1,25 @@ +package RightWhaleEdgeDetector.graphics; + +import RightWhaleEdgeDetector.RWEDataBlock; +import RightWhaleEdgeDetector.RWEProcess; +import dataPlotsFX.data.TDDataInfoFX; +import dataPlotsFX.data.TDDataProviderFX; +import dataPlotsFX.layout.TDGraphFX; + +public class RWEDataPlotProviderFX extends TDDataProviderFX { + + private RWEDataBlock rweDataBlock; + private RWEProcess rweProcess; + + public RWEDataPlotProviderFX(RWEProcess rweProcess, RWEDataBlock rweDataBlock) { + super(rweDataBlock); + this.rweProcess = rweProcess; + this.rweDataBlock = rweDataBlock; + } + + @Override + public TDDataInfoFX createDataInfo(TDGraphFX tdGraph) { + return new RWEDataPlotinfoFX(this, rweProcess, tdGraph, rweDataBlock); + } + +} diff --git a/src/RightWhaleEdgeDetector/graphics/RWEDataPlotinfoFX.java b/src/RightWhaleEdgeDetector/graphics/RWEDataPlotinfoFX.java new file mode 100644 index 00000000..f7a30dfc --- /dev/null +++ b/src/RightWhaleEdgeDetector/graphics/RWEDataPlotinfoFX.java @@ -0,0 +1,168 @@ +package RightWhaleEdgeDetector.graphics; + +import PamUtils.Coordinate3d; +import PamView.GeneralProjector.ParameterType; +import PamView.GeneralProjector.ParameterUnits; +import PamView.symbol.PamSymbolChooser; +import PamguardMVC.PamDataUnit; +import RightWhaleEdgeDetector.RWEDataBlock; +import RightWhaleEdgeDetector.RWEDataUnit; +import RightWhaleEdgeDetector.RWEProcess; +import RightWhaleEdgeDetector.RWESound; +import dataPlots.data.TDSymbolChooser; +import dataPlotsFX.SimpleSymbolChooserFX; +import dataPlotsFX.TDSymbolChooserFX; +import dataPlotsFX.data.TDDataInfoFX; +import dataPlotsFX.data.TDScaleInfo; +import dataPlotsFX.data.generic.GenericDataPlotInfo; +import dataPlotsFX.data.generic.GenericSettingsPane; +import dataPlotsFX.layout.TDGraphFX; +import dataPlotsFX.layout.TDSettingsPane; +import dataPlotsFX.projector.TDProjectorFX; +import fftManager.FFTDataBlock; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import pamViewFX.fxNodes.PamSymbolFX; + +public class RWEDataPlotinfoFX extends GenericDataPlotInfo { + + private RWEDataBlock rweDataBlock; + private TDScaleInfo bearingScaleInfo; + private TDScaleInfo frequencyInfo; + private RWEProcess rweProcess; + +// private SimpleSymbolChooserFX symbolChooser = new SimpleSymbolChooserFX(); + private GenericSettingsPane settingsPane; + + public RWEDataPlotinfoFX(RWEDataPlotProviderFX tdDataProvider, RWEProcess rweProcess, TDGraphFX tdGraph, RWEDataBlock rweDataBlock) { + super(tdDataProvider, tdGraph, rweDataBlock); + this.rweProcess = rweProcess; + this.rweDataBlock = rweDataBlock; + + bearingScaleInfo = new TDScaleInfo(0,180, ParameterType.BEARING, ParameterUnits.DEGREES); + bearingScaleInfo.setReverseAxis(true); //set the axis to be reverse so 0 is at top of graph + frequencyInfo = new TDScaleInfo(0, 1, ParameterType.FREQUENCY, ParameterUnits.HZ); + this.getScaleInfos().add(bearingScaleInfo); + this.getScaleInfos().add(frequencyInfo); + + //set correct frequency range based on nyquist. + frequencyInfo.setMaxVal(rweDataBlock.getSampleRate()/2.); + + + settingsPane = new GenericSettingsPane(this); + settingsPane.setShowingName("Right Whale"); +// settingsPane.setIcon(tdGraph) + } + + @Override + public Double getDataValue(PamDataUnit pamDataUnit) { + if (pamDataUnit.getLocalisation() == null) { + return null; + } + double[] angles = pamDataUnit.getLocalisation().getAngles(); + if (angles != null && angles.length > 0) { + return Math.toDegrees(angles[0]); + } + return null; + } + + @Override + public TDScaleInfo getScaleInfo() { + + setNPlotPanes(frequencyInfo, this.getDataBlock(), false); + + double min = Math.min(bearingScaleInfo.getMinVal(), bearingScaleInfo.getMaxVal()); + double max = Math.max(bearingScaleInfo.getMinVal(), bearingScaleInfo.getMaxVal()); + + bearingScaleInfo.setMaxVal(max); + bearingScaleInfo.setMinVal(min); + + return super.getScaleInfo(); + } + +// @Override +// public TDSymbolChooserFX getSymbolChooser() { +// return symbolChooser; +// } + + @Override + public Polygon drawDataUnit(int plotNumber, PamDataUnit pamDataUnit, GraphicsContext g, double scrollStart, TDProjectorFX tdProjector,int type) { + // if drawing FFT then need to use slight more complex drawing functions. + if (getScaleInfoIndex()==3) { + return drawRWContour(plotNumber, pamDataUnit, g, scrollStart, tdProjector, type); + } + else { + return super.drawDataUnit(plotNumber, pamDataUnit, g, scrollStart, tdProjector ,type); + } +// return null; + } + + private Polygon drawRWContour(int plotNumber, PamDataUnit pamDataUnit, GraphicsContext g, double scrollStart, + TDProjectorFX tdProjector, int type) { + if (!shouldDraw(plotNumber, pamDataUnit)){ + //System.out.println("Cannot plot whistle"); +// iCol++; + return null; + } + RWEDataUnit rweDataUnit = (RWEDataUnit) pamDataUnit; + RWESound rweSound = rweDataUnit.rweSound; + int[] hf = rweSound.highFreq; + int[] lf = rweSound.lowFreq; + int[] pf = rweSound.peakFreq; + + FFTDataBlock dataSource = (FFTDataBlock) rweProcess.getParentDataBlock(); + if (dataSource == null) { + return null; + } + double fs = dataSource.getSampleRate(); + int fftLen = dataSource.getFftLength(); + int fftHop = dataSource.getFftHop(); + + TDSymbolChooserFX symbols = getSymbolChooser(); + if (symbols != null) { + PamSymbolFX symbFX = symbols.getPamSymbol(rweDataUnit, TDSymbolChooser.NORMAL_SYMBOL); + if (symbFX != null) { + g.setStroke(symbFX.getLineColor()); +// g.setStroke(Color.ALICEBLUE); + } + } + + +// Polygon outer = new Poly + int nOut = hf.length*2; + double[] outsideX = new double[nOut]; + double[] outsideY = new double[nOut]; + for (int i = 0; i < hf.length; i++) { + long t = (long) (i*1000*fftHop/fs)+rweDataUnit.getTimeMilliseconds(); + double f = lf[i]*fs/fftLen; + double tdPix = tdProjector.getTimePix(t-scrollStart); + double fPix = tdProjector.getYPix(f); +// Coordinate3d coord = tdProjector.getCoord3d(i, t, f); + outsideX[i] = tdPix; + outsideY[i] = fPix; + + f = hf[i]*fs/fftLen; +// coord = tdProjector.getCoord3d(i, t, f); + fPix = tdProjector.getYPix(f); + outsideX[nOut-1-i] = tdPix; + outsideY[nOut-1-i] = fPix; + + } + g.strokePolygon(outsideX, outsideY, nOut); + double topX = outsideX[hf.length]; + double topY = outsideY[hf.length]; + String txt = String.format("%d", rweSound.soundType); + g.strokeText(txt, topX, topY); + + + return null; + } + + @Override + public TDSettingsPane getGraphSettingsPane() { + if (settingsPane == null) { + } + return settingsPane; + } +} diff --git a/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java b/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java index 3577bd12..caff472e 100644 --- a/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java +++ b/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java @@ -29,6 +29,7 @@ public class RWTethysDataProvider extends AutoTethysProvider { Parameters parameters = detection.getParameters(); parameters.setScore((double) rweDataUnit.rweSound.soundType); double snr = 20.*Math.log10(rweDataUnit.rweSound.signal/rweDataUnit.rweSound.noise); + snr = AutoTethysProvider.roundDecimalPlaces(snr, 1); parameters.setSNRDB(snr); return detection; diff --git a/src/binaryFileStorage/BinaryMapMakeProgress.java b/src/binaryFileStorage/BinaryMapMakeProgress.java index 02e13578..3cb8a10e 100644 --- a/src/binaryFileStorage/BinaryMapMakeProgress.java +++ b/src/binaryFileStorage/BinaryMapMakeProgress.java @@ -87,42 +87,30 @@ public class BinaryMapMakeProgress extends PamTaskUpdate { @Override public double getProgress(){ - //System.out.println("BinaryProgress: " + currentStream + " tot: " + totalStreams); +// System.out.println("BinaryProgress: " + currentStream + " of: " + totalStreams + " status: " + getStatus()); if (totalStreams==0) return 1.; double progress = 0; switch (getStatus()){ - case PamTaskUpdate.STATUS_ANALYSING_FILES: - if (totalStreams==0) progress=0; + case STATUS_ANALYSING_FILES: + if (totalStreams==0) progress=-1; else progress=((double) currentStream)/totalStreams; break; - case PamTaskUpdate.STATUS_COUNTING_FILES: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_DESERIALIZING: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_DONE: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_DONE_ERROR: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_IDLE: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_SERIALIZING: - progress=ProgressIndicator.INDETERMINATE_PROGRESS; - break; - case PamTaskUpdate.STATUS_SORTING: + default: progress=ProgressIndicator.INDETERMINATE_PROGRESS; break; } return progress; } + @Override public String getName() { return "Binary Data Map"; } + + @Override + public String getProgressString() { + return this.streamName; + } } diff --git a/src/binaryFileStorage/BinaryStore.java b/src/binaryFileStorage/BinaryStore.java index 96b89b3f..1785eb6f 100644 --- a/src/binaryFileStorage/BinaryStore.java +++ b/src/binaryFileStorage/BinaryStore.java @@ -37,6 +37,7 @@ import dataMap.OfflineDataMap; import dataMap.OfflineDataMapPoint; import PamController.AWTScheduler; import PamController.DataInputStore; +import PamController.DataIntegrityChecker; import PamController.DataOutputStore; import PamController.OfflineDataStore; import PamController.PamControlledUnit; @@ -74,6 +75,7 @@ import annotation.binary.AnnotationBinaryData; import annotation.handler.AnnotationHandler; import backupmanager.BackupInformation; import binaryFileStorage.backup.BinaryBackupStream; +import binaryFileStorage.checker.BinaryIntegrityChecker; import binaryFileStorage.layoutFX.BinaryStoreGUIFX; /** @@ -946,6 +948,8 @@ PamSettingsSource, DataOutputStore { PamController.getInstance().notifyModelChanged(PamControllerInterface.CHANGED_OFFLINE_DATASTORE); // System.out.println("BinaryDataMapMaker really done " + this); dataMapMaker = null; + + getInegrityChecker().checkDataStore(); } @Override @@ -2593,5 +2597,9 @@ PamSettingsSource, DataOutputStore { public String getDataLocation() { return binaryStoreSettings.getStoreLocation(); } + @Override + public DataIntegrityChecker getInegrityChecker() { + return new BinaryIntegrityChecker(this); + } } diff --git a/src/binaryFileStorage/checker/BinaryIntegrityChecker.java b/src/binaryFileStorage/checker/BinaryIntegrityChecker.java new file mode 100644 index 00000000..18b182d7 --- /dev/null +++ b/src/binaryFileStorage/checker/BinaryIntegrityChecker.java @@ -0,0 +1,56 @@ +package binaryFileStorage.checker; + +import java.util.ArrayList; + +import PamController.DataIntegrityChecker; +import PamController.PamController; +import PamUtils.PamCalendar; +import PamView.dialog.warn.WarnOnce; +import PamguardMVC.PamDataBlock; +import binaryFileStorage.BinaryStore; +import dataMap.MapOverlap; +import dataMap.OfflineDataMap; + +public class BinaryIntegrityChecker implements DataIntegrityChecker { + + private BinaryStore binaryStore; + + public BinaryIntegrityChecker(BinaryStore binaryStore) { + this.binaryStore = binaryStore; + } + + public boolean checkDataStore() { + checkMapOverlaps(); + + + return true; + } + + private boolean checkMapOverlaps() { + boolean ok = true; + ArrayList dataBlocks = binaryStore.getStreamingDataBlocks(false); + for (PamDataBlock aBlock : dataBlocks) { + ok &= checkMapOverlaps(aBlock); + } + return ok; + } + + private boolean checkMapOverlaps(PamDataBlock aBlock) { + OfflineDataMap dataMap = aBlock.getOfflineDataMap(binaryStore); + if (dataMap == null) { + return true; + } + ArrayList overlaps = dataMap.checkOverlaps(); + if (overlaps == null || overlaps.size() == 0) { + return true; + } + String warn = String.format("Binary data %s has %d overlapping data files, the first one at %s to %s
" + + "This can occur when data have been reprocessed multiple times offline into " + + "the same folders.
Since files sometimes get slightly different names, old files are" + + "not always overwritten.
" + + "You should determine which files are 'old' by looking at file creation dates and delete them.", + aBlock.getLongDataName(), overlaps.size(), PamCalendar.formatDBDateTime(overlaps.get(0).getFile1End()), PamCalendar.formatDBDateTime(overlaps.get(0).getFile2Start())); + WarnOnce.showNamedWarning("BINOVERLAPWARNING", PamController.getMainFrame(), "Data Integrity Warning", warn, WarnOnce.WARNING_MESSAGE); + return false; + } +} diff --git a/src/clickDetector/ClickClassifiers/basicSweep/SweepClassifierWorker.java b/src/clickDetector/ClickClassifiers/basicSweep/SweepClassifierWorker.java index 6a52b01c..15af8d1d 100644 --- a/src/clickDetector/ClickClassifiers/basicSweep/SweepClassifierWorker.java +++ b/src/clickDetector/ClickClassifiers/basicSweep/SweepClassifierWorker.java @@ -629,7 +629,8 @@ public class SweepClassifierWorker { * @return true if bearing limits are passed */ private boolean testBearings(ClickDetection click, SweepClassifierSet scs) { - + if (click.getLocalisation()==null) return true; + if (click.getLocalisation().getAngles()==null) return true; //passes the test if there is no bearing info. double bearing = click.getLocalisation().getAngles()[0]; diff --git a/src/clickDetector/ClickTemplate.java b/src/clickDetector/ClickTemplate.java index 61a4b4ef..3d778229 100644 --- a/src/clickDetector/ClickTemplate.java +++ b/src/clickDetector/ClickTemplate.java @@ -1,7 +1,6 @@ package clickDetector; import java.awt.Color; -import java.awt.Paint; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; diff --git a/src/clickDetector/ClickTrainDetector.java b/src/clickDetector/ClickTrainDetector.java index a0141e03..8bfe6bb3 100644 --- a/src/clickDetector/ClickTrainDetector.java +++ b/src/clickDetector/ClickTrainDetector.java @@ -58,7 +58,6 @@ public class ClickTrainDetector extends PamProcess implements PamSettings { this.clickDataBlock = clickDataBlock; - PamSettingManager.getInstance().registerSettings(this); clickDataBlock.addObserver(this); diff --git a/src/clickDetector/dataSelector/ClickDataSelector.java b/src/clickDetector/dataSelector/ClickDataSelector.java index a7a1d4d8..5be77c2a 100644 --- a/src/clickDetector/dataSelector/ClickDataSelector.java +++ b/src/clickDetector/dataSelector/ClickDataSelector.java @@ -112,14 +112,23 @@ public class ClickDataSelector extends DataSelector { // see if there is a super detection and see if it's got a comment. String comment = oev.getComment(); + + boolean isAutomatic = false; if (comment != null) { - if (clickAlarmParameters.onlineAutoEvents && comment.startsWith("Automatic")) { - return true; - } - if (clickAlarmParameters.onlineManualEvents && comment.startsWith("Manual")) { - return true; - } + isAutomatic = comment.startsWith("Automatic"); } + if (isAutomatic && clickAlarmParameters.onlineAutoEvents) { + return true; + } + else if (clickAlarmParameters.onlineManualEvents) { + return true; + } +// if (clickAlarmParameters.onlineAutoEvents && comment.startsWith("Automatic")) { +// return true; +// } +// if (clickAlarmParameters.onlineManualEvents && comment.startsWith("Manual")) { +// return true; +// } /* * Otherwise need to work out where the hell the event type is in the * list of event types and see if it's wanted. diff --git a/src/clickDetector/dataSelector/ClickSelectPanel.java b/src/clickDetector/dataSelector/ClickSelectPanel.java index 859ee841..512e088d 100644 --- a/src/clickDetector/dataSelector/ClickSelectPanel.java +++ b/src/clickDetector/dataSelector/ClickSelectPanel.java @@ -30,6 +30,8 @@ import PamView.dialog.PamDialog; import PamView.dialog.PamDialogPanel; import PamView.dialog.PamGridBagContraints; import PamView.panel.PamAlignmentPanel; +import PamView.panel.SeparatorBorder; +import PamView.panel.WestAlignedPanel; public class ClickSelectPanel implements PamDialogPanel { @@ -40,6 +42,8 @@ public class ClickSelectPanel implements PamDialogPanel { private ClickDataSelector clickDataSelector; private JPanel mainPanel; private boolean isViewer; + + public static final String mainTip = "You should select options in both the Click Type and the Event Type panels"; public ClickSelectPanel(ClickDataSelector clickDataSelector, boolean allowScores, boolean useEventTypes) { this.clickDataSelector = clickDataSelector; @@ -79,7 +83,9 @@ public class ClickSelectPanel implements PamDialogPanel { private LookupList lutList; public EventTypePanel() { - setBorder(new TitledBorder("Event Type Selection")); + setBorder(new SeparatorBorder("Event Type Selection")); + + setToolTipText(mainTip); } void setParams() { @@ -102,16 +108,24 @@ public class ClickSelectPanel implements PamDialogPanel { boxPanel.add(onlineAuto = new JCheckBox("Automatically detected click trains"), c); onlineAuto.setSelected(clickAlarmParameters.onlineAutoEvents); + useUnassigned.setToolTipText("Clicks that are NOT part of a manual or automatic click train"); + onlineManual.setToolTipText("Clicks that are part of a manually marked click train"); + onlineAuto.setToolTipText("Clicks that are part of an automatically detected click train"); + lutList = LookUpTables.getLookUpTables().getLookupList(ClicksOffline.ClickTypeLookupName); if (lutList == null) { return; } + c.gridy++; + boxPanel.add(new JLabel("OR the following click train types ...", JLabel.LEFT), c); useType = new JCheckBox[lutList.getList().size()]; for (int i = 0; i < useType.length; i++) { c.gridy++; boxPanel.add(useType[i] = new JCheckBox(lutList.getList().get(i).getText()), c); useType[i].setSelected(clickAlarmParameters.isUseEventType(lutList.getList().get(i).getCode())); + String tip = String.format("Clicks that are part of a click train labelled as %s", lutList.getList().get(i).getText()); + useType[i].setToolTipText(tip); } } @@ -158,10 +172,9 @@ public class ClickSelectPanel implements PamDialogPanel { northPanel = new JPanel(); northPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new PamGridBagContraints(); - northPanel.setBorder(new TitledBorder("Echoes")); c.gridwidth = 3; c.anchor = GridBagConstraints.WEST; - northPanel.add(useEchoes = new JCheckBox("Use Echoes"), c); + northPanel.add(new PamAlignmentPanel(useEchoes = new JCheckBox("Use Echoes"), BorderLayout.WEST), c); c.gridwidth = 1; c.gridy++; c.gridx = 0; @@ -178,11 +191,13 @@ public class ClickSelectPanel implements PamDialogPanel { northPanel.add(scoreByAmplitude = new JCheckBox("Score by amplitude"), c); scoreByAmplitude.setVisible(allowScores); scoreByAmplitude.addActionListener(new AllSpeciesListener()); - add(BorderLayout.NORTH, northPanel); + WestAlignedPanel walpn; + add(BorderLayout.NORTH, walpn = new WestAlignedPanel(northPanel)); + walpn.setBorder(new SeparatorBorder("Echoes")); JPanel centralOuterPanel = new JPanel(new BorderLayout()); centralPanel.setLayout(new GridBagLayout()); - centralOuterPanel.setBorder(new TitledBorder("Click Type Selection")); + centralOuterPanel.setBorder(new SeparatorBorder("Click Type Selection")); add(BorderLayout.CENTER, centralOuterPanel); JScrollPane scrollPane = new DialogScrollPane(new PamAlignmentPanel(centralPanel, BorderLayout.WEST), 10); @@ -197,6 +212,9 @@ public class ClickSelectPanel implements PamDialogPanel { clearAll.addActionListener(new AutoSelect(false)); centralOuterPanel.add(BorderLayout.SOUTH, new PamAlignmentPanel(centralEastPanel, BorderLayout.WEST)); + centralOuterPanel.setToolTipText(mainTip); + + setToolTipText(mainTip); } void setParams() { diff --git a/src/clickDetector/layoutFX/ClickSettingsPane.java b/src/clickDetector/layoutFX/ClickSettingsPane.java index a62f3be0..1f10f122 100644 --- a/src/clickDetector/layoutFX/ClickSettingsPane.java +++ b/src/clickDetector/layoutFX/ClickSettingsPane.java @@ -7,20 +7,18 @@ import clickDetector.ClickControl; import clickDetector.ClickParameters; import clickDetector.layoutFX.clickClassifiers.ClickClassifyPaneFX; import javafx.geometry.HPos; +import javafx.geometry.Insets; import javafx.geometry.Orientation; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.Spinner; import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; -import javafx.scene.paint.Color; -import javafx.scene.shape.Line; +import javafx.scene.layout.Region; import javafx.util.StringConverter; import net.synedra.validatorfx.Validator; import PamController.PamController; @@ -31,12 +29,12 @@ import PamguardMVC.PamConstants; import PamguardMVC.PamDataBlock; import PamguardMVC.PamRawDataBlock; import pamViewFX.PamGuiManagerFX; -import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamSpinner; -import pamViewFX.fxNodes.PamTabPane; import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.navigationDrawer.NavigationDrawer; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; import pamViewFX.fxNodes.utilityPanes.FilterPaneFX; import pamViewFX.fxNodes.utilityPanes.GroupedSourcePaneFX; @@ -68,7 +66,7 @@ public class ClickSettingsPane extends SettingsPane{ /** * The main tab pane. */ - private PamTabPane pamTabbedPane; + private NavigationDrawer pamTabbedPane; /** * Pane for the pre-filter. The acoustic data detected click waveforms are extracted from but not @@ -163,10 +161,6 @@ public class ClickSettingsPane extends SettingsPane{ */ private Tab tdoaTab; - /** - * The main holder pane. - */ - private PamBorderPane mainPane; /** * The default pane height. @@ -176,7 +170,7 @@ public class ClickSettingsPane extends SettingsPane{ /** * The default pane width */ - public static double PREF_PANE_WIDTH=560; + public static double PREF_PANE_WIDTH=750; /** @@ -188,14 +182,14 @@ public class ClickSettingsPane extends SettingsPane{ public ClickSettingsPane(ClickControl clickControl){ super(null); - this.clickControl=clickControl; - mainPane= new PamBorderPane(); + this.clickControl=clickControl; clickValidator = new PamValidator(); - pamTabbedPane=new PamTabPane(); - pamTabbedPane.setAddTabButton(false); - pamTabbedPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + pamTabbedPane=new NavigationDrawer(); +// pamTabbedPane.setAddTabButton(false); +// pamTabbedPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); +// pamTabbedPane.setStyle("-fx-background-color: blue;"); //create a combined detection and length pane PamVBox detectionPane=new PamVBox(); @@ -211,9 +205,18 @@ public class ClickSettingsPane extends SettingsPane{ detectionPane.getChildren().add(createTriggerGraph()); //add everything to tabs. - pamTabbedPane.getTabs().add(new Tab("Click Detection", detectionPane)); + Tab clickDetectionTabe =new Tab("Click Detection", detectionPane); + clickDetectionTabe.setGraphic(PamGlyphDude.createPamIcon("mdi2w-waveform")); + detectionPane.setPadding(new Insets(5,5,5,5)); + pamTabbedPane.getTabs().add(clickDetectionTabe); + + clickDelayPane=createDelayPane(); - pamTabbedPane.getTabs().add(tdoaTab=new Tab("TDOA and Echoes", clickDelayPane.getContentNode())); + ((Region) clickDelayPane.getContentNode()).setPadding(new Insets(5,5,5,5)); + tdoaTab=new Tab("TDOA and Echoes", clickDelayPane.getContentNode()); + tdoaTab.setGraphic(PamGlyphDude.createPamIcon("mdi2a-av-timer")); + pamTabbedPane.getTabs().add(tdoaTab); + tdoaTab.setOnSelectionChanged((event)->{ if (pamTabbedPane.getSelectionModel().getSelectedItem()==tdoaTab){ //System.out.println("clickDelayPane: "+clickDelayPane); @@ -225,11 +228,20 @@ public class ClickSettingsPane extends SettingsPane{ //pre filter pane. preFilter=new FilterPaneFX(Orientation.VERTICAL); - pamTabbedPane.getTabs().add(new Tab("Pre Filter", preFilter.getContentNode())); + ((Region) preFilter.getContentNode()).setPadding(new Insets(5,5,5,5)); + Tab preFilterTab =new Tab("Pre Filter", preFilter.getContentNode()); + preFilterTab.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chart-bell-curve-cumulative")); + pamTabbedPane.getTabs().add(preFilterTab); //trigger pane triggerFilter=new FilterPaneFX(Orientation.VERTICAL); - pamTabbedPane.getTabs().add(new Tab("Trigger Filter", triggerFilter.getContentNode())); + ((Region) triggerFilter.getContentNode()).setPadding(new Insets(5,5,5,5)); + Tab triggerFilterTab =new Tab("Trigger Filter", triggerFilter.getContentNode()); + triggerFilterTab.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chart-bell-curve")); + pamTabbedPane.getTabs().add(triggerFilterTab); + + + // //echo detection pane. // echoDetection= new ClickEchoPane(clickControl); @@ -238,15 +250,20 @@ public class ClickSettingsPane extends SettingsPane{ /***Note: FX does not implment click train detection in click detector****/ //classifiaction pane. - pamTabbedPane.getTabs().add(new Tab("Classification", clickClassificationPane=new ClickClassifyPaneFX(clickControl))); + Tab classifierTab =new Tab("Classification", clickClassificationPane=new ClickClassifyPaneFX(clickControl)); + classifierTab.setGraphic(PamGlyphDude.createPamIcon("mdi2g-graph")); + pamTabbedPane.getTabs().add(classifierTab); - //want a slightly bigger pane as a lot going on in this dialog. - //Note JavaFX 8u61 + has auto DPI scaling so this is really the size of a dialog on a standard HD monitor of - //reasonable size, rather than actual pixels - mainPane.setPrefSize(PREF_PANE_WIDTH, PREF_PANE_HEIGHT); +// //want a slightly bigger pane as a lot going on in this dialog. +// //Note JavaFX 8u61 + has auto DPI scaling so this is really the size of a dialog on a standard HD monitor of +// //reasonable size, rather than actual pixels + pamTabbedPane.setMinSize(PREF_PANE_WIDTH, PREF_PANE_HEIGHT); //addTabListeners(); - mainPane.setCenter(new PamBorderPane(pamTabbedPane)); + pamTabbedPane.setPadding(new Insets(0,0,0,0)); + + + } // private void addTabListeners(){ @@ -890,7 +907,7 @@ public class ClickSettingsPane extends SettingsPane{ @Override public Node getContentNode() { - return mainPane; + return pamTabbedPane; } @Override diff --git a/src/clickDetector/layoutFX/clickClassifiers/ClickTypeProperty.java b/src/clickDetector/layoutFX/clickClassifiers/ClickTypeProperty.java index 23b41615..83370efd 100644 --- a/src/clickDetector/layoutFX/clickClassifiers/ClickTypeProperty.java +++ b/src/clickDetector/layoutFX/clickClassifiers/ClickTypeProperty.java @@ -11,6 +11,7 @@ import javafx.beans.property.StringProperty; /** * A property wrapper for a basic click with JavaFX properties. This is used so JavaFX controls * automatically change. + * * @author Jamie Macaulay * */ @@ -23,11 +24,12 @@ public class ClickTypeProperty { public ClickTypeProperty(ClickTypeCommonParams clickType){ - this.commonClickParams=clickType; - name.setValue(clickType.getName()); - discardClassifier.setValue(clickType.getDiscard()); - code.setValue(clickType.getSpeciesCode()); - +// this.commonClickParams=clickType; +// name.setValue(clickType.getName()); +// discardClassifier.setValue(clickType.getDiscard()); +// code.setValue(clickType.getSpeciesCode()); + setClickType( clickType); + } public StringProperty name= new SimpleStringProperty("blank"); @@ -47,7 +49,7 @@ public class ClickTypeProperty { this.commonClickParams = clickType; //TODO-need to set properties; name.setValue(clickType.getName()); - enableClassifier.setValue(clickType.enable); + enableClassifier.setValue(clickType.getEnable()); discardClassifier.setValue(clickType.getDiscard()); code.setValue(clickType.getSpeciesCode()); } diff --git a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java index ba4fbf5e..a300684c 100644 --- a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java +++ b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierPaneFX.java @@ -52,8 +52,7 @@ public class SweepClassifierPaneFX extends BasicIdentifierPaneFX { getFlipPane().getAdvLabel().textProperty().unbind(); getFlipPane().getAdvLabel().textProperty().bind(sweepPane.getNameTextProperty()); - // removed DG 2023 12 14 since not everything was in this merge that was required Needs to be put back. -// getFlipPane().getPreAdvLabel().graphicProperty().bind(sweepPane.getNameGraphicProperty()); + getFlipPane().getPreAdvLabel().graphicProperty().bind(sweepPane.getNameGraphicProperty()); sweepPane.classifierItemRow = sweepClickClassifier.getSweepClassifierParams().getSetRow((SweepClassifierSet) clickTypeProperty.getClickType()); diff --git a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierSetPaneFX.java b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierSetPaneFX.java index a86f27f5..b464813a 100644 --- a/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierSetPaneFX.java +++ b/src/clickDetector/layoutFX/clickClassifiers/SweepClassifierSetPaneFX.java @@ -4,9 +4,12 @@ package clickDetector.layoutFX.clickClassifiers; import fftFilter.FFTFilterParams; import fftManager.FFTLengthModeled; import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Orientation; +import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.CheckBox; @@ -19,9 +22,11 @@ import javafx.scene.control.TabPane; import javafx.scene.control.TabPane.TabClosingPolicy; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.text.Font; +import javafx.scene.canvas.Canvas; import net.synedra.validatorfx.GraphicDecoration; import net.synedra.validatorfx.ValidationMessage; import net.synedra.validatorfx.Validator; @@ -29,6 +34,7 @@ import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamSpinner; +import pamViewFX.fxNodes.PamSymbolFX; import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; import pamViewFX.fxNodes.picker.SymbolPicker; @@ -39,19 +45,6 @@ import pamViewFX.fxNodes.utilsFX.PamUtilsFX; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.util.ArrayList; import java.util.List; @@ -60,7 +53,6 @@ import javax.swing.JPanel; import PamController.SettingsPane; import PamUtils.PamUtils; import PamView.PamSymbol; -import PamView.dialog.PamGridBagContraints; import PamView.symbol.SymbolData; import clickDetector.ClickControl; import clickDetector.ClickClassifiers.basicSweep.CodeHost; @@ -125,13 +117,17 @@ public class SweepClassifierSetPaneFX extends SettingsPane { */ private AmplitudeBlock amplitudeBlock; - /** - * Changes the bearing paramters fro clicks. + * Changes the bearing parameters for clicks. */ private BearingBlock bearingBox; + + /** + * Allows classification based on correlation scores. + */ + private XCorrBlock xCorrBox; - + private PamBorderPane mainPane = new PamBorderPane(); @@ -183,15 +179,17 @@ public class SweepClassifierSetPaneFX extends SettingsPane { spectrumHolder.setPadding(new Insets(10,0,0,0)); spectrumTab.setContent(spectrumHolder); - /*********Spectrum Tab****************/ + /*********Bearing Tab****************/ Tab bearingTab=new Tab("Bearing"); - spectrumTab.setGraphic(PamGlyphDude.createPamIcon("mdi2c-compass-outline", PamGuiManagerFX.iconSize)); + bearingTab.setGraphic(PamGlyphDude.createPamIcon("mdi2c-compass-outline", PamGuiManagerFX.iconSize)); + bearingTab.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); PamVBox bearingHolder=new PamVBox(5); bearingBox=new BearingBlock(); - - bearingHolder.getChildren().addAll(bearingBox); + xCorrBox=new XCorrBlock(); + + bearingHolder.getChildren().addAll(bearingBox, xCorrBox); bearingHolder.setPadding(new Insets(10,0,0,0)); bearingTab.setContent(bearingHolder); @@ -200,7 +198,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { TabPane tabPane= new TabPane(); tabPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); - tabPane.getTabs().addAll(waveformTab, spectrumTab); + tabPane.getTabs().addAll(waveformTab, spectrumTab, bearingTab); holder.getChildren().add(optionBox); holder.getChildren().add(tabPane); @@ -237,7 +235,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { private Font disableFont; private Label label; - + SweepBox(String borderTitle, Boolean enableButton) { @@ -279,6 +277,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { } this.setTop(hBox); + //little bit of space between the top and bottom button.m + BorderPane.setMargin(hBox, new Insets(0,0,5,0)); + /**Don't like this in old swing version*/ //tP.setCenter( description = new Label("", JLabel.CENTER)); @@ -356,12 +357,19 @@ public class SweepClassifierSetPaneFX extends SettingsPane { /** - * General options for the sweep classifier set + * General options for the sweep classifier set. This inlcudes, name, symbol, and some basics on + * what part of the click to analyse. + * * @author Jamie Macaulay * */ private class OptionsBox extends SweepBox implements FFTLengthModeled, CodeHost { + /** + * The size of the preview symbol to show next to the title + */ + private static final double TITLE_SYMBOL_SIZE = 20.; + /** * Text field to set classifier name. */ @@ -396,6 +404,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { * Shows lengths of extraction samples in millis. */ private Label lengthMS; + + /** + * The property for the PAMSymbol + */ + private SimpleObjectProperty pamSymbolProperty = new SimpleObjectProperty(); + + private PamSymbolFX currentSymbol = new PamSymbolFX(); private ComboBox lengthTypeBox; @@ -414,6 +429,8 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //create the general options private Node createOptionsPane(){ + //set the canvas for the canvas property. + pamSymbolProperty.set(new Canvas(25,25)); PamGridPane pamGridPane=new PamGridPane(); pamGridPane.setHgap(5); @@ -453,6 +470,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //create colour picker to allow users to change symbol colour. symbolPicker=new SymbolPicker(); + symbolPicker.setOnAction((action)->{ + drawSymbolProperty(); + }); pamGridPane.add(symbolPicker, 3,1); pamGridPane.add(new Label("Symbol"), 2,1); @@ -461,6 +481,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { lineColourPicker.setStyle("-fx-color-label-visible: false ;"); lineColourPicker.setOnAction((action)->{ symbolPicker.setLineColour(lineColourPicker.getValue()); + drawSymbolProperty(); }); pamGridPane.add(lineColourPicker, 4, 1); @@ -469,6 +490,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { fillColourPicker.setStyle("-fx-color-label-visible: false ;"); fillColourPicker.setOnAction((action)->{ symbolPicker.setFillColour(fillColourPicker.getValue()); + drawSymbolProperty(); }); pamGridPane.add(fillColourPicker, 5, 1); @@ -543,6 +565,21 @@ public class SweepClassifierSetPaneFX extends SettingsPane { return holder; } + private void drawSymbolProperty() { + + this.pamSymbolProperty.get().getGraphicsContext2D().clearRect(0, 0, TITLE_SYMBOL_SIZE, TITLE_SYMBOL_SIZE); + + currentSymbol.setSymbol(this.symbolPicker.getValue() == null ? null : this.symbolPicker.getValue().getSymbol()); + currentSymbol.setLineColor(this.lineColourPicker.getValue()); + currentSymbol.setFillColor(this.fillColourPicker.getValue()); + + if (currentSymbol.getSymbol()!=null) { + currentSymbol.draw(this.pamSymbolProperty.get().getGraphicsContext2D(), new Point2D(TITLE_SYMBOL_SIZE/2,TITLE_SYMBOL_SIZE/2), TITLE_SYMBOL_SIZE, TITLE_SYMBOL_SIZE); + } + + this.pamSymbolProperty.setValue(this.pamSymbolProperty.get()); + } + @Override public int getCode() { return codeSpinner.getValue(); @@ -575,11 +612,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { @Override protected void setParams() { + + //must have a symbol selected by default or a null symbol will be returned. + symbolPicker.getSelectionModel().select(0); //set basic data if (sweepClassifierSet == null) { //symbolViewer.setSymbol(null); - symbolPicker.getSelectionModel().select(0); nameField.setText(""); setCode(sweepClassifier.getNextFreeCode(0)); } @@ -619,6 +658,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { // //length stuff // clickLengthSpinner.getValueFactory().setValue(sweepClassifierSet.restrictedBins); + drawSymbolProperty(); } @@ -632,9 +672,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //get the symbol data. SymbolData symbolData = new SymbolData(this.symbolPicker.getValue().getSymbol(), 10,10,true, - PamUtilsFX.fxToAWTColor(this.lineColourPicker.getValue()), PamUtilsFX.fxToAWTColor(this.fillColourPicker.getValue())); + PamUtilsFX.fxToAWTColor(this.fillColourPicker.getValue()), PamUtilsFX.fxToAWTColor(this.lineColourPicker.getValue())); sweepClassifierSet.symbol= new PamSymbol(symbolData); - if (sweepClassifierSet.getName().length() <= 0) { + if (sweepClassifierSet.getName()==null || sweepClassifierSet.getName().length() <= 0) { return showWarning("You must enter a name for this type of click"); } sweepClassifierSet.setSpeciesCode(getCode()); @@ -675,6 +715,11 @@ public class SweepClassifierSetPaneFX extends SettingsPane { return this.nameField; } + public ObservableValue getNameGraphicsProperty() { + return pamSymbolProperty; + } + + } @@ -695,9 +740,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //create the general options private Node createOptionsPane(){ - PamGridPane pamGridPane=new PamGridPane(); - pamGridPane.setHgap(5); - pamGridPane.setVgap(5); +// PamGridPane pamGridPane=new PamGridPane(); +// pamGridPane.setHgap(5); +// pamGridPane.setVgap(5); simpleFilterPane=new SimpleFilterPaneFX(); @@ -831,7 +876,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { // PamGridPane.setFillWidth(clickLengthHolder2, false); - PamVBox vboxholder=new PamVBox(); + vboxholder=new PamVBox(); vboxholder.setSpacing(5); vboxholder.getChildren().addAll(clickLengthHolder1, clickLengthHolder2); @@ -921,7 +966,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { /** * Spinner for the first control band */ - private ArrayList> thresholdSpinners; + private ArrayList> thresholdSpinners; + + private PamGridPane gridPaneHolder; EnergyBandBox() { @@ -933,18 +980,18 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //create the general options private Node createOptionsPane(){ - PamGridPane pamGridPane=new PamGridPane(); - pamGridPane.setHgap(5); - pamGridPane.setVgap(5); + gridPaneHolder=new PamGridPane(); + gridPaneHolder.setHgap(5); + gridPaneHolder.setVgap(5); Label freqLabel=new Label("Frequency (Hz)"); -//// gridPaneHolder.add(freqLabel, 0, 0); -//// gridPaneHolder.add(new Label("Threshold (dB)"), 2, 0); -// PamGridPane.setHalignment(freqLabel, HPos.CENTER); -// PamGridPane.setColumnSpan(gridPaneHolder, 2); -// -// //test band -// gridPaneHolder.add(new Label("Test Band"), 0, 1); + gridPaneHolder.add(freqLabel, 0, 0); + gridPaneHolder.add(new Label("Threshold (dB)"), 2, 0); + PamGridPane.setHalignment(freqLabel, HPos.CENTER); + PamGridPane.setColumnSpan(gridPaneHolder, 2); + + //test band + gridPaneHolder.add(new Label("Test Band"), 0, 1); testBandFreqPane=new FreqBandPane(Orientation.HORIZONTAL); testBandFreqPane.setBandText(""); @@ -954,7 +1001,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { - pamGridPane.add(testBandFreqPane, 1, 1); + gridPaneHolder.add(testBandFreqPane, 1, 1); contralBandFreqPanes = new ArrayList(); @@ -962,13 +1009,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { for (int i=0; i(0,100.,6.,0)); thresholdSpinners.get(i).setEditable(true); - pamGridPane.add(thresholdSpinners.get(i), 2, i+2); + gridPaneHolder.add(thresholdSpinners.get(i), 2, i+2); thresholdSpinners.get(i).getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); thresholdSpinners.get(i).setMaxWidth(100); @@ -989,7 +1036,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { } - return pamGridPane; + return gridPaneHolder; } @@ -1073,11 +1120,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { @Override protected void disbleControls(boolean disable) { - testBandFreqPane.setDisableFreqPane(disable); - for (int j = 0; j < SweepClassifierSet.nControlBands; j++) { - contralBandFreqPanes.get(j).setDisableFreqPane(disable); - thresholdSpinners.get(j).setDisable(disable); - } + this.gridPaneHolder.setDisable(disable); + +// testBandFreqPane.setDisableFreqPane(disable); +// for (int j = 0; j < SweepClassifierSet.nControlBands; j++) { +// contralBandFreqPanes.get(j).setDisableFreqPane(disable); +// thresholdSpinners.get(j).setDisable(disable); +// } } } @@ -1116,34 +1165,34 @@ public class SweepClassifierSetPaneFX extends SettingsPane { int gridy=0; - PamGridPane pamGridPane=new PamGridPane(); - pamGridPane.setHgap(5); - pamGridPane.setVgap(5); + gridPaneHolder=new PamGridPane(); + gridPaneHolder.setHgap(5); + gridPaneHolder.setVgap(5); //search and integration range - pamGridPane.add(new Label("Search Range"),1,gridy); + gridPaneHolder.add(new Label("Search Range"),1,gridy); searchRange=new FreqBandPane(Orientation.HORIZONTAL); searchRange.setBandText(""); - pamGridPane.add(searchRange,2,gridy); + gridPaneHolder.add(searchRange,2,gridy); GridPane.setColumnSpan(searchRange, GridPane.REMAINING); gridy++; - pamGridPane.add(new Label("Smooth"), 1,gridy); + gridPaneHolder.add(new Label("Smooth"), 1,gridy); smoothing=new PamSpinner(3,101,5,2); smoothing.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); smoothing.setMaxWidth(100); - pamGridPane.add(smoothing, 2,gridy); + gridPaneHolder.add(smoothing, 2,gridy); GridPane.setMargin(smoothing, new Insets(0,0,0,5)); //bit of a hack to make sure everything lines up nicely with the frequency pane. //GridPane.setHgrow(smoothing, Priority.NEVER); - pamGridPane.add(new Label("bins"), 3,gridy); + gridPaneHolder.add(new Label("bins"), 3,gridy); gridy++; @@ -1153,14 +1202,14 @@ public class SweepClassifierSetPaneFX extends SettingsPane { peakFreqPane.setDisableFreqPane(!peakFreqCheckBox.isSelected()); }); - pamGridPane.add(peakFreqCheckBox,0,gridy); + gridPaneHolder.add(peakFreqCheckBox,0,gridy); - pamGridPane.add(new Label("Peak Frequency"),1,gridy); + gridPaneHolder.add(new Label("Peak Frequency"),1,gridy); peakFreqPane=new FreqBandPane(Orientation.HORIZONTAL); //peakFreqPane.setHgap(0); peakFreqPane.setBandText(""); - pamGridPane.add(peakFreqPane,2,gridy); + gridPaneHolder.add(peakFreqPane,2,gridy); GridPane.setColumnSpan(peakFreqPane, GridPane.REMAINING); @@ -1177,26 +1226,28 @@ public class SweepClassifierSetPaneFX extends SettingsPane { }); - pamGridPane.add(peakWidthCheckBox,0,gridy); - pamGridPane.add(new Label("Peak Width"),1,gridy); + gridPaneHolder.add(peakWidthCheckBox,0,gridy); + gridPaneHolder.add(new Label("Peak Width"),1,gridy); peakWidthPane=new FreqBandPane(Orientation.HORIZONTAL); peakWidthPane.setBandText(""); - pamGridPane.add(peakWidthPane,2,gridy); + gridPaneHolder.add(peakWidthPane,2,gridy); GridPane.setColumnSpan(peakWidthPane, GridPane.REMAINING); addValidatorFreqCheck(getValidator(), peakWidthPane, "peak width ", "peak_width"); gridy++; - pamGridPane.add(new Label(""), 1,gridy); + + gridPaneHolder.add(new Label(""), 1,gridy); threshold=new PamSpinner(1., 300., 6.,1.); threshold.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); threshold.setPrefWidth(100); GridPane.setMargin(threshold, new Insets(0,0,0,5)); //bit of a hack to make sure everything lines up nicely with the frequency pane. - pamGridPane.add(threshold,2,gridy); - pamGridPane.add(new Label("dB"), 3,gridy); + gridPaneHolder.add(threshold,2,gridy); + + gridPaneHolder.add(new Label("dB"), 3,gridy); gridy++; @@ -1207,19 +1258,19 @@ public class SweepClassifierSetPaneFX extends SettingsPane { meanFreq.setDisableFreqPane(!meanFreqCheckBox.isSelected()); }); - pamGridPane.add(meanFreqCheckBox,0,gridy); + gridPaneHolder.add(meanFreqCheckBox,0,gridy); - pamGridPane.add(new Label("Mean Frequency"),1,gridy); + gridPaneHolder.add(new Label("Mean Frequency"),1,gridy); meanFreq=new FreqBandPane(Orientation.HORIZONTAL); meanFreq.setBandText(""); - pamGridPane.add(meanFreq,2,gridy); + gridPaneHolder.add(meanFreq,2,gridy); GridPane.setColumnSpan(meanFreq, GridPane.REMAINING); addValidatorFreqCheck(getValidator(), meanFreq, "mean freq. ", "mean_freq"); - return pamGridPane; + return gridPaneHolder; } @@ -1235,8 +1286,8 @@ public class SweepClassifierSetPaneFX extends SettingsPane { } @Override -// protected void disbleControls(boolean disable) { -// this.gridPaneHolder.setDisable(disable); + protected void disbleControls(boolean disable) { + this.gridPaneHolder.setDisable(disable); // peakFreqCheckBox.setDisable(enable); // peakWidthCheckBox.setDisable(enable); @@ -1251,20 +1302,6 @@ public class SweepClassifierSetPaneFX extends SettingsPane { // threshold.setDisable(enable); // searchRange.setDisable(enable); // meanFreq.setDisable(enable); - protected void disbleControls(boolean enable) { - peakFreqCheckBox.setDisable(enable); - peakWidthCheckBox.setDisable(enable); - meanFreqCheckBox.setDisable(enable); - - - /** - * Pane to set frequency band range */ - peakFreqPane.setDisable(enable); - smoothing.setDisable(enable); - peakWidthPane.setDisable(enable); - threshold.setDisable(enable); - searchRange.setDisable(enable); - meanFreq.setDisable(enable); } } @@ -1310,27 +1347,27 @@ public class SweepClassifierSetPaneFX extends SettingsPane { private Node createZeroCrossPane(){ - PamGridPane pamGridPane=new PamGridPane(); - pamGridPane.setHgap(5); - pamGridPane.setVgap(5); + gridPaneHolder=new PamGridPane(); + gridPaneHolder.setHgap(5); + gridPaneHolder.setVgap(5); //Number of zeros crossings Label zeroLabel; - pamGridPane.add(zeroLabel = new Label("Number of zero crossings"),0,0); + gridPaneHolder.add(zeroLabel = new Label("Number of zero crossings"),0,0); zeroCrossingsMin=new PamSpinner(0,999999,0,1); zeroCrossingsMin.setEditable(true); zeroCrossingsMin.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); zeroCrossingsMin.setPrefWidth(100); - pamGridPane.add(zeroCrossingsMin, 1,0); + gridPaneHolder.add(zeroCrossingsMin, 1,0); - pamGridPane.add(new Label("to"),2,0); + gridPaneHolder.add(new Label("to"),2,0); zeroCrossingsMax=new PamSpinner(0,999999,0,1); zeroCrossingsMax.setEditable(true); zeroCrossingsMax.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); zeroCrossingsMax.setPrefWidth(100); - pamGridPane.add(zeroCrossingsMax, 3,0); + gridPaneHolder.add(zeroCrossingsMax, 3,0); //this.zeroCrossingsMax=zeroCorssingsMax; @@ -1377,13 +1414,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { //zero crossing freuquency sweep - pamGridPane.add(new Label("Zero crossing frequency sweep"),0,1); + gridPaneHolder.add(new Label("Zero crossing frequency sweep"),0,1); freqZeroMin=new PamSpinner(0.,999999.,0.,1.); freqZeroMin.setEditable(true); freqZeroMin.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); freqZeroMin.setPrefWidth(100); - pamGridPane.add(freqZeroMin, 1,1); + gridPaneHolder.add(freqZeroMin, 1,1); @@ -1392,13 +1429,13 @@ public class SweepClassifierSetPaneFX extends SettingsPane { }); - pamGridPane.add(new Label("to"),2,1); + gridPaneHolder.add(new Label("to"),2,1); freqZeroMax=new PamSpinner(0.,999999.,0,1); freqZeroMax.setEditable(true); freqZeroMax.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); freqZeroMax.setPrefWidth(100); - pamGridPane.add(freqZeroMax, 3,1); + gridPaneHolder.add(freqZeroMax, 3,1); getValidator().createCheck() .dependsOn("minzerofreq", freqZeroMin.valueProperty()) @@ -1434,10 +1471,10 @@ public class SweepClassifierSetPaneFX extends SettingsPane { }); - pamGridPane.add(new Label("KHz/ms"),4,1); + gridPaneHolder.add(new Label("KHz/ms"),4,1); - return pamGridPane; + return gridPaneHolder; } @@ -1496,10 +1533,11 @@ public class SweepClassifierSetPaneFX extends SettingsPane { @Override protected void disbleControls(boolean disable) { - zeroCrossingsMin.setDisable(disable); - zeroCrossingsMax.setDisable(disable); - freqZeroMin.setDisable(disable); - freqZeroMax.setDisable(disable); + gridPaneHolder.setDisable(disable); +// zeroCrossingsMin.setDisable(disable); +// zeroCrossingsMax.setDisable(disable); +// freqZeroMin.setDisable(disable); +// freqZeroMax.setDisable(disable); } } @@ -1513,12 +1551,17 @@ public class SweepClassifierSetPaneFX extends SettingsPane { private TextField[] ampRange = new TextField[2]; + + /** + * The main holder + */ + private PamGridPane gridPane; public AmplitudeBlock() { super("Amplitude Range", true); setDescription("Set a minimum and maximum click amplitude for this type"); - PamGridPane gridPane= new PamGridPane(); + gridPane= new PamGridPane(); gridPane.setHgap(5); gridPane.setVgap(5); @@ -1572,13 +1615,16 @@ public class SweepClassifierSetPaneFX extends SettingsPane { @Override protected void disbleControls(boolean disable) { - for (int i = 0; i < 2; i++) { - ampRange[i].setDisable(!getEnableBox()); - } + gridPane.setDisable(disable); + +// for (int i = 0; i < 2; i++) { +// ampRange[i].setDisable(!getEnableBox()); +// } } } - + + /** * * Parameters for testing bearing values. @@ -1591,18 +1637,12 @@ public class SweepClassifierSetPaneFX extends SettingsPane { /** * The minimum correlation value. */ - private JTextField minBearing; + private PamSpinner minBearing; /** * The maximum bearing field */ - private JTextField maxBearing; - - /** - * The enable bearings check box. - */ - private JCheckBox enableBearings; - + private PamSpinner maxBearing; /** * True if using multi-channel data */ @@ -1611,55 +1651,100 @@ public class SweepClassifierSetPaneFX extends SettingsPane { /** * Combo box to select whetrher bearings should be kept or excluded within limits. */ - private JComboBox bearingsExcludeBox; + private ComboBox bearingsExcludeBox; + private PamHBox bearingHolder; + BearingBlock() { - super("Bearings", false); + super("Bearings", true); JPanel p = new JPanel(); - minBearing = new JTextField(5); - maxBearing = new JTextField(5); + minBearing = new PamSpinner(-180., 180., 0., 1.); + minBearing.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + minBearing.setMaxWidth(110); + minBearing.setEditable(true); + maxBearing = new PamSpinner(-180., 180., 0., 1.); + maxBearing.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + maxBearing.setMaxWidth(110); + maxBearing.setEditable(true); + + bearingsExcludeBox = new ComboBox(); + bearingsExcludeBox.getItems().add("Include only"); + bearingsExcludeBox.getItems().add("Exclude"); - bearingsExcludeBox = new JComboBox(); - bearingsExcludeBox.addItem("Include only"); - bearingsExcludeBox.addItem("Exclude"); + bearingHolder = new PamHBox(); + bearingHolder.setSpacing(5); + bearingHolder.setAlignment(Pos.CENTER_LEFT); p.setLayout(new GridBagLayout()); - GridBagConstraints c = new PamGridBagContraints(); -// c.gridx = 0; -// addComponent(p, enableBearings, c); -// c.gridx += c.gridwidth; -// addComponent(p, bearingsExcludeBox, c); -// c.gridx += c.gridwidth; -// addComponent(p, new JLabel("bearings between ", JLabel.RIGHT), c); -// c.gridx += c.gridwidth; -// addComponent(p, minBearing, c); -// c.gridx += c.gridwidth; -// addComponent(p, new JLabel(" and ", JLabel.RIGHT), c); -// c.gridx += c.gridwidth; -// addComponent(p, maxBearing, c); -// c.gridx += c.gridwidth; -// addComponent(p, new JLabel("(\u00B0)", JLabel.LEFT), c); -// -// add(BorderLayout.WEST, p); + + bearingHolder.getChildren().add(bearingsExcludeBox); + bearingHolder.getChildren().add(new Label("bearings between ")); + bearingHolder.getChildren().add(minBearing); + bearingHolder.getChildren().add(new Label("\u00B0 and ")); + bearingHolder.getChildren().add(maxBearing); + bearingHolder.getChildren().add(new Label("\u00B0")); + + + getValidator() .createCheck().dependsOn("maxbearing_", maxBearing.valueProperty()) + .withMethod(c -> { + Double maxBearingVal = c.get("maxbearing_"); + if (maxBearingVal<=minBearing.getValue() && isPaneShowing()) { + c.error("Maximum bearing must be greater than the minimum bearing"); + } + if (maxBearingVal<-180 || maxBearingVal>180) { + c.error("Bearing values must be between -180\u00B0 and 180\u00B0"); + } + }) + .decorates(maxBearing) + .immediate(); + + getValidator() .createCheck().dependsOn("minbearing_", minBearing.valueProperty()) + .withMethod(c -> { + Double minBearingVal = c.get("minbearing_"); + if (minBearingVal>maxBearing.getValue() && isPaneShowing()) { + c.error("Minimum bearing must be less than the maximum bearing"); + } + if (minBearingVal<-180 || minBearingVal>180) { + c.error("Bearing values must be between -180\u00B0 and 180\u00B0"); + } + }) + .decorates(minBearing) + .immediate(); + + + maxBearing.valueProperty().addListener((obsVal, oldVal, newVal)->{ + getValidator().validate(); + }); + + minBearing.valueProperty().addListener((obsVal, oldVal, newVal)->{ + getValidator().validate(); + }); this.multiChan = checkMultiChan(); + + this.getHolderPane().setCenter(bearingHolder); + + //setParams(); } @Override - protected void setParams() { + protected void setParams() { + + this.setEnableBox(sweepClassifierSet.enableBearingLims); + sweepClassifierSet.checkBearingAllocation(); // setEnableBox(sweepClassifierSet.enableZeroCrossings); - enableBearings.setSelected(sweepClassifierSet.enableBearingLims); + //enableBearings.setSelected(sweepClassifierSet.enableBearingLims); - if (sweepClassifierSet.excludeBearingLims) bearingsExcludeBox.setSelectedIndex(1); - else bearingsExcludeBox.setSelectedIndex(0); + if (sweepClassifierSet.excludeBearingLims) bearingsExcludeBox.getSelectionModel().select(1); + else bearingsExcludeBox.getSelectionModel().select(0); - this.minBearing.setText(String.format("%3.1f", Math.toDegrees(sweepClassifierSet.bearingLims[0]))); - this.maxBearing.setText(String.format("%3.1f", Math.toDegrees(sweepClassifierSet.bearingLims[1]))); + this.minBearing.getValueFactory().setValue(Math.toDegrees(sweepClassifierSet.bearingLims[0])); + this.maxBearing.getValueFactory().setValue(Math.toDegrees(sweepClassifierSet.bearingLims[1])); this.multiChan = checkMultiChan(); } @@ -1667,46 +1752,31 @@ public class SweepClassifierSetPaneFX extends SettingsPane { @Override protected boolean getParams() { - if (enableBearings.isSelected()) { - sweepClassifierSet.enableBearingLims = enableBearings.isSelected(); - sweepClassifierSet.excludeBearingLims = bearingsExcludeBox.getSelectedIndex()==1 ? true : false; + sweepClassifierSet.excludeBearingLims = bearingsExcludeBox.getSelectionModel().getSelectedIndex()==1 ? true : false; - try { - sweepClassifierSet.bearingLims[0] = Math.toRadians(Double.valueOf(minBearing.getText())); - } - catch (NumberFormatException e) { - return showWarning("Invalid minimum correlation value"); - } - - try { - sweepClassifierSet.bearingLims[1] = Math.toRadians(Double.valueOf(maxBearing.getText())); - } - catch (NumberFormatException e) { - return showWarning("Invalid maximum bearing limits value"); - } - } + sweepClassifierSet.bearingLims[0] = Math.toRadians(minBearing.getValue()); + + sweepClassifierSet.bearingLims[1] = Math.toRadians(maxBearing.getValue()); + return true; } - protected void enableControls() { - checkMultiChan(); - - enableBearings.setEnabled(multiChan); - bearingsExcludeBox.setEnabled(multiChan); - minBearing.setEnabled(multiChan); - maxBearing.setEnabled(multiChan); - - if (!multiChan) return; - - bearingsExcludeBox.setEnabled(enableBearings.isSelected()); - minBearing.setEnabled(enableBearings.isSelected()); - maxBearing.setEnabled(enableBearings.isSelected()); - } @Override protected void disbleControls(boolean disable) { - // TODO Auto-generated method stub + checkMultiChan(); + + bearingHolder.setDisable(!multiChan); +// bearingsExcludeBox.setDisable(!multiChan); +// minBearing.setDisable(!multiChan); +// maxBearing.setDisable(!multiChan); + if (!multiChan) return; + bearingHolder.setDisable(disable); + +// bearingsExcludeBox.setDisable(disable); +// minBearing.setDisable(disable); +// maxBearing.setDisable(disable); } } @@ -1719,25 +1789,150 @@ public class SweepClassifierSetPaneFX extends SettingsPane { private boolean checkMultiChan() { boolean multiChan = false; //do we have multi-channel clicks? -// if (clickControl!=null) { -// int[] chanGroups = clickControl.getClickParameters().getGroupedSourceParameters().getChannelGroups(); -// multiChan = false; -// -// if (chanGroups==null) return multiChan; -// -// for (int i=0; i1) { -// multiChan = true; -// break; -// } -// } -// } + if (this.sweepClassifier.getClickDetector().getClickControl()!=null) { + int[] chanGroups = getClickControl().getClickParameters().getGroupedSourceParameters().getChannelGroups(); + multiChan = false; + + if (chanGroups==null) return multiChan; + + for (int i=0; i1) { + multiChan = true; + break; + } + } + } + else multiChan = true; // Debug.out.println("Check multi-channel: " + multiChan); return multiChan; } + + /** + * + * Parameters for testing the cross correlation value. + * + * @author Jamie Macaulay + * + */ + private class XCorrBlock extends SweepBox { + + /** + * The minimum correlation value. + */ + private PamSpinner minCorrelation; + + private PamSpinner minPeakTorugh; + + private PamToggleSwitch minXCorrEnable; + + private PamToggleSwitch minPeakTroughEnable; + + private PamSpinner minPeakFactor; + + /** + * True if using multi-channel data + */ + boolean multiChan = false; + + private GridPane gridPane; + + + XCorrBlock() { + super("Cross Correlation", true); + + minXCorrEnable = new PamToggleSwitch(""); + minXCorrEnable.selectedProperty().addListener((obsVal, oldVal, newVal)->{ + disbleControls(!newVal); + }); + + minCorrelation = new PamSpinner(-Double.MAX_VALUE, Double.MAX_VALUE, 0, 0.1); + minCorrelation.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + + minPeakTroughEnable = new PamToggleSwitch(""); + minPeakTroughEnable.selectedProperty().addListener((obsVal, oldVal, newVal)->{ + disbleControls(!newVal); + }); + + minPeakFactor = new PamSpinner(-Double.MAX_VALUE, Double.MAX_VALUE, 1., 0.1); + minPeakFactor.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + + int x = 0; + int y = 0; + + gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + + gridPane.add(minXCorrEnable, x, y); + gridPane.add(new Label("Min. xcorr value "), ++x, y); + gridPane.add(minCorrelation, ++x, y); + + y++; + x=0; + gridPane.add(minPeakTroughEnable, x, y); + gridPane.add(new Label("Max. xcorr value greater than "), ++x, y); + gridPane.add(minPeakFactor,++x, y); + gridPane.add(new Label("* absolute min. value"),++x, y); + + this.getHolderPane().setCenter(gridPane); + + this.multiChan = checkMultiChan(); + } + + @Override + protected void setParams() { + + this.setEnableBox(sweepClassifierSet.enableMinXCrossCorr); + + sweepClassifierSet.checkXCCorrAllocation(); +// setEnableBox(sweepClassifierSet.enableZeroCrossings); + minXCorrEnable.setSelected(sweepClassifierSet.enableMinXCrossCorr); + minPeakTroughEnable.setSelected(sweepClassifierSet.enablePeakXCorr); + + this.minCorrelation.getValueFactory().setValue(sweepClassifierSet.minCorr); + this.minPeakFactor.getValueFactory().setValue(sweepClassifierSet.corrFactor); + + this.multiChan = checkMultiChan(); + } + + @Override + protected boolean getParams() { + sweepClassifierSet.enableMinXCrossCorr = minXCorrEnable.isSelected(); + sweepClassifierSet.enablePeakXCorr = minPeakTroughEnable.isSelected(); + + if (minXCorrEnable.isSelected()) { + sweepClassifierSet.minCorr = minCorrelation.getValue(); + } + + if (minPeakTroughEnable.isSelected()) { + sweepClassifierSet.corrFactor = minPeakFactor.getValue(); + } + return true; + } + + @Override + protected void disbleControls(boolean disable) { + this.multiChan = checkMultiChan(); + + minXCorrEnable.setDisable(!multiChan); + minCorrelation.setDisable(!multiChan); + minPeakTroughEnable.setDisable(!multiChan); + minPeakFactor.setDisable(!multiChan); + + if (!multiChan) return; + + gridPane.setDisable(disable); + + minCorrelation.setDisable(!minXCorrEnable.isSelected()); + minPeakFactor.setDisable(!minPeakTroughEnable.isSelected()); + } + + + } + /**~*main set and get params functions***/ @@ -1753,6 +1948,9 @@ public class SweepClassifierSetPaneFX extends SettingsPane { freqBox.getParams(); zeroCrossingsBox.getParams(); amplitudeBlock.getParams(); + bearingBox.getParams(); + xCorrBox.getParams(); + currentClickProperty.setClickType(sweepClassifierSet); @@ -1772,6 +1970,8 @@ public class SweepClassifierSetPaneFX extends SettingsPane { freqBox.setParams(); zeroCrossingsBox.setParams(); amplitudeBlock.setParams(); + bearingBox.setParams(); + xCorrBox.setParams(); } @@ -1797,6 +1997,12 @@ public class SweepClassifierSetPaneFX extends SettingsPane { } + public ObservableValue getNameGraphicProperty() { // TODO Auto-generated method stub + return optionBox.getNameGraphicsProperty(); + + } + + /** @@ -1859,6 +2065,12 @@ public class SweepClassifierSetPaneFX extends SettingsPane { } + /** + * Show a warning. + * @param string - the + * @param string2 + * @return false usually + */ private boolean showWarning(String string, String string2) { //PamController.getInstance(); PamDialogFX.showWarning(null, string, string2); @@ -1896,4 +2108,7 @@ public class SweepClassifierSetPaneFX extends SettingsPane { private boolean isPaneShowing() { return sweepClassifier.getClassifierPane().getFlipPane().isBackVisible(); } -} \ No newline at end of file + + + +} diff --git a/src/clickDetector/localisation/GeneralGroupLocaliser.java b/src/clickDetector/localisation/GeneralGroupLocaliser.java index 7a6af5cd..ed592672 100644 --- a/src/clickDetector/localisation/GeneralGroupLocaliser.java +++ b/src/clickDetector/localisation/GeneralGroupLocaliser.java @@ -86,7 +86,7 @@ abstract public class GeneralGroupLocaliser implements LocaliserModel getSettingsPane() { + public LocaliserPane getAlgorithmSettingsPane() { // no settings pane for this. Settings come from click control params //and so are never called here. return null; diff --git a/src/clickDetector/offlineFuncs/ClickWaveTask.java b/src/clickDetector/offlineFuncs/ClickWaveTask.java index 49f4368a..a5bfb24c 100644 --- a/src/clickDetector/offlineFuncs/ClickWaveTask.java +++ b/src/clickDetector/offlineFuncs/ClickWaveTask.java @@ -131,8 +131,11 @@ public class ClickWaveTask extends OfflineTask { // setParentDataBlock(clickControl.getClickDataBlock()); addAffectedDataBlock(clickControl.getClickDataBlock()); - rawDataSource = (PamRawDataBlock) clickDetector.getRawSourceDataBlock(clickDetector.getSampleRate()); - daqStatusDataBlock = ((AcquisitionProcess) rawDataSource.getParentProcess()).getDaqStatusDataBlock(); + rawDataSource = (PamRawDataBlock) clickDetector.getRawSourceDataBlock(); + + if (rawDataSource!=null) { + daqStatusDataBlock = ((AcquisitionProcess) rawDataSource.getParentProcess()).getDaqStatusDataBlock(); + } //TODO- Should not hard wire these but this is not going to be used very often. filterParams = new FilterParams(FilterType.BUTTERWORTH, FilterBand.HIGHPASS, 250000, 5000, 4); @@ -155,6 +158,12 @@ public class ClickWaveTask extends OfflineTask { public void prepareTask() { Debug.out.println("Load data units from: daqStatusDataBlock: " + daqStatusDataBlock.getUnitsCount()); + if (daqStatusDataBlock==null) { + if (rawDataSource!=null) { + daqStatusDataBlock = ((AcquisitionProcess) rawDataSource.getParentProcess()).getDaqStatusDataBlock(); + } + } + filterMethod = FilterMethod.createFilterMethod(clickDetector.getSampleRate(), filterParams); daqStatusDataBlock.loadViewerData(new OfflineDataLoadInfo(clickControl.getClickDataBlock().getFirstUnit().getTimeMilliseconds()-DEFAULTDAQUNITLOAD, diff --git a/src/clickDetector/offlineFuncs/ClicksOffline.java b/src/clickDetector/offlineFuncs/ClicksOffline.java index 6b72da91..4eff23ae 100644 --- a/src/clickDetector/offlineFuncs/ClicksOffline.java +++ b/src/clickDetector/offlineFuncs/ClicksOffline.java @@ -587,10 +587,10 @@ public class ClicksOffline { offlineTaskGroup.addTask(new EchoDetectionTask(clickControl)); offlineTaskGroup.addTask(new ClickDelayTask(clickControl)); offlineTaskGroup.addTask(new ClickBearingTask(clickControl)); - if (JamieDev.isEnabled()) { - //re import waveform data from raw wave files. - offlineTaskGroup.addTask(new ClickWaveTask(clickControl)); - } +// if (JamieDev.isEnabled()) { +// //re import waveform data from raw wave files. +// offlineTaskGroup.addTask(new ClickWaveTask(clickControl)); +// } /* * Add the click detector tasks first since these will need to operate 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/clickDetector/tdPlots/ClickEventSymbolChooser.java b/src/clickDetector/tdPlots/ClickEventSymbolChooser.java index 3da72c23..8d9c5074 100644 --- a/src/clickDetector/tdPlots/ClickEventSymbolChooser.java +++ b/src/clickDetector/tdPlots/ClickEventSymbolChooser.java @@ -1,17 +1,11 @@ package clickDetector.tdPlots; -import java.awt.Color; - import PamView.GeneralProjector; -import PamView.PamColors; import PamView.symbol.StandardSymbolChooser; import PamView.symbol.StandardSymbolManager; import PamView.symbol.StandardSymbolOptions; import PamView.symbol.SymbolData; import PamguardMVC.PamDataBlock; -import PamguardMVC.PamDataUnit; -import clickDetector.ClickTrainDetection; -import clickDetector.offlineFuncs.OfflineEventDataUnit; public class ClickEventSymbolChooser extends StandardSymbolChooser { diff --git a/src/clickDetector/tdPlots/ClickEventSymbolManager.java b/src/clickDetector/tdPlots/ClickEventSymbolManager.java index 89ce1d02..0c068486 100644 --- a/src/clickDetector/tdPlots/ClickEventSymbolManager.java +++ b/src/clickDetector/tdPlots/ClickEventSymbolManager.java @@ -1,12 +1,6 @@ package clickDetector.tdPlots; -import PamView.GeneralProjector; -import PamView.symbol.StandardSymbolChooser; -import PamView.symbol.StandardSymbolManager; import PamView.symbol.SymbolData; -import PamView.symbol.modifier.SuperDetSymbolModifier; -import PamguardMVC.PamDataBlock; -import PamguardMVC.superdet.SuperDetDataBlock; import PamguardMVC.superdet.swing.SuperDetectionSymbolManager; import clickDetector.offlineFuncs.OfflineEventDataBlock; diff --git a/src/clickTrainDetector/CTDetectionGroupDataUnit.java b/src/clickTrainDetector/CTDetectionGroupDataUnit.java index e7464363..f91df1a4 100644 --- a/src/clickTrainDetector/CTDetectionGroupDataUnit.java +++ b/src/clickTrainDetector/CTDetectionGroupDataUnit.java @@ -6,9 +6,7 @@ import java.util.ListIterator; import PamDetection.LocContents; import PamDetection.PamDetection; -import PamguardMVC.AcousticDataUnit; import PamguardMVC.PamDataUnit; -import PamguardMVC.dataSelector.DataSelector; import clickTrainDetector.dataselector.CTSelectParams; import detectiongrouplocaliser.DetectionGroupDataUnit; diff --git a/src/clickTrainDetector/ClickTrainProcess.java b/src/clickTrainDetector/ClickTrainProcess.java index 54f7b9f6..b3193d10 100644 --- a/src/clickTrainDetector/ClickTrainProcess.java +++ b/src/clickTrainDetector/ClickTrainProcess.java @@ -6,6 +6,7 @@ import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.PamInstantProcess; import PamguardMVC.PamObservable; +import PamguardMVC.PamProcess; import clickDetector.ClickDetection; import clickTrainDetector.layout.CTDataUnitGraphics; import clickTrainDetector.layout.UnconfirmedCTSymbolManager; @@ -19,7 +20,7 @@ import pamScrollSystem.AbstractScrollManager; * @author Jamie Macaulay * */ -public class ClickTrainProcess extends PamInstantProcess { +public class ClickTrainProcess extends PamProcess { /** * The source data block @@ -47,7 +48,7 @@ public class ClickTrainProcess extends PamInstantProcess { private CTProcessCheck processCheck; public ClickTrainProcess(ClickTrainControl pamControlledUnit) { - super(pamControlledUnit); + super(pamControlledUnit, null); this.clickTrainControl=pamControlledUnit; this.processCheck=new CTProcessCheck(this); diff --git a/src/clickTrainDetector/clickTrainAlgorithms/mht/test/ExampleClickTrains.java b/src/clickTrainDetector/clickTrainAlgorithms/mht/test/ExampleClickTrains.java index ade0870a..b8036bd4 100644 --- a/src/clickTrainDetector/clickTrainAlgorithms/mht/test/ExampleClickTrains.java +++ b/src/clickTrainDetector/clickTrainAlgorithms/mht/test/ExampleClickTrains.java @@ -4,12 +4,11 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; -import com.jmatio.io.MatFileReader; -import com.jmatio.types.MLCell; -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLInt32; - +import PamUtils.PamArrayUtils; import clickTrainDetector.clickTrainAlgorithms.mht.mhtMAT.SimpleClick; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.Cell; /** * Simulates some clicks to test the MHT algorithm on. @@ -127,7 +126,6 @@ public class ExampleClickTrains { public SimpleClickDataBlock importSimClicks(SimClickImport type) { File file = getSimClicksFile(type); return importSimClicks(file); - } /** @@ -135,25 +133,29 @@ public class ExampleClickTrains { * @param file - the file. */ private ArrayList importSimpleClickMAT(File file) { - MatFileReader mfr = null; + Mat5File mfr = null; try { if (file ==null) { System.out.println("The imported file is null"); return null; } - - mfr = new MatFileReader(file); + + + mfr = Mat5.readFromFile(file); + + + mfr.getCell("clicksMHT"); //get array of a name "my_array" from file - MLCell mlArrayRetrived = (MLCell) mfr.getMLArray( "clicksMHT" ); - MLDouble mlSampleRate = (MLDouble) mfr.getMLArray( "sR" ); + Cell mlArrayRetrived = mfr.getCell("clicksMHT"); + Double mlSampleRate = mfr.getMatrix( "sR" ).getDouble(0); ArrayList simpleClicks= new ArrayList(); //now the cell array can be anywhere from 2- 6 columns depending on the data contained. - int nColumns = mlArrayRetrived.getN(); - int nClks = mlArrayRetrived.getM(); + int nColumns = mlArrayRetrived.getNumRows(); + int nClks = mlArrayRetrived.getNumCols(); SimpleClick simpleClick; @@ -163,25 +165,27 @@ public class ExampleClickTrains { Double bearing = null; double[][] waveform =null; - float sR = mlSampleRate.get(0).floatValue(); + float sR =(float) mlSampleRate.doubleValue(); for (int i=0; i2) amplitude = ((MLDouble) mlArrayRetrived.get(i,2)).get(0); + if (nColumns>2) amplitude = mlArrayRetrived.getCell(i,2).get(0); //fourth column is bearing in degrees (double) - if (nColumns<3) bearing = ((MLDouble) mlArrayRetrived.get(i,3)).get(0); + if (nColumns<3) bearing = mlArrayRetrived.getCell(i,3).get(0); //fifth column is a waveform array (double[][]) - if (nColumns>4) waveform = ((MLDouble) mlArrayRetrived.get(i,4)).getArray(); - + if (nColumns>4) waveform = PamArrayUtils.matrix2array(mlArrayRetrived.getCell(i,4).getMatrix(0)); //tranpose the waveform waveform = PamUtils.PamArrayUtils.transposeMatrix(waveform); diff --git a/src/clickTrainDetector/dataselector/CTDataSelector.java b/src/clickTrainDetector/dataselector/CTDataSelector.java index 8fd9e4d7..a011ac00 100644 --- a/src/clickTrainDetector/dataselector/CTDataSelector.java +++ b/src/clickTrainDetector/dataselector/CTDataSelector.java @@ -1,7 +1,5 @@ package clickTrainDetector.dataselector; -import java.util.Arrays; - import PamDetection.LocContents; import PamguardMVC.PamDataUnit; import PamguardMVC.dataSelector.DataSelectParams; 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/clickTrainDetector/logging/ClickTrainDetLogging.java b/src/clickTrainDetector/logging/ClickTrainDetLogging.java index 4370dad9..6918442a 100644 --- a/src/clickTrainDetector/logging/ClickTrainDetLogging.java +++ b/src/clickTrainDetector/logging/ClickTrainDetLogging.java @@ -30,7 +30,6 @@ import pamMaths.PamInterp; */ public class ClickTrainDetLogging extends SuperDetLogging { - /** * The delimiter used for the average spectrum in the database. */ @@ -46,7 +45,6 @@ public class ClickTrainDetLogging extends SuperDetLogging { */ private final static int DEFAULT_SPECTRUM_LEN = 256; - /** * Reference to the click train data block. */ @@ -58,7 +56,7 @@ public class ClickTrainDetLogging extends SuperDetLogging { private ClickTrainControl clickTrainControl; /** - * The pam table items for saving. + * The PAM table items for saving. */ private PamTableItem endTime, dataCount, chi2, medianIDI, meanIDI, stdIDI, algorithmInfo, avrg_Spectrum, speciesFlag, avrg_Spectrum_max; diff --git a/src/clickTrainDetector/logging/ClickTrainDetSubLogging.java b/src/clickTrainDetector/logging/ClickTrainDetSubLogging.java index 6126b973..67866f25 100644 --- a/src/clickTrainDetector/logging/ClickTrainDetSubLogging.java +++ b/src/clickTrainDetector/logging/ClickTrainDetSubLogging.java @@ -9,7 +9,7 @@ import generalDatabase.SuperDetLogging; /** * Logs the the children of the click train detection. - * @author macst + * @author Jamie Macaulay * */ public class ClickTrainDetSubLogging extends SQLLogging { diff --git a/src/cpod/CPODBinaryStore.java b/src/cpod/CPODBinaryStore.java index 6c3453f2..9cff8285 100644 --- a/src/cpod/CPODBinaryStore.java +++ b/src/cpod/CPODBinaryStore.java @@ -9,18 +9,20 @@ import java.io.IOException; import PamguardMVC.DataUnitBaseData; import PamguardMVC.PamDataUnit; +import PamguardMVC.RawDataTransforms; import binaryFileStorage.BinaryDataSource; import binaryFileStorage.BinaryHeader; import binaryFileStorage.BinaryObjectData; import binaryFileStorage.ModuleFooter; import binaryFileStorage.ModuleHeader; +import cpod.CPODClick.CPODWaveTransforms; /** * Binary storage for CPOD data. *

* Seems a little silly to re create a new file format for CPOD data but by storing * as binary files we can take advantage of the PAMGuard datagram, UID manager, super detections, a - * annotations etc. without creating a very hackey module. + * annotations etc. without creating a very hacky module. * * @author Jamie Macauly * @@ -28,11 +30,14 @@ import binaryFileStorage.ModuleHeader; public class CPODBinaryStore extends BinaryDataSource { + + /** *

Module version changes

* Version 2: Moved start sample, channel bitmap, duration to DataUnitBaseData general data structure
+ * Version 3: Implemented FPOD data which includes waveforms
*/ - private static final int currentVersion = 2; + private static final int currentVersion = 3; /** * Reference to the CPOD control. @@ -84,8 +89,6 @@ public class CPODBinaryStore extends BinaryDataSource { CPODClick cd = (CPODClick) pamDataUnit; //System.out.println("DLDetecitonBinarySource: packed: " + pamDataUnit.getBasicData().getMeasuredAmplitudeType()); - - // make a byte array output stream and write the data to that, // then dump that down to the main storage stream if (dos == null || bos == null) { @@ -96,7 +99,7 @@ public class CPODBinaryStore extends BinaryDataSource { } try { - writeCPODData(dos, cd); + writeCPODdata3(dos, cd); } catch (IOException e) { e.printStackTrace(); @@ -106,13 +109,49 @@ public class CPODBinaryStore extends BinaryDataSource { return new BinaryObjectData(1, bos.toByteArray()); } + + + private void writeCPODdata3(DataOutputStream dos2, CPODClick cd) throws IOException { + +// data[3]=nCyc; +// data[4]=bw; +// data[5]=kHz; +// data[6]=endF; +// data[7]=spl; +// data[8]=slope; + + dos.writeShort(cd.getnCyc()); + dos.writeShort(cd.getBw()); + dos.writeShort(cd.getkHz()); + dos.writeShort(cd.getEndF()); + dos.writeShort(cd.getSpl()); + dos.writeShort(cd.getSlope()); + + boolean hasWav = cd.getWaveData()!=null; + dos.writeBoolean(cd.getWaveData()!=null); + + if (hasWav) { + //the number of samples - note FPODs only have one sample + dos.writeShort(cd.getWaveData()[0].length); + + //convert back to shorts using scale factor + for (int i=0; i implements AcousticDataUnit { +/** + * CPOD or FPOD click. + */ +public class CPODClick extends PamDataUnit implements RawDataHolder { + private double[][] wavData; + private short nCyc; private short bw; private short kHz; @@ -18,11 +27,19 @@ public class CPODClick extends PamDataUnit implement private long iciSamples; private short[] rawData; + /** * The amplitude in dB. */ private Double amplitudedB; + /** + * The raw data transforms for the CPOD click + */ + private RawDataTransforms rawDataTransforms = null; + + private CPODClassification cpodClassification; + /** * Create a CPOD click. (This is used to load CPOD clicks from the binary store) * @@ -41,6 +58,7 @@ public class CPODClick extends PamDataUnit implement short slope = shortData[8]; CPODClick cpodClick = new CPODClick(baseData.getTimeMilliseconds(), baseData.getStartSample(), nCyc, bw, kHz, endF, spl, slope, shortData); +// cpodClick.setDurationInMilliseconds(baseData.getMillisecondDuration()); return cpodClick; } @@ -52,11 +70,13 @@ public class CPODClick extends PamDataUnit implement * @param shortData * @return */ + @Deprecated public static CPODClick makeClick(CPODControl cpodControl, long minuteMillis, short[] shortData) { int t = shortData[0]<<16 | shortData[1]<<8 | shortData[2]; // 5 microsec intervals ! + long tMillis = minuteMillis + t/200; // now a bit of time stretching. Need to get the start time and see how // different this is, then it's a linear stretch. @@ -72,44 +92,80 @@ public class CPODClick extends PamDataUnit implement short endF = shortData[6]; short spl = shortData[7]; short slope = shortData[8]; + CPODClick cpodClick = new CPODClick(tMillis, fileSamples, nCyc, bw, kHz, endF, spl, slope, shortData); + +// //estimate the duration in millis - not accurate but gives an idea. +// double duration = (nCyc/(double) kHz); +// cpodClick.setDurationInMilliseconds(duration); + return cpodClick; } /** - * Create a CPOD click. This is usually called whenever the CPOD click is imported from a CSV file. - * - * @param minuteMillis The time in milliseconds of the minute block preceding the - * current click. - * @param shortData - * @return + * Make a FPOD click. This called whenever click has been imported from a FP1 or FP3 file + * @param tMillis - the time in milliseconds datenum. + * @param fileSamples - the number of samples into the file the CPOD click is at - this is calculated from CPODClickDataBlock.CPOD_SR + * @param shortData - the raw data from the CPOD click. This can be 8 bytes or 30 bytes if a click train clcik + * @return a CPODClick object. */ - public static CPODClick makeClick(CPODControl2 cpodControl, long minuteMillis, short[] shortData) { + public static CPODClick makeFPODClick(long tMillis, long fileSamples, FPODdata fpodData) { + + + CPODClick cpodClick = new CPODClick(tMillis, fileSamples, (short) fpodData.Ncyc, (short) fpodData.BW, + (short) FPODReader.IPItoKhz(fpodData.IPIatMax), (short) FPODReader.IPItoKhz(fpodData.EndIPI), + (short) fpodData.MaxPkExtnd, (short) 0, null); + + //durartion is measured more accurately in FPOD data + cpodClick.setDurationInMilliseconds((fpodData.duration*5.)/1000.); + cpodClick.setSampleDuration((long) ((fpodData.duration*5./1e6)*CPODClickDataBlock.CPOD_SR)); + + //some FPOD clicks have raw wave data - some do not. + if (fpodData.getWavData()!=null) { + + int[] waveData = FPODReader.makeResampledWaveform(fpodData); + + //now need to scale the data so it fits as a raw data holder. + double[] waveDataD = FPODReader.scaleWavData(waveData); + + cpodClick.wavData = new double[1][];//create a 2D array + cpodClick.wavData[0]=waveDataD; + + cpodClick.setRawDataTransfroms(new CPODWaveTransforms(cpodClick)); + } + + return cpodClick; + } + - int t = shortData[0]<<16 | - shortData[1]<<8 | - shortData[2]; // 5 microsec intervals ! - long tMillis = minuteMillis + t/200; - // now a bit of time stretching. Need to get the start time and see how - // different this is, then it's a linear stretch. - tMillis = cpodControl.stretchClicktime(tMillis); + /** + * Make a CPOD click. This called whenever click has been imported from a CP1 or CP3 file + * @param tMillis - the time in milliseconds datenum. + * @param fileSamples - the number of samples into the file the CPOD click is at - this is calculated from CPODClickDataBlock.CPOD_SR + * @param shortData - the raw data from the CPOD click. This can be 8 bytes or 40 bytes if a click train clcik + * @return a CPODClick object. + */ + public static CPODClick makeCPODClick(long tMillis, long fileSamples, short[] shortData) { - - // do a sample number within the file as 5us intervals - long fileSamples = t + minuteMillis * 200; -// int micros = t%200; short nCyc = shortData[3]; - short bw = shortData[4]; + short bw = shortData[4]; //bandwidth is an arbitary scale between 0 and 31; + bw = (short) ((255./31.) * (bw+1)); //make some attempt to convert to kHz short kHz = shortData[5]; short endF = shortData[6]; short spl = shortData[7]; short slope = shortData[8]; CPODClick cpodClick = new CPODClick(tMillis, fileSamples, nCyc, bw, kHz, endF, spl, slope, shortData); + +// //estimate the duration in millis - not accurate but gives an idea. + double duration = (nCyc/(double) kHz); + cpodClick.setDurationInMilliseconds(duration); + + return cpodClick; } /** - * Constructor for a CPOD click. + * Constructor for a CPOD click. This adds all basic information that is required for a CPOD or FPOD click * @param tMillis - the time in millis. * @param fileSamples - the file samples * @param nCyc - the number of cycles @@ -130,15 +186,19 @@ public class CPODClick extends PamDataUnit implement this.spl = spl; this.slope = slope; double[] f = new double[2]; - f[0] = Math.min(kHz, endF)*1000; - f[1] = Math.max(kHz, endF)*1000; + + f[0] = (kHz - bw/2.)*1000.; + f[1] = (kHz + bw/2.)*1000.; + setFrequency(f); - //estimate the duration in millis - not accurate but gives an idea. - double duration = (nCyc/(double) kHz); - this.setDurationInMilliseconds(duration); - - this.rawData = shortData.clone(); +// double duration = (nCyc/(double) kHz); +// setDurationInMilliseconds(duration); + + if (shortData!=null) { + //only CPOD + this.rawData = shortData.clone(); + } } /** @@ -252,6 +312,7 @@ public class CPODClick extends PamDataUnit implement } return amplitudedB; } + /* (non-Javadoc) * @see PamDetection.AcousticDataUnit#getSummaryString() @@ -267,6 +328,7 @@ public class CPODClick extends PamDataUnit implement } long tm = getTimeMilliseconds(); str += PamCalendar.formatDate(tm) + " " + PamCalendar.formatTime(tm, 3) + "

"; + str += String.format("UID: %dkHz

", this.getUID()); str += String.format("Start Freq: %dkHz

", getkHz()); str += String.format("N Cycles: %d

", getnCyc()); str += String.format("BandWidth: %dkHz

", getBw()); @@ -274,12 +336,12 @@ public class CPODClick extends PamDataUnit implement str += String.format("Slope: %d

", getSlope()); str += String.format("SPL: %d", getSpl()); if (rawData.length == 40) { - str += String.format("

QClass %d, SpClass %d", getBits(rawData[19], (short) 0x3), - getBits(rawData[19], (short) 0b11100)); + str += String.format("

QClass %d, SpClass %d", CPODUtils.getBits(rawData[19], (short) 0x3), + CPODUtils.getBits(rawData[19], (short) 0b11100)); str += String.format("

Train %d, %d click", rawData[20], rawData[23]); str += String.format("

Qn %d, RateGood %d, SpGood %d, SpClass %d", - getBits(rawData[36], (short)3), getBits(rawData[36], (short) 4), - getBits(rawData[36], (short)8), getBits(rawData[36], (short) 240)); + CPODUtils.getBits(rawData[36], (short)3), CPODUtils.getBits(rawData[36], (short) 4), + CPODUtils.getBits(rawData[36], (short)8), CPODUtils.getBits(rawData[36], (short) 240)); } if (rawData != null) { int nRaw = rawData.length; @@ -291,19 +353,83 @@ public class CPODClick extends PamDataUnit implement } } } + CPODClickTrainDataUnit clicktrain = getCPODClickTrain(); + if (clicktrain!=null) { + str += "

" + clicktrain.getStringInfo() + "

"; + } + else { + str += "

" + String.format("No click train info

"); + } + // str += "<\html>"; return str; } - short getBits(short data, short bitMap) { - short firstBit = 0; - for (int i = 0; i < 8; i++) { - if ((bitMap & (1<>firstBit); + + @Override + public double[][] getWaveData() { + return this.wavData; } + + @Override + public RawDataTransforms getDataTransforms() { + return rawDataTransforms; + } + + public void setRawDataTransfroms(RawDataTransforms rawDataTransforms) { + this.rawDataTransforms = rawDataTransforms; + + } + + public void setWavData(double[][] ds) { + this.wavData=ds; + } + + static class CPODWaveTransforms extends RawDataTransforms { + + CPODClick rawDataHolder; + + public CPODWaveTransforms(PamDataUnit rawDataHolder) { + super(rawDataHolder); + this.rawDataHolder = (CPODClick) rawDataHolder; + + } + + @Override + public float getSampleRate() { + return FPODReader.FPOD_WAV_SAMPLERATE; + } + + @Override + public int getCurrentSpectrumLength() { + //note that the waveform has a higher sample rate than the CPODclicks. + return PamUtils.getMinFftLength(rawDataHolder.getWaveData()[0].length); + } + + } + + /** + * Get the click train detection from the click detection + * @return the click train detection + */ + public CPODClickTrainDataUnit getCPODClickTrain() { + for (int i=0; i { super(CPODClick.class, dataName, parentProcess, 1); this.clikcType = clikcType; + if (parentProcess!=null) { this.setDataSelectCreator(new CPODDataSelectorCreator(parentProcess.getPamControlledUnit(), this)); + } } - /* (non-Javadoc) - * @see PamguardMVC.PamDataBlock#getDataName() - */ - @Override - public String getDataName() { - PamControlledUnit cpodControl = getParentProcess().getPamControlledUnit(); - if (cpodControl == null) { - return super.getDataName(); - } - return cpodControl.getUnitName() + "_" + getDataTypeString(); - } +// /* (non-Javadoc) +// * @see PamguardMVC.PamDataBlock#getDataName() +// */ +// @Override +// public String getDataName() { +// if (getParentProcess()!=null) { +// PamControlledUnit cpodControl = getParentProcess().getPamControlledUnit(); +// if (cpodControl == null) { +// return super.getDataName(); +// } +// return cpodControl.getUnitName() + "_" + getDataTypeString(); +// } +// return "CPOD_temporary"; +// +// } - private String getDataTypeString() { - switch (clikcType) { - case CPODMap.FILE_CP1: - return "CP1"; - case CPODMap.FILE_CP3: - return "CP3"; - } - return null; - } - @Override public float getSampleRate() { diff --git a/src/cpod/CPODClickTrainDataBlock.java b/src/cpod/CPODClickTrainDataBlock.java new file mode 100644 index 00000000..dc989911 --- /dev/null +++ b/src/cpod/CPODClickTrainDataBlock.java @@ -0,0 +1,141 @@ +package cpod; + +import PamUtils.PamCalendar; +import PamguardMVC.PamDataUnit; +import PamguardMVC.PamProcess; +import PamguardMVC.dataOffline.OfflineDataLoadInfo; +import PamguardMVC.debug.Debug; +import PamguardMVC.superdet.SuperDetDataBlock; +import clickTrainDetector.CTDataUnit; +import pamScrollSystem.ViewLoadObserver; + +/** + * + * The click train detector data block. Holds all detected click trains in + * ClickTrainDataUnits which themselves contain a list of detected data units + * within a train. + * + * @author Jamie Macaulay + * + */ +public class CPODClickTrainDataBlock extends SuperDetDataBlock { + + /** + * Convenience reference to the click train control + */ + private CPODControl2 cpodControl; + +// /** +// * The data selector for click trains. +// */ +// private CTDataSelectCreator clickDataSelectCreator; + + + public CPODClickTrainDataBlock(CPODControl2 cpodControl, PamProcess parentProcess, String name, int channelMap) { + super(CTDataUnit.class, name, parentProcess, channelMap, SuperDetDataBlock.ViewerLoadPolicy.LOAD_OVERLAPTIME); + this.cpodControl = cpodControl; + } + + +// public ClickTrainDataBlock(ClickTrainControl clickTrainControl, Class class1, String name, PamProcess parentProcess, int channelMap) { +// super(CTDataUnit.class, name, parentProcess, channelMap, SuperDetDataBlock.LOAD_VIEWERDATA); +// this.clickTrainControl = clickTrainControl; +// } + + + @Override + public boolean saveViewerData() { + return super.saveViewerData(); + } + + @Override + public boolean loadViewerData(OfflineDataLoadInfo offlineDataLoadInfo, ViewLoadObserver loadObserver) { + Debug.out.println("Loading click train viewer data: startMillis: " + PamCalendar.formatDateTime(offlineDataLoadInfo.getStartMillis()) + + " endMillis: " + PamCalendar.formatDateTime(offlineDataLoadInfo.getEndMillis())); + return super.loadViewerData(offlineDataLoadInfo, loadObserver); + } + + + /* (non-Javadoc) + * @see PamguardMVC.PamDataBlock#addPamData(PamguardMVC.PamDataUnit) + */ + @Override + public void addPamData(CPODClickTrainDataUnit pamDataUnit) { + super.addPamData(pamDataUnit); + //this.sortData(); + } + + /* (non-Javadoc) + * @see PamguardMVC.PamDataBlock#addPamData(PamguardMVC.PamDataUnit, java.lang.Long) + */ + @Override + public void addPamData(CPODClickTrainDataUnit pamDataUnit, Long uid) { + super.addPamData(pamDataUnit, uid); + //this.sortData(); + } + + /* (non-Javadoc) + * @see PamguardMVC.PamDataBlock#updatePamData(PamguardMVC.PamDataUnit, long) + */ + @Override + public void updatePamData(CPODClickTrainDataUnit pamDataUnit, long updateTimeMillis) { + super.updatePamData(pamDataUnit, updateTimeMillis); + //this.sortData(); + } + + @Override + public float getSampleRate() { + return super.getSampleRate(); + } + + + @Override + public boolean needViewerDataLoad(OfflineDataLoadInfo offlineDataLoadInfo) { + // always reload these data to make sure everything gets linked up correctly. +// 22/10/2019. Changed a bunch of viewer load stuff including have the click train detector +// load up with a longer scroll than click detector. Have disabled because it now really messes up +// the click train detector. +// Debug.out.println("-- CLICK TRAIN DATABLOCK: Need viewer load: " + super.needViewerDataLoad(offlineDataLoadInfo)); + return super.needViewerDataLoad(offlineDataLoadInfo); + } + +// @Override +// public void clearAll() { +// Debug.out.println("-- CLICK TRAIN DATABLOCK: clear 1: "); +// +// ListIterator iterator = this.getListIterator(0); +// int count = 0; +// T dataUnit; +// while (iterator.hasNext()) { +// dataUnit= iterator.next(); +// dataUnit.removeAllSubDetections(); +// dataUnit.clearSubdetectionsRemoved(); +// if (count%100==0) { +// Debug.out.println("-- CLICK TRAIN DATABLOCK: clear: " + count+++ " " + dataUnit); +// } +// } +// super.clearAll(); +// +// Debug.out.println("-- CLICK TRAIN DATABLOCK: clear 2: "); +// } + + + + + public CPODControl2 getClickTrainControl() { + return cpodControl; + } + +// /* (non-Javadoc) +// * @see PamguardMVC.PamDataBlock#getDataSelectCreator() +// */ +// @Override +// public synchronized DataSelectorCreator getDataSelectCreator() { +// if (clickDataSelectCreator == null) { +// clickDataSelectCreator = new CTDataSelectCreator(clickTrainControl, this); +// } +// return clickDataSelectCreator; +// +// } + +} \ No newline at end of file diff --git a/src/cpod/CPODClickTrainDataUnit.java b/src/cpod/CPODClickTrainDataUnit.java new file mode 100644 index 00000000..2883e660 --- /dev/null +++ b/src/cpod/CPODClickTrainDataUnit.java @@ -0,0 +1,49 @@ +package cpod; + +import java.util.List; +import PamDetection.PamDetection; +import PamguardMVC.PamDataUnit; +import cpod.CPODClassification.CPODSpeciesType; +import detectiongrouplocaliser.DetectionGroupDataUnit; + +/** + * Base class for a click train data unit. + * Note this must implrement PamDetection to work with the clip generator. + * + * @author Jamie Macaulay + * + */ +public class CPODClickTrainDataUnit extends DetectionGroupDataUnit implements PamDetection { + + CPODClassification cpodClassification; + + + public CPODClickTrainDataUnit(long timeMilliseconds, List list, CPODClassification cpodClassification) { + super(timeMilliseconds, null); + this.cpodClassification=cpodClassification; + } + + public CPODSpeciesType getSpecies() { + return cpodClassification.species; + } + + public int getConfidence() { + return cpodClassification.qualitylevel; + } + + + public boolean isEcho() { + return cpodClassification.isEcho; + } + + /** + * Gte information on the click train + * @return + */ + public String getStringInfo() { + + + return String.format("Species: %s Confidence %d is echo? %b", getSpecies(), getConfidence(), isEcho()); + } + +} \ No newline at end of file diff --git a/src/cpod/CPODControl.java b/src/cpod/CPODControl.java index c095a825..58eec388 100644 --- a/src/cpod/CPODControl.java +++ b/src/cpod/CPODControl.java @@ -30,7 +30,8 @@ import PamguardMVC.dataOffline.OfflineDataLoadInfo; * @author Doug Gillespie * @author Jamie Macaulay * - */ + */ +@Deprecated public class CPODControl extends OfflineFileControl implements PamSettings { private CPODClickDataBlock cp1DataBlock, cp3DataBlock; @@ -91,7 +92,7 @@ public class CPODControl extends OfflineFileControl implements PamSettings { * @return milliseconds. */ public static long podTimeToMillis(long podTime) { - return podTime * 60L * 1000L - (25569L*3600L*24000L); + return CPODUtils.podTimeToMillis(podTime); } public long stretchClicktime(long rawTime) { diff --git a/src/cpod/CPODControl2.java b/src/cpod/CPODControl2.java index a8e0d92b..fb350c1d 100644 --- a/src/cpod/CPODControl2.java +++ b/src/cpod/CPODControl2.java @@ -3,6 +3,7 @@ package cpod; import java.awt.Frame; import java.io.File; import java.io.Serializable; +import java.util.List; import javax.swing.JMenuItem; @@ -16,16 +17,22 @@ import PamController.SettingsPane; import PamView.PamDetectionOverlayGraphics; import PamView.PamSymbol; import PamView.WrapperControlledGUISwing; +import cpod.dataPlotFX.CPODDPlotProvider; import cpod.dataPlotFX.CPODPlotProviderFX; import cpod.fx.CPODGUIFX; import cpod.fx.CPODSettingsPane; +import cpod.logging.CPODClickTrainLogging; +import cpod.logging.CPODSubDetLogging; import dataPlotsFX.data.TDDataProviderRegisterFX; +import detectionPlotFX.data.DDPlotRegister; import fileOfflineData.OfflineFileParams; +import javafx.concurrent.Task; +import pamScrollSystem.AbstractScrollManager; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX2AWT; /** - * CPOD control. Loads and manages CPOD (and hopefully soon FPOD) data into - * PAMGaurd. + * CPOD control. Loads and manages CPOD and FPOD data into + *PAMGaurd. *

* Note that this module (CPODControl) originally used a folder of CP1 files as it's file store but * this version now converts CP1/CP3 etc. into binary files instead in order to integrate into PAMGuard's @@ -36,9 +43,10 @@ import pamViewFX.fxNodes.pamDialogFX.PamDialogFX2AWT; */ public class CPODControl2 extends PamControlledUnit implements PamSettings { - private static final float CPOD_SAMPLE_RATE = 200000; - - private CPODClickDataBlock cp1DataBlock, cp3DataBlock; + /** + * The CPOD detection datablock + */ + private CPODClickDataBlock cp1DataBlock; /** * The CPOD paramters. @@ -78,19 +86,23 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { * The JavaFX settings pane for the cpod */ private CPODSettingsPane settingsPane; - + /** * CPOD importer. */ private CPODImporter cpodImporter; + private CPODClickTrainDataBlock clickTrainDataBlock; + + private CPODClickTrainLogging clickTrainDetLogging; + public CPODControl2(String unitName) { super("CPOD", unitName); addPamProcess(cpodProcess = new CPODProcess(this)); - cpodProcess.addOutputDataBlock(cp1DataBlock = new CPODClickDataBlock("CP1 Data", + cpodProcess.addOutputDataBlock(cp1DataBlock = new CPODClickDataBlock("CPOD Detections", cpodProcess, CPODMap.FILE_CP1)); cp1DataBlock.setDatagramProvider(cpodDataGramProvider[0] = new CPODDataGramProvider(this)); cp1DataBlock.setPamSymbolManager(new CPODSymbolManager(this, cp1DataBlock)); @@ -98,29 +110,44 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { //must set overlay draw so that hover text can be extracted from general projector and thus plotted on TDGraphFX . This is a HACK and should be sorted. cp1DataBlock.setOverlayDraw(new PamDetectionOverlayGraphics(cp1DataBlock, new PamSymbol())); - // add the CP3 data block - cpodProcess.addOutputDataBlock(cp3DataBlock = new CPODClickDataBlock("CP3 Data", - cpodProcess, CPODMap.FILE_CP3)); - cp3DataBlock.setPamSymbolManager(new CPODSymbolManager(this, cp3DataBlock)); - cp3DataBlock.setDatagramProvider(cpodDataGramProvider[1] = new CPODDataGramProvider(this)); - cp3DataBlock.setBinaryDataSource(new CPODBinaryStore(this, cp1DataBlock)); - //must set overlay draw so that hover text can be extracted from general projector and thus plotted on TDGraphFX . This is a HACK and should be sorted. - cp3DataBlock.setOverlayDraw(new PamDetectionOverlayGraphics(cp3DataBlock, new PamSymbol())); + + + // // add the CP3 data block + // cpodProcess.addOutputDataBlock(cp3DataBlock = new CPODClickDataBlock("CP3 Data", + // cpodProcess, CPODMap.FILE_CP3)); + // + // cp3DataBlock.setPamSymbolManager(new CPODSymbolManager(this, cp3DataBlock)); + // cp3DataBlock.setDatagramProvider(cpodDataGramProvider[1] = new CPODDataGramProvider(this)); + // cp3DataBlock.setBinaryDataSource(new CPODBinaryStore(this, cp1DataBlock)); + // //must set overlay draw so that hover text can be extracted from general projector and thus plotted on TDGraphFX . This is a HACK and should be sorted. + // cp3DataBlock.setOverlayDraw(new PamDetectionOverlayGraphics(cp3DataBlock, new PamSymbol())); + + + clickTrainDataBlock= new CPODClickTrainDataBlock(this, cpodProcess, "CPOD Click Trains", 0); + clickTrainDataBlock.SetLogging(clickTrainDetLogging = new CPODClickTrainLogging(this, clickTrainDataBlock)); + clickTrainDetLogging.setSubLogging(new CPODSubDetLogging(clickTrainDetLogging, clickTrainDataBlock)); + //create symbols for clicks trains + clickTrainDataBlock.setPamSymbolManager(new CPODTrainSymbolManager(clickTrainDataBlock)); + + //makes sure the click trains are loaded + int maxndays = 5; //maximum days to load. + AbstractScrollManager.getScrollManager().addToSpecialDatablock(clickTrainDataBlock, maxndays*24*60*60*1000L , maxndays*24*60*60*1000L); + + cpodProcess.addOutputDataBlock(clickTrainDataBlock); PamSettingManager.getInstance().registerSettings(this); - cpodProcess.setSampleRate(CPOD_SAMPLE_RATE, false); + cpodProcess.setSampleRate(CPODClickDataBlock.CPOD_SR, false); cpodImporter = new CPODImporter(this); - - + //FX display data providers CPODPlotProviderFX cpodPlotProviderFX = new CPODPlotProviderFX(this, cp1DataBlock); TDDataProviderRegisterFX.getInstance().registerDataInfo(cpodPlotProviderFX); - - cpodPlotProviderFX = new CPODPlotProviderFX(this, cp3DataBlock); - TDDataProviderRegisterFX.getInstance().registerDataInfo(cpodPlotProviderFX); - + // register the DD display + DDPlotRegister.getInstance().registerDataInfo(new CPODDPlotProvider(this, cp1DataBlock)); + // DDPlotRegister.getInstance().registerDataInfo(new CPODDPlotProvider(this, cp3DataBlock)); + // //swing time display data providers. // CPODPlotProvider cpodPlotProvider = new CPODPlotProvider(this, cp1DataBlock); @@ -138,26 +165,6 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { } - protected static File getCP3File(File cp1File) { - String newName = cp1File.getAbsolutePath(); - newName = newName.substring(0, newName.length()-4) + ".CP3"; - File cp3File = new File(newName); - return cp3File; - } - - - - - /** - * Convert POD time to JAVA millis - POD time is - * integer minutes past the same epoc as Windows uses - * i.e. 0th January 1900. - * @param podTime - * @return milliseconds. - */ - public static long podTimeToMillis(long podTime) { - return podTime * 60L * 1000L - (25569L*3600L*24000L); - } public long stretchClicktime(long rawTime) { if (cp1DataBlock.getDatagrammedMap() == null) { @@ -191,8 +198,16 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { return cp1DataBlock; } - public CPODClickDataBlock getCP3DataBlock() { - return cp3DataBlock; + // public CPODClickDataBlock getCP3DataBlock() { + // return cp3DataBlock; + // } + + /** + * Get the CPOD click train data block. + * @return the CPOD click train data block. + */ + public CPODClickTrainDataBlock getClickTrainDataBlock() { + return this.clickTrainDataBlock; } /** @@ -213,9 +228,6 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { /**** GUI ****/ - - - /** * Get the settings pane. * @@ -302,4 +314,15 @@ public class CPODControl2 extends PamControlledUnit implements PamSettings { return cpodImporter; } + /** + * Import POD data. This will either be a list of CPOD or FPOD files. + * @param files - the files to import. + * @return a list of import Tasks. + */ + public Task importPODData(List files) { + return cpodImporter.importCPODData(files); + } + + + } diff --git a/src/cpod/CPODDataGramProvider.java b/src/cpod/CPODDataGramProvider.java index 8c3dc2ce..6e2395b2 100644 --- a/src/cpod/CPODDataGramProvider.java +++ b/src/cpod/CPODDataGramProvider.java @@ -24,7 +24,17 @@ public class CPODDataGramProvider implements DatagramProvider { @Override public int addDatagramData(PamDataUnit dataUnit, float[] dataGramLine) { CPODClick cpodClick = (CPODClick) dataUnit; - dataGramLine[cpodClick.getkHz()] ++; + + int minKhz = cpodClick.getkHz()- cpodClick.getBw()/2; + int maxkHz = cpodClick.getkHz()- cpodClick.getBw()/2; + //each datagram line is a 1kHz bin + for (int i=0; i=minKhz && i<=maxkHz) { + dataGramLine[i] ++; + } + } + +// dataGramLine[cpodClick.getkHz()] ++; return 1; } diff --git a/src/cpod/CPODDialog.java b/src/cpod/CPODDialog.java index abe27518..99d845d7 100644 --- a/src/cpod/CPODDialog.java +++ b/src/cpod/CPODDialog.java @@ -15,6 +15,7 @@ import PamUtils.SelectFolder; import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; +@Deprecated public class CPODDialog extends PamDialog { private CPODControl cpodControl; diff --git a/src/cpod/CPODFile.java b/src/cpod/CPODFile.java new file mode 100644 index 00000000..6d739771 --- /dev/null +++ b/src/cpod/CPODFile.java @@ -0,0 +1,45 @@ +package cpod; + +import java.io.File; + +import cpod.CPODUtils.CPODFileType; + +/** + * A CP1 along with it's CP3 file + * @author Jamie Macaulay + * + */ +public class CPODFile { + + /** + * A CP1 or FP1 file + */ + public File cp1File = null; + + /** + * A CP3 or CP3 file + */ + public File cp3File = null; + + /** + * Get the name of the file + * @return the name of the file; + */ + public String getName() { + String fileName = ""; + if (cp1File!=null) fileName+=cp1File.getName() + " "; + if (cp3File!=null) fileName+=cp3File.getName() + " "; + return fileName; + } + + /** + * Check whether the current CPOD file is a CPOD or FPOD + * @return true if FPOD. + */ + public boolean isFPOD() { + if (cp1File !=null) return CPODUtils.getFileType(cp1File).equals(CPODFileType.FP1); + if (cp3File !=null) return CPODUtils.getFileType(cp3File).equals(CPODFileType.FP3); + return false; + } + +} diff --git a/src/cpod/CPODImporter.java b/src/cpod/CPODImporter.java index a9294a3b..0fe65221 100644 --- a/src/cpod/CPODImporter.java +++ b/src/cpod/CPODImporter.java @@ -1,14 +1,14 @@ package cpod; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.concurrent.ExecutorService; @@ -24,10 +24,15 @@ import binaryFileStorage.BinaryFooter; import binaryFileStorage.BinaryObjectData; import binaryFileStorage.BinaryOutputStream; import binaryFileStorage.BinaryStore; +import cpod.CPODReader.CPODHeader; +import cpod.CPODUtils.CPODFileType; +import cpod.FPODReader.FPODHeader; +import cpod.FPODReader.FPODdata; +import javafx.application.Platform; import javafx.concurrent.Task; /** - * Imports data CPOD data and converts into binary files. + * Imports FPOD and CPOD data and converts into binary files. * * @author Jamie Macaulay * @@ -36,32 +41,10 @@ public class CPODImporter { public static final int MAX_SAVE = 2000000; - - private File cpFile; - private long fileStart; - - private long fileEnd; - private short podId; - private byte waterDepth; - private CPODControl2 cpodControl; - float[] tempDataGramData; - - // /** - // * Flag for a CP1 file. - // */ - // public static final int FILE_CP1 = 1; - // - // /** - // * Flag for a CP3 file. - // */ - // public static final int FILE_CP3 = 3; - - - CPODFileType cpFileType = CPODFileType.CP1; /** - * Hnadles the queue for importing files tasks + * Handles the queue for importing files tasks */ private ExecutorService exec = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r); @@ -69,312 +52,53 @@ public class CPODImporter { return t ; }); - /** - * CPOD file types - * @author Jamie Macaulay - * - */ - public enum CPODFileType { - CP1("CP1"), - CP3("CP3"); - // FP1("fp1"), - // FP3("fp3"); - - private String text; - - CPODFileType(String text) { - this.text = text; - } - - public String getText() { - return this.text; - } - - public static CPODFileType fromString(String text) { - for (CPODFileType b : CPODFileType.values()) { - if (b.text.equalsIgnoreCase(text)) { - return b; - } - } - return null; - } - } - public CPODImporter(CPODControl2 cpodControl) { this.cpodControl = cpodControl; - // if (fileType != cpFileType) { - // System.err.println("CPOD Mismatched file type " + cpFile.getAbsolutePath()); - // } } - public static CPODFileType getFileType(File cpFile) { - for (int i=0; ifrom && nClicks<(from+maxNum))) { - - //System.out.println("Create a new CPOD click: "); - CPODClick cpodClick = processClick(nMinutes, shortData); - - dataBlock.addPamData(cpodClick); - - } - - // // now remove the data unit from the data block in order to clear up memory. Note that the remove method - // // saves the data unit to the Deleted-Items list, so clear that as well (otherwise we'll just be using - // // up all the memory with that one) - // dataBlock.remove(cpodClick); - // dataBlock.clearDeletedList(); - } - else { - nMinutes ++; - processMinute(byteData); - } - totalBytes += dataSize; - } - bis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println(String.format("File read: Clicks %d, minutes %d", nClicks, nMinutes)); - - return nClicks; + return cpodClick; } - /** - * A new minute. Don;t think we need to do anything here.? - * @param byteData - */ - private void processMinute(byte[] byteData) { - // TODO Auto-generated method stub - - } - - private CPODClick processClick(int nMinutes, short[] shortData) { - /* - * - */ - return CPODClick.makeClick(cpodControl, fileStart + nMinutes * 60000L, shortData); - } - - /** - * Java will only have read signed bytes. Nick clearly - * uses a lot of unsigned data, so convert and inflate to int16. - * @param signedByte - * @return unsigned version as int16. - */ - static short toUnsigned(byte signedByte) { - short ans = signedByte; - if (ans < 0) { - ans += 256; - } - return ans; - } - - /** - * Is it the end of the file ? - * @param byteData - * @return true if all bytes == 255 - */ - static boolean isFileEnd(byte[] byteData) { - for (int i = 0; i < byteData.length; i++) { - // if ((byteData[i] ^ 0xFF) != 0) { - // return false; - // } - if (byteData[i] != -1) { - return false; - } - } - return true; - } - - boolean readHeader(BufferedInputStream bis) { - int bytesRead; - byte[] headData = new byte[getHeadSize(cpFileType)]; - try { - bytesRead = bis.read(headData); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - if (bytesRead != headData.length) { - return false; - } - // read as a load of 4 byte integers and see what we get ! - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(headData)); - int nShort = headData.length / 2; - short[] shortData = new short[nShort]; - for (int i = 0; i < shortData.length; i++) { - try { - shortData[i] = dis.readShort(); - if (shortData[i] == 414) { -// System.out.println("Found id at %d" + i); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - dis = new DataInputStream(new ByteArrayInputStream(headData)); - int nFloat = headData.length / 4; - float[] floatData = new float[nFloat]; - for (int i = 0; i < floatData.length; i++) { - try { - floatData[i] = dis.readFloat(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - dis = new DataInputStream(new ByteArrayInputStream(headData)); - int nInt = headData.length / 4; - int[] intData = new int[nInt]; - for (int i = 0; i < nInt; i++) { - try { - intData[i] = dis.readInt(); - int bOff = i*4; - int sOff = i*2; -// if (intData[i] > 0) -// System.out.println(String.format("%d, Int = %d, Float = %3.5f, Short = %d,%d, bytes = %d,%d,%d,%d", i, intData[i], -// floatData[i], -// shortData[sOff], shortData[sOff+1], -// headData[bOff], headData[bOff+1], headData[bOff+2], headData[bOff+3])); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - fileStart = CPODControl.podTimeToMillis(intData[64]); - fileEnd = CPODControl.podTimeToMillis(intData[65]); - // other times seem to be packed in ints 66 - 69. - podId = shortData[50]; - waterDepth = headData[8]; - - return true; - } - - /** - * @return the fileStart - */ - public long getFileStart() { - return fileStart; - } - - /** - * Get the data - * @param type - * @return - */ - public CPODClickDataBlock getDataBlock(CPODFileType type) { - switch (type) { - case CP1: - return this.cpodControl.getCP1DataBlock(); - case CP3: - return this.cpodControl.getCP3DataBlock(); - } - return null; - } /** * Run the import task. */ - public void runImportTask(ArrayList files, CPODClickDataBlock clickDataBlock) { - Task cpodTask = new CPODImportTask(files, clickDataBlock); + public void runImportTask(ArrayList files, CPODClickDataBlock clickDataBlock, CPODClickTrainDataBlock clickTrainDataBlock) { + Task cpodTask = new CPODImportTask(files, clickDataBlock, clickTrainDataBlock); Thread th = new Thread(cpodTask); @@ -383,61 +107,102 @@ public class CPODImporter { th.start(); } + /** * Import the CPOD data from a certain file type. * @param files to import - a list of CPOD compatible files (can be a mix but only the files corresponding to type will be processed) * @param type - the type flag of the file e.g. CPODFileType.CP1 * @return the CPOD import task. */ - public Task importCPODDataTask(List files, CPODFileType type) { + public Task importCPODDataTask(List files) { - List cpXFIles = new ArrayList(); - - for (int i=0; i> importCPODData(List files) { - - List> tasks = new ArrayList>(); - - for (int i=0; i cp1Task = importCPODDataTask(files, CPODFileType.values()[i]); - tasks.add(cp1Task); + public Task importCPODData(List files) { + // Need to create a list of cp1 and cp3 files. + ArrayList cpodFiles = new ArrayList(); + + CPODFile cpodFile; + while (files.size()>0) { + cpodFile = new CPODFile(); + + CPODFileType type = CPODUtils.getFileType(files.get(0)); + + File currentFile = files.get(0); + switch (type) { + case CP1: + case FP1: + cpodFile.cp1File= files.get(0); + break; + case CP3: + case FP3: + cpodFile.cp3File= files.get(0); + break; + default: + break; + + } + files.remove(0); + + //now search for the corresponding CP3 or CP1 file + for (int i=0; i{ - PamController.getInstance().updateDataMap(); - }); - - //TODO what if a task is cancelled... - return tasks; + else return false; } + /** * Run the tasks - * @param tasks - the tasks. + * @param task - the tasks. */ - public void runTasks(List> tasks) { - - for (int i=0; i task) { + this.exec.execute(task); } @@ -446,14 +211,13 @@ public class CPODImporter { * @author Jamie Macaulay * */ - class CPODImportTask extends Task { - + class CPODImportTask extends Task { /** * List of files, either CP1 or CP3 */ - private List cpxFile; + private List cpxFile; /** * Reference to the binary store. @@ -468,27 +232,37 @@ public class CPODImporter { /** * The binary stream */ - private BinaryOutputStream binaryStream; + private BinaryOutputStream binaryStream; + + /** + * Reference to the CPOD click train datablock. + */ + private CPODClickTrainDataBlock clickTrainDataBlock; + + private ArrayList cpodTrainList; /** * * @param cpxfiles - a list of CP1 or CP3 files. * @param cpodDataBlock - the CPOD data block. */ - public CPODImportTask(List cpxfiles, CPODClickDataBlock cpodDataBlock) { + public CPODImportTask(List cpxfiles, CPODClickDataBlock cpodDataBlock, CPODClickTrainDataBlock clickTrainDataBlock) { this.cpxFile = cpxfiles; this.cpodDataBlock=cpodDataBlock; + this.clickTrainDataBlock=clickTrainDataBlock; } + @Override protected Integer call() throws Exception { try { + BinaryDataSource binarySource = cpodDataBlock.getBinaryDataSource(); binaryStore = (BinaryStore) PamController.getInstance().findControlledUnit(BinaryStore.defUnitType); if (binaryStore == null) { String msg = "Error: Can't convert CPOD files unless you have a Binary Storage module.
" + "Please close this dialog and add/configure a binary store first."; - int ans = WarnOnce.showWarning(null, "CPOD Import", msg, WarnOnce.OK_OPTION); + WarnOnce.showWarning(null, "CPOD Import", msg, WarnOnce.OK_OPTION); System.out.println("Can't convert CPOD files unless you have a binary storage module"); return null; } @@ -497,26 +271,54 @@ public class CPODImporter { binarySource.setBinaryStorageStream(outputStream); binaryStream = cpodDataBlock.getBinaryDataSource().getBinaryStorageStream(); - for (int i=0; i iterator = cpodDataBlock.getListIterator(0); + this.updateProgress(-1, 1); + + System.out.println("Number of CPOD data units in the data block: " + cpodDataBlock.getUnitsCount() + " progress: " + (i+1) + " " + cpxFile.size() ); + + //need to make a copy of the data incase we clear the cp2 datablock to look for previously + //loaded detection. Not memory efficient but the easiest way to do and only occurs + //in the laoding process. + ListIterator iterator = cpodDataBlock.getDataCopy().listIterator(); + cpodDataBlock.clearAll(); + //save the click trains to the database + clickTrainDataBlock.saveViewerData(); + + //save the raw clicks to the binary file.s CPODClick click; double day = -1; @@ -525,15 +327,16 @@ public class CPODImporter { while (iterator.hasNext()) { if (this.isCancelled()) return -1; click = iterator.next(); - count++; - - //System.out.println("Saving click: " + click.getUID()); //new binary file every daya; cal.setTimeInMillis(click.getTimeMilliseconds()); int dayYear = cal.get(Calendar.DAY_OF_YEAR); + if (day!=dayYear) { - this.updateProgress(i+(count/(double) totalClicks), cpxFile.size()); + + //set the progress + double progress=(((double) click.getTimeMilliseconds()-fileStartEnd[0]))/(fileStartEnd[1]-fileStartEnd[0]); + this.updateProgress(i+(progress), cpxFile.size()); if (day>-1) { //close current file @@ -544,7 +347,11 @@ public class CPODImporter { } System.out.println("Open new binary file: " + PamCalendar.formatDBDateTime(click.getTimeMilliseconds())); - this.updateMessage("Saving file: " + PamCalendar.formatDBDateTime(click.getTimeMilliseconds())); + + //send an update message + final String timeMillisStr = PamCalendar.formatDBDateTime(click.getTimeMilliseconds()); + + this.updateMessage(("Saving file: " + timeMillisStr)); //write the module head binaryStream.openOutputFiles(click.getTimeMilliseconds()); @@ -556,24 +363,286 @@ public class CPODImporter { data = cpodDataBlock.getBinaryDataSource().getPackedData(click); this.binaryStream.storeData(data.getObjectType(), click.getBasicData(), data); + nClicks++; } - cpodDataBlock.clearAll(); - - //update number of clicks. - nClicks=nClicks+MAX_SAVE; + + nClicks=nClicks+1; //so we start at the right click. + cpodDataBlock.clearAll(); + } - + } } catch (Exception e) { e.printStackTrace(); } - System.out.println("CPOD import thread finished: "); + this.updateMessage("Finished saving detections"); + System.out.println("CPOD import thread finished: " + this); return 1; } + + + /** + * Rounds millis to start of da=y + * @param millis + * @return + */ + long roundToDay(long millis) { + Date date = new Date(millis); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTimeInMillis(); + } + + /** + * Import the CPOD file. + * @param cpFile2 - the cp1 file + * @param dataBlock - the data block + * @param from - the click index to save from. e.g. 100 means that only click 100 + in the file is saved + * @param maxNum - the maximum number to import + * @return the total number of clicks in the file. + */ + private int importCPODFile(CPODFile cpFile, CPODClickDataBlock dataBlock, CPODClickTrainDataBlock clickTrainDataBlock, int from, int maxNum) { + + ArrayList cpodCP1Data = null; + ArrayList cpodCP3Data = null; + + try { + if (cpFile.isFPOD()) { + //load a chunk of FP1 data + cpodCP1Data = FPODReader.importFPODFile(cpFile.cp1File, from, maxNum); + //load all FP3 data + cpodCP3Data = FPODReader.importFPODFile(cpFile.cp3File, 0, Integer.MAX_VALUE); + } + else { + //load a chunk of CP1 data + cpodCP1Data = CPODReader.importCPODFile(cpFile.cp1File, from, maxNum); + //load all CP3 data + cpodCP3Data = CPODReader.importCPODFile(cpFile.cp3File, 0, Integer.MAX_VALUE); + } + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + + //create an ArrayList + ArrayList cpodData = new ArrayList(); + + int repcount = 0; + //now we get rid if duplicate clicks + if (cpodCP1Data != null && cpodCP3Data != null) { + CPODClickOmparator cpodComparator = new CPODClickOmparator(); + + //make sure to sort the list. + Collections.sort(cpodCP3Data, cpodComparator);; + //here we need to replace the CP1 detection in the CP1 file with the CP3 detections in the CP3 file + for (int j=0; j=0) { + cpodCP1Data.set(j, cpodCP3Data.get(index)); + repcount++; + } + } + cpodData= cpodCP1Data; //cp1 data now contains CP3 clicks. + } + else if (cpodCP1Data != null) cpodData= cpodCP1Data; //only CP1 data imported + else if (cpodCP3Data != null) cpodData= cpodCP3Data; //only CP3 data imported + + //now iterate through the detections and grab the click classiifcations. + + HashMap cpodClickTrains = new HashMap(); + + int nClicks = 0; + for (int i=0; i { + @Override public int compare(CPODClick s1, CPODClick s2) + { + if (s1.getTimeMilliseconds() > s2.getTimeMilliseconds()) { + return 1; + } + else if (s1.getTimeMilliseconds() < s2.getTimeMilliseconds()) { + return -1; + } + else if (s1.getTimeMilliseconds() == s2.getTimeMilliseconds()) { + +// return 0; + if (s1.getStartSample()>(s2.getStartSample())) return 1; + else if (s1.getStartSample()<(s2.getStartSample())) return -1; + else return 0; + } + return -1; + } + } + + + public static void main(String[] args) { + + + CPODFile cpFile = new CPODFile(); + try { + + //CPOD + cpFile.cp1File = new File("D:\\Dropbox\\PAMGuard_dev\\tutorials\\CPOD_wav\\data\\Hyskeir\\CPOD\\0740 Hyskeir 2022 12 02 POD1655 file01.CP1"); + cpFile.cp3File = new File("D:\\Dropbox\\PAMGuard_dev\\tutorials\\CPOD_wav\\data\\Hyskeir\\CPOD\\0740 Hyskeir 2022 12 02 POD1655 file01.CP3"); + +// //FPOD +// cpFile.cp1File = new File("D:\\Dropbox\\PAMGuard_dev\\tutorials\\CPOD_wav\\data\\NunBank\\FPOD\\0866 NunBankB 2023 06 27 FPOD_6480 file0.FP1"); +// cpFile.cp3File = new File("D:\\Dropbox\\PAMGuard_dev\\tutorials\\CPOD_wav\\data\\NunBank\\FPOD\\0866 NunBankB 2023 06 27 FPOD_6480 file0.FP3"); +// + + ArrayList cpodCP1Data; + ArrayList cpodCP3Data; + if (cpFile.isFPOD()) { + //load a chunk of FP1 data + cpodCP1Data = FPODReader.importFPODFile(cpFile.cp1File, 0, MAX_SAVE); + + //load all FP3 data + cpodCP3Data = FPODReader.importFPODFile(cpFile.cp3File, 0, Integer.MAX_VALUE); + } + else { + //load a chunk of CP1 data + cpodCP1Data = CPODReader.importCPODFile(cpFile.cp1File, 0, MAX_SAVE); + //load all CP3 data + cpodCP3Data = CPODReader.importCPODFile(cpFile.cp3File, 0, Integer.MAX_VALUE); + } + + //create an ArrayList + ArrayList cpodData = new ArrayList(); + + int repcount = 0; + int lastCP1=-1; + //now we get rid if duplicate clicks + if (cpodCP1Data != null && cpodCP3Data != null) { + CPODClickOmparator cpodComparator = new CPODClickOmparator(); + //make sure to sort the list. + Collections.sort(cpodCP3Data, cpodComparator); + Collections.sort(cpodCP1Data, cpodComparator); + + long lastCP1Date = cpodCP1Data.get(cpodCP1Data.size()-1).getTimeMilliseconds(); + + + for (int j=0; jlastCP1Date) { + lastCP1 = j; + break; + } + } + + //here we need to replace the CP1 detection in the CP1 file with the CP3 detections in the CP3 file + for (int j=0; jlastCP1) { + break; + } + + int index = Collections.binarySearch(cpodCP1Data, cpodCP3Data.get(j), cpodComparator ); + //replace + if (index>=0) { + repcount++; + if (j%1000==0) { + System.out.println("Match: cp1 " + cpodCP1Data.get(index).getStartSample() + " " + cpodCP3Data.get(j).getStartSample() + +" diff " + (cpodCP1Data.get(index).getStartSample() - cpodCP3Data.get(j).getStartSample())); + } + } + } + cpodData= cpodCP1Data; //cp1 data now contains CP3 clicks. + } + else if (cpodCP1Data != null) cpodData= cpodCP1Data; //only CP1 data imported + else if (cpodCP3Data != null) cpodData= cpodCP3Data; //only CP3 data imported + + + System.out.println("CPOD data: n CP1: " + cpodCP1Data.size() + " n CP3: " + cpodCP3Data.size() + " n matches " + repcount + " of " + lastCP1); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } } diff --git a/src/cpod/CPODLoader.java b/src/cpod/CPODLoader.java index 73d4dbfb..2c26315c 100644 --- a/src/cpod/CPODLoader.java +++ b/src/cpod/CPODLoader.java @@ -17,6 +17,7 @@ import fileOfflineData.OfflineFileMapPoint; * @author Doug Gillespie * */ +@Deprecated public class CPODLoader { private CPODControl cpodControl; diff --git a/src/cpod/CPODMap.java b/src/cpod/CPODMap.java index cd0b8301..df85bfe5 100644 --- a/src/cpod/CPODMap.java +++ b/src/cpod/CPODMap.java @@ -17,6 +17,7 @@ import PamguardMVC.RequestCancellationObject; import dataGram.DatagramDataPoint; import fileOfflineData.OfflineFileMapPoint; +@Deprecated public class CPODMap { private File cpFile; @@ -29,6 +30,7 @@ public class CPODMap { public static final int FILE_CP1 = 1; public static final int FILE_CP3 = 3; + int cpFileType = 0; diff --git a/src/cpod/CPODReader.java b/src/cpod/CPODReader.java new file mode 100644 index 00000000..8d368783 --- /dev/null +++ b/src/cpod/CPODReader.java @@ -0,0 +1,369 @@ +package cpod; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import PamUtils.PamCalendar; +import cpod.CPODUtils.CPODFileType; +import cpod.FPODReader.FPODdata; + +/** + * Read CPOD data. + *

+ * Note this should, as much as possible not have any PAMGuard based code. + * + * @author Jamie Macaulay + * @author Douglas Gillespie + * + */ +public class CPODReader { + + + /** + * A new minute. Don;t think we need to do anything here.? + * @param byteData + */ + private static void processMinute(byte[] byteData) { + // TODO Auto-generated method stub + + } + + public static CPODHeader readHeader(File cpFile) { + + BufferedInputStream bis = null; + FileInputStream fileInputStream = null; + + try { + bis = new BufferedInputStream(fileInputStream = new FileInputStream(cpFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + return readHeader( bis, CPODUtils.getFileType(cpFile)); + + } + + + + public static CPODHeader readHeader(BufferedInputStream bis, CPODFileType cpFileType) { + int bytesRead; + byte[] headData = new byte[getHeadSize(cpFileType)]; + try { + bytesRead = bis.read(headData); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + if (bytesRead != headData.length) { + return null; + } + // read as a load of 4 byte integers and see what we get ! + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(headData)); + int nShort = headData.length / 2; + short[] shortData = new short[nShort]; + for (int i = 0; i < shortData.length; i++) { + try { + shortData[i] = dis.readShort(); + if (shortData[i] == 414) { + // System.out.println("Found id at %d" + i); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + dis = new DataInputStream(new ByteArrayInputStream(headData)); + int nFloat = headData.length / 4; + float[] floatData = new float[nFloat]; + for (int i = 0; i < floatData.length; i++) { + try { + floatData[i] = dis.readFloat(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + dis = new DataInputStream(new ByteArrayInputStream(headData)); + int nInt = headData.length / 4; + int[] intData = new int[nInt]; + for (int i = 0; i < nInt; i++) { + try { + intData[i] = dis.readInt(); + int bOff = i*4; + int sOff = i*2; + // if (intData[i] > 0) + // System.out.println(String.format("%d, Int = %d, Float = %3.5f, Short = %d,%d, bytes = %d,%d,%d,%d", i, intData[i], + // floatData[i], + // shortData[sOff], shortData[sOff+1], + // headData[bOff], headData[bOff+1], headData[bOff+2], headData[bOff+3])); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + CPODHeader header = new CPODHeader(); + + header.fileStart = CPODUtils.podTimeToMillis(intData[64]); + header.fileEnd = CPODUtils.podTimeToMillis(intData[65]); + // other times seem to be packed in ints 66 - 69. + header.podId = shortData[50]; + header.waterDepth = headData[8]; + + return header; + } + + + /** + * Import a CPOD file. + * @param cpFile - the CP1 or CP3 file. + * @param from - the click index to save from. e.g. 100 means that only click 100 + in the file is saved + * @param maxNum - the maximum number to import + * @return the total number of clicks in the file. + */ + protected static ArrayList importCPODFile(File cpFile, int from, int maxNum) { + + if (cpFile==null) return null; + + //holds a map of the click train detections. + HashMap clickTrains = new HashMap(); + //the current classification + CPODClassification cpodClassification; + + ArrayList clicks = new ArrayList(); + BufferedInputStream bis = null; + int bytesRead; + FileInputStream fileInputStream = null; + long totalBytes = 0; + + CPODFileType cpFileType = CPODUtils.getFileType(cpFile); + + try { + bis = new BufferedInputStream(fileInputStream = new FileInputStream(cpFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + CPODHeader header = readHeader(bis, cpFileType); + if (header == null) { + return null; + }; + + totalBytes = getHeadSize(cpFileType); + int dataSize = getDataSize(cpFileType); + byte[] byteData = new byte[dataSize]; + short[] shortData = new short[dataSize]; + int fileEnds = 0; + boolean isClick; + // first record is always a minute mark, so start + // at -1 to avoid being skipped forward one minute. + int nClicks = 0, nMinutes = -1; + try { + while (true) { + bytesRead = bis.read(byteData); + for (int i = 0; i < bytesRead; i++) { + shortData[i] = CPODUtils.toUnsigned(byteData[i]); + } + if (isFileEnd(byteData)) { + fileEnds++; + } + else { + fileEnds = 0; + } + if (fileEnds == 2) { + break; + } + + isClick = byteData[dataSize-1] != -2; + if (isClick) { + + if (from<0 || (nClicks>from && nClicks<(from+maxNum))) { + + //System.out.println("Create a new CPOD click: "); + CPODClick cpodClick = processCPODClick(nMinutes, shortData, header); + + + if (cpFileType.equals(CPODFileType.CP3)) { + + short trainID = shortData[20]; + +// short species = (short) (shortData[36] & (112 >> 4)); + + short quality = (short) (shortData[36] & 3); + + short species = (short) (shortData[36] >> 3); + +// short species = (short) ((shortData[19] >> 2) & 7); +// short quality = (short) (shortData[19] & 3); + + //generate a unique train ID within the file + int trainUID = Integer.valueOf(String.format("%06d", nMinutes) + String.format("%d", trainID)); + + //find the click train from the hash map - if it is not there, create a new one. + cpodClassification = clickTrains.get(trainUID); + + if (cpodClassification==null) { + cpodClassification = new CPODClassification(); + cpodClassification.isEcho = false; + cpodClassification.clicktrainID = trainUID; + cpodClassification.species = CPODUtils.getCPODSpecies(species); + cpodClassification.qualitylevel = quality; + + clickTrains.put(trainUID, cpodClassification); +// long timeMillis = PamCalendar.msFromDate(2023, 1, 6, 22, 32, 41, 200); +// if (cpodClick.getTimeMilliseconds()>timeMillis && cpodClick.getTimeMilliseconds()<(timeMillis+20000)) { +// System.out.println("Click train ID: " + trainUID + " minutes: " + nMinutes + " species: " + species + " quality level: " + quality); +// System.out.println(String.format("

QClass %d, SpClass %d", CPODUtils.getBits(shortData[19], (short) 0x3), +// CPODUtils.getBits(shortData[19], (short) 0b11100))); +// } + } + cpodClick.setClassification(cpodClassification); + } + + clicks.add(cpodClick); + + if (nClicks%100000==0) { + System.out.println("CPOD data: " + nClicks + " " + PamCalendar.formatDateTime(cpodClick.getTimeMilliseconds()) + " " +cpodClick.getBw() + " " +shortData[4] ); +// if (nClicks>0)return clicks; //FIXME + } + } + + nClicks++; + // // now remove the data unit from the data block in order to clear up memory. Note that the remove method + // // saves the data unit to the Deleted-Items list, so clear that as well (otherwise we'll just be using + // // up all the memory with that one) + // dataBlock.remove(cpodClick); + // dataBlock.clearDeletedList(); + } +// else { +// nMinutes ++; +// processMinute(byteData); +// } + else if(CPODUtils.toUnsigned(byteData[dataSize-1])==254){ + nMinutes ++; + } + totalBytes += dataSize; + } + bis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println(String.format("File read: Clicks %d, minutes %d", nClicks, nMinutes)); + + return clicks; + } + + public static int getHeadSize(CPODFileType fileType) { + switch (fileType) { + case CP1: + return 360; + case CP3: + return 720; + case FP1: + return 1024; + case FP3: + return 1024; + } + return 0; + } + + public static int getDataSize(CPODFileType fileType) { + switch (fileType) { + case CP1: + return 10; + case CP3: + return 40; + case FP1: + return 16; + case FP3: + return 32; + } + return 0; + } + + /** + * Holds an CPOD header information + *

+ * Note that is pretty opaque what all this means. The important parameters have been commented. + */ + public static class CPODHeader { + + public byte waterDepth; + public short podId; + public long fileEnd; + public long fileStart; + + } + + /** + * Is it the end of the file ? + * @param byteData + * @return true if all bytes == 255 + */ + public static boolean isFileEnd(byte[] byteData) { + for (int i = 0; i < byteData.length; i++) { + // if ((byteData[i] ^ 0xFF) != 0) { + // return false; + // } + if (byteData[i] != -1) { + return false; + } + } + return true; + } + + /** + * Create a CPOD click object from CPOD data. + * @param nMinutes + * @param shortData + * @param header + * @return + */ + private static CPODClick processCPODClick(int nMinutes, short[] shortData, CPODHeader header) { + + long minuteMillis = header.fileStart + nMinutes * 60000L; + + int t = shortData[0]<<16 | + shortData[1]<<8 | + shortData[2]; // 5 microsec intervals ! + long tMillis = minuteMillis + t/200; + + + + // do a sample number within the file as 5us intervals + long fileSamples = (long) (((minuteMillis*60) + (t*5/1000000.))*CPODClickDataBlock.CPOD_SR); + + /* + * + */ + return CPODClick.makeCPODClick(tMillis, fileSamples, shortData); + } + + /** + * Test the program + * @param args + */ + public static void main(String[] args) { + // String filePath = "/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/CPOD/FPOD_NunBank/0866 NunBankB 2023 06 27 FPOD_6480 file0.FP1"; + + String filePath = "D:\\Dropbox\\PAMGuard_dev\\tutorials\\CPOD_wav\\data\\Hyskeir\\CPOD\\0740 Hyskeir 2022 12 02 POD1655 file01.CP3"; + // String filePath = "D:\\DropBox\\PAMGuard_dev\\CPOD\\FPOD_NunBank\\0866 NunBankB 2023 06 27 FPOD_6480 file0.FP3"; + + File fpfile = new File(filePath); + + + importCPODFile( fpfile, 0, Integer.MAX_VALUE); + + + } + +} diff --git a/src/cpod/CPODSpeciesModifier.java b/src/cpod/CPODSpeciesModifier.java new file mode 100644 index 00000000..446d58b2 --- /dev/null +++ b/src/cpod/CPODSpeciesModifier.java @@ -0,0 +1,62 @@ +package cpod; + +import java.awt.Color; + +import PamView.GeneralProjector; +import PamView.PamSymbolType; +import PamView.symbol.PamSymbolChooser; +import PamView.symbol.SymbolData; +import PamView.symbol.modifier.SymbolModType; +import PamView.symbol.modifier.SymbolModifier; +import PamguardMVC.PamDataUnit; +import cpod.CPODClassification.CPODSpeciesType; + +public class CPODSpeciesModifier extends SymbolModifier { + + final Color porpColor = new Color(93,30,255); + + final Color dolphColor = new Color(255,160,0); + + private SymbolData symbolData = new SymbolData(PamSymbolType.SYMBOL_CIRCLE, 5, 5, true, java.awt.Color.BLACK, java.awt.Color.BLACK); + + public CPODSpeciesModifier(PamSymbolChooser symbolChooser) { + super("Species", symbolChooser, SymbolModType.FILLCOLOUR | SymbolModType.LINECOLOUR); + } + + @Override + public SymbolData getSymbolData(GeneralProjector projector, PamDataUnit dataUnit) { + + CPODClick cpodClick = (CPODClick) dataUnit; + + Color color = Color.BLACK; + + + if (cpodClick.getCPODClickTrain()!=null) { + + CPODSpeciesType species = cpodClick.getCPODClickTrain().getSpecies(); + + switch(species) { + case DOLPHIN: + color=dolphColor; + break; + case NBHF: + color=porpColor;; + break; + case SONAR: + color = Color.DARK_GRAY; + break; + case UNKNOWN: + break; + default: + break; + } + + } + + symbolData.setFillColor(color); + symbolData.setLineColor(color); + + return symbolData; + } + +} diff --git a/src/cpod/CPODSymbolManager.java b/src/cpod/CPODSymbolManager.java index 361f6e33..92903425 100644 --- a/src/cpod/CPODSymbolManager.java +++ b/src/cpod/CPODSymbolManager.java @@ -1,11 +1,13 @@ package cpod; +import java.awt.Color; + import PamController.PamControlledUnit; +import PamView.PamSymbolType; import PamView.symbol.PamSymbolChooser; import PamView.symbol.StandardSymbolManager; import PamView.symbol.SymbolData; import PamView.symbol.modifier.PeakFreqModifier; -import PamView.symbol.modifier.SuperDetSymbolModifier; import PamguardMVC.PamDataBlock; public class CPODSymbolManager extends StandardSymbolManager { @@ -15,6 +17,9 @@ public class CPODSymbolManager extends StandardSymbolManager { */ private PamControlledUnit cpodControl; + + private static SymbolData defaultSymbol = new SymbolData(PamSymbolType.SYMBOL_CIRCLE, 10, 10, true, Color.BLACK, Color.BLACK); + // /** // * Flag to colour clicks by their frequency. It has to be one higher than the other options. // * @@ -22,7 +27,7 @@ public class CPODSymbolManager extends StandardSymbolManager { // public static final int COLOUR_BY_FREQ= 6; public CPODSymbolManager(PamControlledUnit cpodControl, PamDataBlock pamDataBlock) { - super(pamDataBlock, new SymbolData()); + super(pamDataBlock, defaultSymbol); this.cpodControl = cpodControl; addSymbolOption(HAS_SYMBOL); } @@ -44,13 +49,15 @@ public class CPODSymbolManager extends StandardSymbolManager { @Override public void addSymbolModifiers(PamSymbolChooser psc) { - super.addSymbolModifiers(psc); //add the peak frequency modifier that allows clicks to be coloured by peak frequency. psc.addSymbolModifier(new PeakFreqModifier(psc)); - // we can also add some default behaviour here to match the old behaviour + //add the peak frequency modifier that allows clicks to be coloured by peak frequency. + psc.addSymbolModifier(new CPODSpeciesModifier(psc)); + + // we can also add some default behaviour here to match the old behaviours // these will get overridden once user options are set, but it's good to give defaults. // SymbolModifier eventMod = psc.hasSymbolModifier(SuperDetSymbolModifier.class); // if (eventMod != null) { diff --git a/src/cpod/CPODTrainSymbolManager.java b/src/cpod/CPODTrainSymbolManager.java new file mode 100644 index 00000000..599bec05 --- /dev/null +++ b/src/cpod/CPODTrainSymbolManager.java @@ -0,0 +1,16 @@ +package cpod; + +import PamView.symbol.SymbolData; +import PamguardMVC.superdet.swing.SuperDetectionSymbolManager; + +public class CPODTrainSymbolManager extends SuperDetectionSymbolManager { + + public CPODTrainSymbolManager(CPODClickTrainDataBlock pamDataBlock) { + super(pamDataBlock, new SymbolData()); + super.setSpecialColourName("Click Train"); + } + + +} + + diff --git a/src/cpod/CPODUtils.java b/src/cpod/CPODUtils.java new file mode 100644 index 00000000..a89c5741 --- /dev/null +++ b/src/cpod/CPODUtils.java @@ -0,0 +1,152 @@ +package cpod; + +import java.io.File; + +import cpod.CPODClassification.CPODSpeciesType; + + +/** + * Some useful utility function for CPOD and FPOD data + */ +public class CPODUtils { + + /** + * CPOD file types + * @author Jamie Macaulay + * + */ + public enum CPODFileType { + CP1("CP1"), + CP3("CP3"), + FP1("FP1"), + FP3("FP3"); + + private String text; + + CPODFileType(String text) { + this.text = text; + } + + public String getText() { + return this.text; + } + + public static CPODFileType fromString(String text) { + for (CPODFileType b : CPODFileType.values()) { + if (b.text.equalsIgnoreCase(text)) { + return b; + } + } + return null; + } + } + + /** + * Java will only have read signed bytes. Nick clearly + * uses a lot of unsigned data, so convert and inflate to int16. + * @param signedByte + * @return unsigned version as int16. + */ + public static short toUnsigned(byte signedByte) { +// short ans = signedByte; + + short ans = (short) (signedByte & 0xff); + +// if (ans < 0) { +// ans += 256; +// } + + return ans; + } + + /** + * Convert POD time to JAVA millis - POD time is + * integer minutes past the same epoc as Windows uses + * i.e. 0th January 1900. + * @param podTime + * @return milliseconds. + */ + public static long podTimeToMillis(long podTime) { + return podTime * 60L * 1000L - (25569L*3600L*24000L); + } + + public static CPODFileType getFileType(File cpFile) { + for (int i=0; i>firstBit); + } + + /** + * Get the FPOD species from an int flag + * @param species - integer flag representing the species + * @return the ENUM species type. + */ + public static CPODSpeciesType getFPODSpecies(short species) { + CPODSpeciesType type= CPODSpeciesType.UNKNOWN; + switch (species) { + case 0: + type = CPODSpeciesType.NBHF; + break; + case 1: + type = CPODSpeciesType.DOLPHIN; + break; + case 2: + type = CPODSpeciesType.UNKNOWN; + break; + case 4: + type = CPODSpeciesType.SONAR; + break; + } + return type; + } + + +} diff --git a/src/cpod/FPODReader.java b/src/cpod/FPODReader.java new file mode 100644 index 00000000..09a4d01a --- /dev/null +++ b/src/cpod/FPODReader.java @@ -0,0 +1,1432 @@ +package cpod; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import PamUtils.PamArrayUtils; +import PamUtils.PamCalendar; +import cpod.FPODReader.FPODdata; + + +/** + * Functions for importing FPOD files. + *

+ * Note this class should be independent of any PAMGuard functionality. + *

+ * Although some anming has changed a lot of the variable names are consistent with Pascal code + * used in FPOD.exe software from whihc this is absed. + * + * @author Jamie Macaulay + */ +public class FPODReader { + + /** + * Look up array to convert byte values to linear sound level + */ + private static int[] LinearPkValsArr; + + /** + * Look up array to convert byte values to linear sound level if using extended amps. + */ + private static int[][] ClippedPkArr; + + + /** + * Look up sine array fro reconstructing waveforms. + */ + private static double[] SineArr; + + /** + * Look up sine array for converting IPI (inter-pulse-interval) to kHz values + */ + private static int[] IPItoKHZ = new int[257]; + + /** + * Length of the FPOD header in bytes. + */ + public static final int FPOD_HEADER = 1024; + + + /** + * The length of a standard FPOD entry + */ + public static final int FP1_FPOD_DATA_LEN = 16; + + + /** + * The click length for FP3 files. + */ + public static final int FP3_FPOD_DATA_LEN = 32; + + /** + * Scale factor to convert waveform measurements to PAMGuard -1 to 1 measurements. + */ + public static final double WAV_SCALE_FACTOR = 255.; + + + public static final float FPOD_WAV_SAMPLERATE = 2000000; + + public static FPODHeader readHeader(File cpFile) { + + BufferedInputStream bis = null; + int bytesRead; + FileInputStream fileInputStream = null; + int totalBytes = 0; + try { + bis = new BufferedInputStream(fileInputStream = new FileInputStream(cpFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + FPODHeader header = new FPODHeader(); + try { + if (readHeader(bis, header) != FPOD_HEADER) { + return null; + } + bis.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return header; + + } + + /** + * Import FPOD data and return a structure of CPOD clicks. + * @param cpFile - the FP1 file. + * @param from - the click index to save from. e.g. 100 means that only click 100 + in the file is saved + * @param maxNum - the maximum number of data units to import. + * @return an array of CPOD clicks. + * @throws IOException + */ + public static ArrayList importFPODFile(File cpFile, int from, int maxNum ) throws IOException { + if (cpFile==null) return null; + ArrayList fpodData = new ArrayList(); + FPODReader.importFile(cpFile, fpodData, from, maxNum); + + ArrayList cpodClickData = new ArrayList(); + + CPODClick cpodClick; + for (int i=0; i fpodData, int from, int maxNum ) throws IOException { + + populateRawToRealPkArrays(); + populateIPIArray(); + + BufferedInputStream bis = null; + int bytesRead; + FileInputStream fileInputStream = null; + int totalBytes = 0; + try { + bis = new BufferedInputStream(fileInputStream = new FileInputStream(cpFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return 0; + } + FPODHeader header = new FPODHeader(); + if (readHeader(bis, header) != FPOD_HEADER) { + return 0; + } + + int fileEnds = 0; + boolean isClick; + FPODdata fpodClick; + // first record is always a minute mark, so start + // at -1 to avoid being skipped forward one minute. + int nClicks = 0, nMinutes = -1; + int nWavClicks = 0; + + //keep track of wav data + FPODWavData wavData = null; + + //holds a map of the click train detections. + HashMap clickTrains = new HashMap(); + + //the click train of the current data unit. + CPODClassification cpodClassification = null; + short lastSpecies = 0; + int wavbufpos = 0; + try { + while (true) { + + fpodClick = new FPODdata(); + + byte[] bufPosN = new byte[FP1_FPOD_DATA_LEN]; + + int byteRead = bis.read(bufPosN, 0, FP1_FPOD_DATA_LEN); + if (byteRead!=FP1_FPOD_DATA_LEN) { + System.out.println("Total len: " + totalBytes); + break; + } + + // ftFP3 is FP3 file in Padcal? + // ftFP2 is FP2 file in Padcal? + // ftFP1 is FP1 file + + totalBytes+=byteRead; + + isClick = toUnsigned(bufPosN[0]) < 184; + + if (isClick) { + // time within minute in 5 microsecond units + fpodClick.FiveMusec = ((bufPosN[0] & 0xFF) << 16) | // Shift first byte 16 bits to the left + ((bufPosN[1] & 0xFF) << 8) | // Shift second byte 8 bits to the left + (bufPosN[2] & 0xFF); // Leave third byte as is + + //N of cycles in the click - up to 255cycles + fpodClick.Ncyc = toUnsigned(bufPosN[3]); //number of cycles + + + //Wavenumber of loudest cycle; range of IPIs in click + fpodClick.PkAt = (bufPosN[4] & 0xF0) >> 4; + + // decompress the click IPI values + if ((bufPosN[4] & 0xF) == 15) { + fpodClick.ClkIPIrange = 65; // Since > 64 isn't directly assignable, assign 65 instead. + } else if ((bufPosN[4] & 0x8) == 8) { + fpodClick.ClkIPIrange = (((bufPosN[4] & 0x7) + 1) << 3); + } else { + fpodClick.ClkIPIrange = (bufPosN[4] & 0x7); + } + + // IPI of Pk-1 + fpodClick.IPIpreMax = toUnsigned(bufPosN[5]) + 1; // Increment byte at BufPosn[5] and assign to + // NrClk.IPIpreMax + + // IPI of Pk this is the loudest cycle in the click + fpodClick.IPIatMax = toUnsigned(bufPosN[6]) + 1; // Increment byte at BufPosn[6] and assign to + // NrClk.IPIatMax + + int localIPIatMax; + if (header.FPGAcodeVersion > 801) { + localIPIatMax = fpodClick.IPIatMax; + } else { + localIPIatMax = fpodClick.IPIpreMax; + } + + if (bufPosN[7] > 0) { + // IPI of Pk+1 + fpodClick.IPIplus1 = toUnsigned(bufPosN[7]) + 1; + if (bufPosN[8] > 0) { + // IPI of Pk+1 + fpodClick.IPIplus2 = toUnsigned(bufPosN[8]) + 1; + } + } + + // now on to amplitudes + if (bufPosN[9] > 0) { + // Amplitude of P-1 + fpodClick.RawPkminus1 = toUnsigned(bufPosN[9]); + + fpodClick.Pkminus1Extnd = RawToRealPk(toUnsigned(bufPosN[9]), localIPIatMax, + header.HasExtendedAmps); + } + + fpodClick.MaxPkRaw = (short) Math.max(2, toUnsigned(bufPosN[10])); + // fpodClick.MaxPkLinear = RawToLinearPk(toUnsigned(bufPosN[10])); + fpodClick.MaxPkExtnd = RawToRealPk(toUnsigned(bufPosN[10]), localIPIatMax, + header.HasExtendedAmps); + + if (toUnsigned(bufPosN[11]) > 0) { + fpodClick.RawPkplus1 = toUnsigned(bufPosN[11]); + fpodClick.Pkplus1Extnd = RawToRealPk(toUnsigned(bufPosN[11]), localIPIatMax, + header.HasExtendedAmps); + } + + fpodClick.IPIbefore = toUnsigned(bufPosN[12]) + 1; + fpodClick.AmpReversals = bufPosN[13] & 15; + + // Defer MSnibble[13][14] until [15] has been read + if (header.FileType == 2) { + // fpodClick.Clstr.cNall = toUnsigned(bufPosN[15]); //not implemented in PAMGuard + } else if (toUnsigned(bufPosN[15]) < 2) { // POD marks where a sonar has been found + fpodClick.EndIPI = fpodClick.IPIpreMax; + } else { + //fpodClick.HasWave = (bufPosN[15] & 1) == 1; + fpodClick.EndIPI = bufPosN[15] & 254 + 1; // + 1 on all IPIs because counts from the POD start at + } + + //the duration is in 5us units. + fpodClick.duration = ((bufPosN[13] & 240)*16 + toUnsigned(bufPosN[14])); + + ///rm...can't exactly explain this but it's translated from FPOD Pascal code - calculates bandwidth + int ampDfSum = Math.abs(fpodClick.Pkminus1Extnd - fpodClick.MaxPkRaw); + int ampSum = Math.round((fpodClick.Pkminus1Extnd + fpodClick.MaxPkRaw) / 2); + int ipIdfSum = Math.abs(fpodClick.IPIpreMax - fpodClick.IPIatMax); + int ipISum = fpodClick.IPIatMax; + + if (fpodClick.Pkplus1Extnd > 0) { + ampDfSum += Math.abs(fpodClick.Pkplus1Extnd - fpodClick.MaxPkRaw); + ampSum += Math.round((fpodClick.Pkplus1Extnd + fpodClick.MaxPkRaw) / 2); + ipIdfSum += Math.abs(fpodClick.IPIatMax - fpodClick.Pkplus1Extnd); + ipISum += fpodClick.Pkplus1Extnd; + } + + //set the nadwidth + fpodClick.BW = Math.max(0, Math.min(11, Math.round(ipIdfSum << 4 / ipISum)) + Math.min(20, Math.round((ampDfSum + (ampDfSum >> 1)) << 3 / ampSum) - 1)); + + + // if (fpodClick.HasWave) { + // System.err.println("Pod data: " + nClicks + " " + fpodClick.FiveMusec + " hasWav: " + fpodClick.HasWave); + // } + + //set the time in millis. + + long timeMillis = (long) (CPODUtils.podTimeToMillis(header.FirstLoggedMin) + (nMinutes*60*1000.) + fpodClick.FiveMusec/200); + fpodClick.setTimeMillis(timeMillis); + fpodClick.HasWave = false; + + + /** + * + * Set the raw (sort of - actually peak positions and IPI) waveform data + * + * Note that the wav data is recorded before the click - this is why this + * section of code is here. A non null wavData object indicates that wav data has been processed + * and it must belong to this click. We then set wavData to null indicated there is no wav data + * until the wavData is again present. + */ + if (wavData!=null) { + + fpodClick.HasWave = true; + // fpodClick.setNWavRecs(0); + wavData.setClickCyclesStartAt(21 - fpodClick.Ncyc); + + for (int count = wavData.getWavPtr(); count >= 1; count--) { + wavData.setWavValsIPI((short) 255, count); + wavData.setWavValsSPL((short) 0, count); + } + + fpodClick.setWavData(wavData); + // int[] wave = makeResampledWaveform(fpodClick); + + //Test clicks + // if (fpodClick.getTimeMillis()==1689706702252L) {//porpoise click + // if (fpodClick.getTimeMillis()== 1689615091621L) { + // int[] wave = makeResampledWaveform(fpodClick); + // System.out.println("WAV DATA " + nClicks + " " + fpodClick.getWavData().getNWavRecs() + " " + wavData.getWavPtr()); + // + // System.out.println(" Peaks: "); + // for (int i = 0; i< wavData.getWavValsSPL().length; i++) { + // System.out.print(" " + wavData.getWavValsSPL()[i]); + // } + // + // System.out.println(); + // + // System.out.println(" Waveform: "); + // for (int i = 0; i< wave.length; i++) { + // System.out.print(" " + wave[i]); + // } + // return 0; + // } + + // fpodClick.getWavData(); + + + //does this set the reference to null - NOPE; + wavData = null; + + } + + /** + * Set the classification (if FP3 file) + */ + if (clickTrains!=null) { + fpodClick.setClassification(cpodClassification); + + //TEST +// long timeMillis2 = PamCalendar.msFromDate(2023, 8, 31, 2, 52, 46, 200); //NBHF +// //long timeMillis2 = PamCalendar.msFromDate(2023, 7, 17, 16, 33, 2, 200); //DOLPH +// if (fpodClick.getTimeMillis()>timeMillis2 && fpodClick.getTimeMillis()<(timeMillis2+5000)) { +// System.out.println("Click train ID: " + cpodClassification.clicktrainID + " minutes: " + nMinutes + " species: " + cpodClassification.species + " species code: " + lastSpecies + " quality level: " + cpodClassification.qualitylevel); +// } + + } + + fpodClick.setMinute(nMinutes); + + if (from<0 || (nClicks>from && nClicks<(from+maxNum))) { + //add the click to the FPODdata. + fpodData.add(fpodClick); + } + + + if (nClicks%400000==0) { + System.out.println("Pod data: " + nClicks + " " + PamCalendar.formatDateTime(fpodClick.getTimeMillis()) + " " +fpodClick.dataString() + " " +toUnsigned(bufPosN[13]) + " " + toUnsigned(bufPosN[14])); + } + + nClicks++; + + + } + else if (toUnsigned(bufPosN[0])==250) { + + //wav data preceedes the next click + + if (wavData==null) { + //starting a new wav record + wavData = new FPODWavData(); + wavData.setNWavRecs(0); + wavData.setWavPtr(21); + wavbufpos = 0; + nWavClicks++; + } + + //the block has already been read... + // if (Fs[FN].isInPlay() && bufpos >= Fs[FN].getNumBytesRead()) { + // readNextBlock(); // Assumes you have a method for reading the next block + // } + if (toUnsigned(bufPosN[wavbufpos]) == 250) { //a little redundant but keep in just incase + wavData.setNWavRecs(wavData.getNWavRecs() + 1); + if (wavData.getNWavRecs() < 4) { + int posn = wavData.getWavPtr(); + for (int count = 0; count <= 6; count++) { + wavData.setWavValsIPI(toUnsigned((bufPosN[wavbufpos + (count << 1) + 1])), posn); + wavData.setWavValsSPL(toUnsigned((bufPosN[wavbufpos + (count << 1) + 2])), posn); + posn--; + } + wavData.setWavPtr(wavData.getWavPtr() - 7); + } + } + + // JamArr.printArray(fpodClick.getWavValsSPL()); + //wav data + nWavClicks++; + } + + else if(toUnsigned(bufPosN[0])==249) { + //click train data - this is not included - for now + //click train data precedes the next click + + //the train ID is unique to the minute, + short trainID = toUnsigned(bufPosN[15]); + + //0 is NBHF + //1 is dolphin + //2 spUnclassiifed? + //4 sonar? + short species = (short) ((bufPosN[14] >>> 2) & 3); + lastSpecies = species; + + //quality level for the click train + short qualitylevel = (short) ((bufPosN[14]) & 3); + + boolean echo = false; + if ((bufPosN[14] & 32) == 32) { + echo = true; + } + + + //generate a unique train ID within the file + int trainUID = Integer.valueOf(String.format("%06d", nMinutes) + String.format("%d", trainID)); + + //find the click train from the hash map - if it is not there, create a new one. + cpodClassification = clickTrains.get(trainUID); + + if (cpodClassification==null) { + cpodClassification = new CPODClassification(); + cpodClassification.isEcho = echo; + cpodClassification.clicktrainID = trainUID; + cpodClassification.species = CPODUtils.getFPODSpecies(species); //add onme to make same format as CPOD...? + cpodClassification.qualitylevel = qualitylevel; + + clickTrains.put(trainUID, cpodClassification); + +// System.out.println(PamCalendar.formatDateTime2(fpodClick.getTimeMillis()) + "Click train ID: " + trainUID + " minutes: " + nMinutes + " species: " + species + " quality level: " + qualitylevel); + + } + + + } + else if(toUnsigned(bufPosN[0])==254){ + nMinutes ++; + } + + } + + System.out.println("Number clks " + nClicks + " nWav: " + nWavClicks + " minutes: " + nMinutes); + bis.close(); + return totalBytes; + + } catch (IOException e) { + e.printStackTrace(); + return totalBytes; + } + } + + + /** + * Convert a raw binary peak to true linear peak. + * @param Pk - the raw maximum peak + * @param IPI - the inter pulse interval + * @param UseExtendedAmps - true to use extended amps + * @return the real linear peak of he wave + */ + public static int RawToRealPk(int Pk, int IPI, boolean UseExtendedAmps) { + if (Pk == 0) { + return 1; + } + if (IPI < 10) { // or (IPI > 256) + return LinearPkValsArr[Pk]; + } + return UseExtendedAmps && (Pk > 222) ? ClippedPkArr[Pk][IPI - 1] : LinearPkValsArr[Pk]; + } + + + /** + * Populate the arrays used for calculatung true peaks + */ + private static void populateRawToRealPkArrays() { + final int constMaxAmpKHZScaler = 50; + final int constMinPkampAllowed = 384; // (224 - 128) shl 2; + + int count, IPI, val, MaxPkampAllowed, Pk; + boolean MaxExceeded; + int[] RiseTimeConversionArr = new int[256 - 223 + 1]; // Initialized for indices 223 to 255 + + LinearPkValsArr = new int[256]; + + // Populate LinearPkValsArr + for (count = 0; count <= 127; count++) { + LinearPkValsArr[count] = count; + } + for (count = 128; count <= 191; count++) { + LinearPkValsArr[count] = (count - 64) << 1; + } + for (count = 192; count <= 255; count++) { + LinearPkValsArr[count] = (count - 128) << 2; + } + + + + // Populate RiseTimeConversionArr + for (Pk = 223; Pk <= 255; Pk++) { + if (Pk < 232) { + RiseTimeConversionArr[Pk - 223] = 32 + (231 - Pk) * 4; + } else if (Pk < 240) { + RiseTimeConversionArr[Pk - 223] = 16 + (239 - Pk) * 2; + } else { + RiseTimeConversionArr[Pk - 223] = 255 - Pk; + } + } + + ClippedPkArr = new int[256][256]; + + // Populate ClippedPkArr + for (IPI = 10; IPI <= 256; IPI++) { + MaxExceeded = false; + MaxPkampAllowed = IPI * constMaxAmpKHZScaler; + for (Pk = 223; Pk <= 255; Pk++) { + if (!MaxExceeded) { + val = (int) Math.round(Math.pow(4000.0 / IPI, -0.75) * Math.pow(10, 5.24 * Math.pow(RiseTimeConversionArr[Pk - 223], -0.11))); + } else { + val = MaxPkampAllowed; + } + if (val > MaxPkampAllowed) { + val = MaxPkampAllowed; + MaxExceeded = true; + } else { + val = Math.max(val, constMinPkampAllowed); + } + ClippedPkArr[Pk][IPI - 1] = Math.min(IPI * constMaxAmpKHZScaler, Math.max(384, val)); + } + } + } + + + /** + * Convert IPI to KHz. + * @param IPI - the IPI + * @return the kHz value + */ + public static int IPItoKhz(int IPI) { + return IPItoKHZ[IPI]; + } + + /** + * Ppulate the IPI array + */ + public static void populateIPIArray() { + + for (int count = 0; count < 16; count++) { + IPItoKHZ[count] = 255; + } + + for (int count = 16; count < 256; count++) { + IPItoKHZ[count] = Math.round(4000 / count); + } + + IPItoKHZ[64] = 63; // Smoothes an uncomfortable step here + IPItoKHZ[256] = 1; // An 'indicative' value + } + + + /** + * Read the FPOD header + * @param bis + * @return the number of bytes read. + * @throws IOException + */ + private static int readHeader(BufferedInputStream bis, FPODHeader PODset) throws IOException { + + byte[] HdrBuf = new byte[FPOD_HEADER]; + bis.read(HdrBuf, 0, FPOD_HEADER); + + // INITS - GET F METADATA gfmd + PODset.gain = HdrBuf[1]; + // ... (Previously translated code for PODID and PodNstr5char) + + // Assuming MinToDDMMYrstr is a function converting minutes to a DDMMYYYY string + PODset.DateOfCalibrationStr = MinToDDMMYrstr((HdrBuf[5] << 8 | HdrBuf[6]) * 1440); + + int mainBoardNumber = HdrBuf[7] * 10000 + HdrBuf[8] * 100 + HdrBuf[9]; + PODset.MainBoardNumber = mainBoardNumber; + + PODset.PreAmpVersion = toUnsigned(HdrBuf[10]); + PODset.HydrophoneVersion = toUnsigned(HdrBuf[11]); + PODset.FPGAtype = toUnsigned(HdrBuf[12]); + PODset.FromWavFileData = toUnsigned(HdrBuf[13]) != 0; // Convert boolean value + PODset.PaDataVersion = 1; // xx revise this + PODset.PICtype = toUnsigned(HdrBuf[14]); + PODset.HousingType = toUnsigned(HdrBuf[16]); + PODset.ReleaseTone1Min= toUnsigned(HdrBuf[17]); + PODset.ReleaseTone1Max= toUnsigned(HdrBuf[18]); + PODset.ReleaseTone2Min= toUnsigned(HdrBuf[19]); + PODset.ReleaseTone2Max= toUnsigned(HdrBuf[20]); + PODset.ReleaseTone3Min= toUnsigned(HdrBuf[21]); + PODset.ReleaseTone3Max= toUnsigned(HdrBuf[22]); + PODset.ReleaseTone4Min= toUnsigned(HdrBuf[23]); + PODset.ReleaseTone4Max= toUnsigned(HdrBuf[24]); + PODset.ReleaseTone5Min= toUnsigned(HdrBuf[25]); + PODset.ReleaseTone5Max= toUnsigned(HdrBuf[26]); + PODset.AcRelNcyc= toUnsigned(HdrBuf[27]); + PODset.AcRelSlotLength= toUnsigned(HdrBuf[28]); + PODset.AcRelSlotSpacing= toUnsigned(HdrBuf[29]); + PODset.RelaysSecs= toUnsigned(HdrBuf[30]); // xxcheck where this is + //unused + // these were zero + PODset.PICcodeMajorVersion= toUnsigned(HdrBuf[37]); + + if (PODset.PICcodeMajorVersion > 14) { + PODset.SteeperStepping = true; + } + + PODset.PICcodeVersionStr = String.format("%d.%d", toUnsigned(HdrBuf[37]), toUnsigned(HdrBuf[38])); + PODset.FPGAcodeVersion = (HdrBuf[39] << 8) | HdrBuf[40]; + PODset.FPGAcodeVersionStr =String.format("%d.%d", toUnsigned(HdrBuf[39]), toUnsigned(HdrBuf[40])); + + // // Assuming F.CurrentActivityLbl is a label component + // F.CurrentActivityLbl.setText("PIC code v" + PODset.PICcodeVersionStr + + // " FPGA code v" + PODset.FPGAcodeVersionStr); + + if (PODset.FromWavFileData) { + PODset.PODID = (HdrBuf[2] >>> 24) | (HdrBuf[3] >>> 16) | (HdrBuf[4] >>> 8) | HdrBuf[5]; + } + + if (PODset.FPGAcodeVersion > 0) { + PODset.HasExtendedAmps = true; + } + + // SETS: + PODset.UTCoffset = toUnsigned(HdrBuf[64]); + + switch (HdrBuf[65]) { + case 0: + PODset.FilterKHZ = 120; + break; + case 1: + PODset.FilterKHZ = 80; + break; + case 2: + PODset.FilterKHZ = 40; + break; + case 3: + PODset.FilterKHZ = 20; + break; + } + + switch (HdrBuf[66]) { + case 0: + PODset.FileMinutecountLimit = 2048; + break; + case 1: + PODset.FileMinutecountLimit = 8192; + break; + case 2: + PODset.FileMinutecountLimit = 4096; + break; + case 3: + PODset.FileMinutecountLimit = 16384; + break; + case 16: + PODset.FileMinutecountLimit = 4096; + break; + case 17: + PODset.FileMinutecountLimit = 8192; + break; + case 18: + PODset.FileMinutecountLimit = 16384; + break; + case 19: + PODset.FileMinutecountLimit = 32768; + break; + case 32: + PODset.FileMinutecountLimit = 8192; + break; + + case 33: + PODset.FileMinutecountLimit = 16384; + break; + case 34: + PODset.FileMinutecountLimit = 32768; + break; + case 35: + PODset.FileMinutecountLimit = 65536; + break; + default: + // Handle error or unexpected value + System.out.println("FileMinutecountLimit error in reading header of file " + ", code = " + HdrBuf[66]); + PODset.FileMinutecountLimit = 65536; // Set a default value + } + + + PODset.MinimumCycleAmplitude = toUnsigned(HdrBuf[67]); + PODset.MinIPI = toUnsigned(HdrBuf[68]); + PODset.MaxIPI = toUnsigned(HdrBuf[69]); + PODset.MinNofCyc = HdrBuf[70] & 15; + PODset.PeakDetectionMode = (HdrBuf[70] >> 6); + PODset.MaxSPLforReversalCount = toUnsigned(HdrBuf[71]); // ww check these + PODset.BWaddon = toUnsigned(HdrBuf[72]); + + PODset.StrongLimit1 = toUnsigned( HdrBuf[73]); // ? rescale these to true values, as currently on non-linear scale ww + PODset.StrongLimit2 = toUnsigned(HdrBuf[74]); + PODset.StrongLimit3 = toUnsigned(HdrBuf[75]); + PODset.StrongLimit4 = toUnsigned(HdrBuf[76]); + PODset.UsingQtrAmpDrop = (HdrBuf[77] & 1) == 1; + // PODset.MaxSPLforBWtest = HdrBuf[77]; add code re versions before 14 that used this.... ww + PODset.SonarLongNcyc = toUnsigned(HdrBuf[78]); + PODset.SonarFiltering = toUnsigned(HdrBuf[78]) > 0; + PODset.MaxSPLforAmpDropTest = toUnsigned(HdrBuf[79]); + // unused HdrBuf[80]; + PODset.ThermalGainControl = toUnsigned(HdrBuf[81]) == 1; + PODset.BatterySwitchLevel = toUnsigned(HdrBuf[82]); + + PODset.WavMode = tWavMode(HdrBuf[83]); + PODset.WavSNR = toUnsigned(HdrBuf[84]); + PODset.WavPkAddOn = toUnsigned(HdrBuf[85]); + PODset.WavMinPk = toUnsigned(HdrBuf[86]); + PODset.WavMinLimit = (HdrBuf[87] << 3); + PODset.WavTotalLimit = (HdrBuf[88] >> 2); + PODset.WavMinICI = toUnsigned(HdrBuf[89]); + PODset.WavMaxICI = toUnsigned(HdrBuf[90]); + PODset.WavOKseqN = toUnsigned(HdrBuf[91]); + PODset.WavMaxRawPerTrain = toUnsigned(HdrBuf[92]); + PODset.NoisyLevel = ( HdrBuf[93] << 8); + PODset.QuietLevel = ( HdrBuf[94] << 8); + PODset.NoisyMinsLimit = toUnsigned(HdrBuf[95]); + PODset.QuietMinsLimit = toUnsigned(HdrBuf[96]); + + PODset.SwitchNmin = toUnsigned( HdrBuf[98]); + PODset.SwitchNmax = toUnsigned(HdrBuf[100]); + + + if (HdrBuf[101] < 10) { + PODset.ReleaseAfterStr = "200" + Integer.toString(HdrBuf[101]); + } else { + PODset.ReleaseAfterStr = "20" + Integer.toString(HdrBuf[101]); + } + PODset.ReleaseAfterStr += " m" + Integer.toString(HdrBuf[102]) + " d" + Integer.toString(HdrBuf[103]) + " h" + Integer.toString(HdrBuf[104]); + + if (HdrBuf[105] < 10) { + PODset.ReleaseOnStr = "200" + Integer.toString(HdrBuf[105]); + } else { + PODset.ReleaseOnStr = "20" + Integer.toString(HdrBuf[105]); + } + PODset.ReleaseOnStr += " m" + Integer.toString(HdrBuf[106]) + " d" + Integer.toString(HdrBuf[107]) + " h" + Integer.toString(HdrBuf[108]); + + if (HdrBuf[109] < 10) { + PODset.StartOnStr = "200" + Integer.toString(HdrBuf[109]); + } else { + PODset.StartOnStr = "20" + Integer.toString(HdrBuf[109]); + } + PODset.StartOnStr += " m" + Integer.toString(HdrBuf[110]) + " d" + Integer.toString(HdrBuf[111]) + "@00:00"; + + + switch (HdrBuf[112]) { + case 0: + PODset.MinsOFFbetweenON = 0; + break; + case (byte) 128: + PODset.MinsOFFbetweenON = 1; + break; + case (byte) 131: + PODset.MinsOFFbetweenON = 4; + break; + case (byte) 136: + PODset.MinsOFFbetweenON = 9; + break; + default: + PODset.MinsOFFbetweenON = 0; + } + + // ... (more translations) + + PODset.DeploymentDepth = (HdrBuf[129] << 8) + HdrBuf[130]; // pOpenF + PODset.WaterDepth = (HdrBuf[131] << 8) + HdrBuf[132]; + + + // Lat Long and Location are in the same position in all file structures + String tempS = ""; + for (int posn = 133; posn <= 144; posn++) { + if (Character.isLetterOrDigit(HdrBuf[posn])) { + tempS += (char) HdrBuf[posn]; + } + } + PODset.LatText = tempS; + // PODset.LatText = ParseLatLong(tempS, true).Value; // Uncomment if needed + + tempS = ""; + for (int posn = 145; posn <= 156; posn++) { + if (Character.isLetterOrDigit(HdrBuf[posn])) { + tempS += (char) HdrBuf[posn]; + } + } + PODset.LongText = tempS; + // PODset.LongText = ParseLatLong(tempS, false).Value; // Uncomment if needed + + // Extract LocationText, NotesText, and GMTText + for (int posn = 157; posn <= 187; posn++) { + if (Character.isLetterOrDigit(HdrBuf[posn])) { + tempS += (char) HdrBuf[posn]; + } + } + PODset.LocationText = tempS; + + tempS = ""; + for (int posn = 188; posn <= 231; posn++) { + if (Character.isLetterOrDigit(HdrBuf[posn])) { + tempS += (char) HdrBuf[posn]; + } + } + PODset.NotesText = tempS; + + tempS = ""; + for (int posn = 232; posn <= 233; posn++) { + if (Character.isLetterOrDigit(HdrBuf[posn])) { + tempS += (char) HdrBuf[posn]; + } + } + PODset.GMTText = tempS; + + // Combine bytes for NclxInFP1file + PODset.NclxInFP1file = (HdrBuf[235] << 24) | (HdrBuf[236] << 16) | (HdrBuf[237] << 8) | HdrBuf[238]; + + // Create TDAversionStr + PODset.TDAversionStr = Integer.toString(HdrBuf[239]); + if (HdrBuf[240] > 0) { + PODset.TDAversionStr += "." + Integer.toString(HdrBuf[240]); + } + + // // Conditionally create ShortNameV + // if (PODset.FileType == ftFP3) { + // PODset.ShortNameV = PODset/ShortName + "(v" + TDAversionStr + ")"; + // } else { + // PODset. ShortNameV = PODset.ShortName; + // } + + + // Info for warnings + PODset.AllSpHiModRatio = toUnsigned(HdrBuf[241]); + PODset.DolHiModTrnCount = toUnsigned(Expanded(HdrBuf[242])); // Assuming Expanded is defined + PODset.NBHFHiModTrnCount = toUnsigned(Expanded(HdrBuf[243])); + PODset.SonarHiModTrnCount = toUnsigned(Expanded(HdrBuf[244])); + PODset.NBHFmode = toUnsigned(HdrBuf[245]); + PODset.NBHF10to5KHZbelow = toUnsigned(HdrBuf[246]); + PODset.NBHF5to10KHZabove = toUnsigned(HdrBuf[247]); + PODset.NBHFtargetMode = toUnsigned(HdrBuf[248]); + PODset.NBHFdownsweeps = toUnsigned(HdrBuf[249]); + // PODset.DolBadFrac = Expanded(HdrBuf[250]); + // PODset.NBHFBadFrac = Expanded(HdrBuf[251]); + // PODset.SonarBadFrac = Expanded(HdrBuf[252]); + PODset.nLandmarks = toUnsigned(HdrBuf[253]); + + // These are same positions as CPOD files + PODset.FirstLoggedMin = Math.max(Int32FromBytes(HdrBuf, 256, 4), 1); // MoveFilePosToMin exits if it gets a zero + PODset.LastLoggedMin = Math.max(Int32FromBytes(HdrBuf, 260, 4), PODset.FirstLoggedMin); + + + if (PODset.LastLoggedMin == 0 || PODset.LastLoggedMin < PODset.FirstLoggedMin) { + // ShowTimedMessage(5000, 'PODset.LastLoggedMin = 0) or (PODset.LastLoggedMin < PODset.FirstLoggedMin'); // Adjust for Java + } + + PODset.GoToMin = Math.min(Int32FromBytes(HdrBuf, 264, 4), PODset.LastLoggedMin); + PODset.GoToFiveMusec = Int32FromBytes(HdrBuf, 268, 4); + PODset.StoredFirstProcessMin = Math.max(Int32FromBytes(HdrBuf, 272, 4), PODset.FirstLoggedMin); + if (PODset.StoredFirstProcessMin < PODset.FirstLoggedMin || PODset.StoredFirstProcessMin > PODset.LastLoggedMin) { + PODset.StoredFirstProcessMin = PODset.FirstLoggedMin; + } + PODset.StoredLastProcessMin = Math.min(Int32FromBytes(HdrBuf, 276, 4), PODset.LastLoggedMin); + if (PODset.StoredLastProcessMin < PODset.FirstLoggedMin || PODset.StoredLastProcessMin > PODset.LastLoggedMin) { + PODset.StoredLastProcessMin = PODset.LastLoggedMin; + } + PODset.nLoggingMins = (HdrBuf[280] << 24) | (HdrBuf[281] << 16) | (HdrBuf[282] << 8) | HdrBuf[283]; + + return FPOD_HEADER; + } + + /** + * Convert 4 bytes to an Java integer + * @param hdrBuf - a byte array of any length + * @param i - the index to start from + * @param j - the number of bytes to convert to an integer + * @return the integer value + */ + private static int Int32FromBytes(byte[] hdrBuf, int start, int nbytes) { + int ret = 0; + for (int i=start; i + * Note that is pretty opaque what all this means. The important parameters have been commented. + */ + public static class FPODHeader { + + public int FileType; + public int nLoggingMins; + public int StoredLastProcessMin; + public int StoredFirstProcessMin; + public int GoToFiveMusec; + public int GoToMin; + public int LastLoggedMin; + public int FirstLoggedMin; + public short nLandmarks; + public short DolHiModTrnCount; + public short SonarHiModTrnCount; + public short NBHFmode; + public short NBHF10to5KHZbelow; + public short NBHF5to10KHZabove; + public short NBHFdownsweeps; + public short NBHFtargetMode; + public short NBHFHiModTrnCount; + public short AllSpHiModRatio; + public String GMTText; + public String NotesText; + public String LocationText; + public Object ShortNameV; + public String TDAversionStr; + public int NclxInFP1file; + public String LongText; + public String LatText; + public String StartOnStr; + public String ReleaseOnStr; + public int WaterDepth; + public int DeploymentDepth; + public int MinsOFFbetweenON; + public String ReleaseAfterStr; + public short SwitchNmax; + public short SwitchNmin; + public short QuietMinsLimit; + public short NoisyMinsLimit; + public int QuietLevel; + public int NoisyLevel; + public short WavMaxICI; + public short WavOKseqN; + public short WavMaxRawPerTrain; + public short WavMinICI; + public int WavTotalLimit; + public boolean SonarFiltering; + public short WavMinPk; + public int WavMinLimit; + public short WavSNR; + public short WavPkAddOn; + public short WavMode; + public short BatterySwitchLevel; + public boolean ThermalGainControl; + public short MaxSPLforAmpDropTest; + public short SonarLongNcyc; + public boolean UsingQtrAmpDrop; + public short StrongLimit3; + public short StrongLimit4; + public short StrongLimit2; + public short StrongLimit1; + public short BWaddon; + public short MaxSPLforReversalCount; + public int PeakDetectionMode; + public int MinNofCyc; + public short MaxIPI; + public short MinIPI; + public short MinimumCycleAmplitude; + public int FileMinutecountLimit; + public short UTCoffset; + public boolean HasExtendedAmps; + public int FilterKHZ; + public boolean SteeperStepping; + public int FPGAcodeVersion; + public String PICcodeVersionStr; + public String FPGAcodeVersionStr; + public int PODID; + public short PICcodeMajorVersion; + public short RelaysSecs; + public short AcRelSlotLength; + public short AcRelSlotSpacing; + public short AcRelNcyc; + public short ReleaseTone5Min; + public short ReleaseTone5Max; + public short ReleaseTone4Max; + public short ReleaseTone4Min; + public short ReleaseTone2Max; + public short ReleaseTone3Min; + public short ReleaseTone3Max; + public short ReleaseTone1Max; + public short HousingType; + public short ReleaseTone1Min; + public short ReleaseTone2Min; + public short PICtype; + public int PaDataVersion; + public boolean FromWavFileData; + public short FPGAtype; + public short HydrophoneVersion; + public int MainBoardNumber; + public short PreAmpVersion; + public short gain; + public String DateOfCalibrationStr; + + } + + + + public static void BuildSineArray() { + SineArr = new double[2001]; + final double constPiFrac = Math.PI / 1000; + double S; + for (int count = 0; count < 2000; count++) { + S = Math.sin(constPiFrac * count); + SineArr[count] = S; + } + } + + /** + * Scale wave data so it is returned as a double + * @param wavData - the wavdata + * @return the scaled wav data between -1 and 1; + */ + public static double[] scaleWavData(int[] wavData) { + double[] wavArr = new double[wavData.length]; + for (int i=0; i 0 && click.getWavData().getWavValsIPI()[RawStartPtr] < 255) { + click.getWavData().getWavValsSPL()[RawStartPtr] = (short) LinearPkValsArr[click.getWavData().getWavValsSPL()[RawStartPtr]]; + RawStartPtr--; + } + + RawStartPtr = Math.min(21, RawStartPtr + 1); + FirstClkCyc = 21 - click.Ncyc; + + // Construct each cycle in MhzSampledArr + SinePtr = 0; + int MHzArrPtr = 0; + int MaxSPLval = 0; + IPIsum = 0; + + cyc = 21; + + do { + // Populate MhzSampledArr + SinePtsPerUs = Math.round(4000 / click.getWavData().getWavValsIPI()[cyc]); + while (SinePtr <= 1999 && MHzArrPtr < MhzSampledArr.length) { + MhzSampledArr[MHzArrPtr] = (int) Math.round(SineArr[SinePtr] + * click.getWavData().getWavValsSPL()[cyc]); + + // System.out.println( MhzSampledArr[MHzArrPtr] +" index: " + MHzArrPtr + " SinePtr " + SinePtr + " cyc " + cyc); + MHzArrPtr++; + SinePtr += SinePtsPerUs; + } + + if (MHzArrPtr >= MhzSampledArr.length) { + //System.err.println("FPOD Waveform index greater than len? " + MhzSampledArr.length); + break; // Fix: extend this array if needed + } + + SinePtr -= 2000; + if (cyc == FirstClkCyc) { + int StartOfClickHighRes = MHzArrPtr; + } + IPIsum += click.getWavData().getWavValsIPI()[cyc]; + cyc--; + } while (cyc > click.wavData.WavPtr); + + // Bring line up to zero + if (MHzArrPtr < MhzSampledArr.length) { + MhzSampledArr[MHzArrPtr] = 0; + } + + + + int[] waveform = Arrays.copyOf(MhzSampledArr, MHzArrPtr); + //waveform is backwards so flip it. + return PamArrayUtils.flip(waveform); + } + + + /** + * Test the program + * @param args + */ + public static void main(String[] args) { + // String filePath = "/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/CPOD/FPOD_NunBank/0866 NunBankB 2023 06 27 FPOD_6480 file0.FP1"; + +// String filePath = "D:\\DropBox\\PAMGuard_dev\\CPOD\\FPOD_NunBank\\0866 NunBankB 2023 06 27 FPOD_6480 file0.FP1"; + String filePath = "D:\\DropBox\\PAMGuard_dev\\CPOD\\FPOD_NunBank\\0866 NunBankB 2023 06 27 FPOD_6480 file0.FP3"; + + File fpfile = new File(filePath); + + ArrayList fpodData = new ArrayList(); + + try { + importFile( fpfile, fpodData, 0, Integer.MAX_VALUE); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + } + + +} diff --git a/src/cpod/dataPlotFX/CPODDDDataInfo.java b/src/cpod/dataPlotFX/CPODDDDataInfo.java new file mode 100644 index 00000000..e287db6f --- /dev/null +++ b/src/cpod/dataPlotFX/CPODDDDataInfo.java @@ -0,0 +1,107 @@ +package cpod.dataPlotFX; + + + +import PamView.symbol.PamSymbolChooser; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import cpod.CPODClick; +import cpod.FPODReader; +import dataPlotsFX.TDManagedSymbolChooserFX; +import dataPlotsFX.TDSymbolChooserFX; +import dataPlotsFX.clickPlotFX.ClickSymbolChooserFX; +import dataPlotsFX.data.TDDataInfoFX; +import detectionPlotFX.data.DDDataInfo; +import detectionPlotFX.layout.DetectionPlotDisplay; +import detectionPlotFX.projector.DetectionPlotProjector; +import detectionPlotFX.rawDDPlot.RawSpectrumPlot; +import detectionPlotFX.rawDDPlot.RawWaveformPlot; +import javafx.geometry.Side; +import pamViewFX.fxNodes.PamSymbolFX; + +/** + * Data info for showing a CPOD waveform. + * + * @author Jamie Macaulay + * + */ +public class CPODDDDataInfo extends DDDataInfo { + + + public CPODDDDataInfo(PamDataBlock dataBlock, + DetectionPlotDisplay displayPlot) { + super(displayPlot, dataBlock); + + //add the various click plots + super.addDetectionPlot(new RawWaveformPlot(displayPlot)); + super.addDetectionPlot(new CPODSpectrumPlot(displayPlot)); + + super.setCurrentDetectionPlot(0); + } + + @Override + public float getHardSampleRate() { + return FPODReader.FPOD_WAV_SAMPLERATE; + } + + /** + * Plots CPOD waveform spectrum. K + * @author Jamie Macaulay + * + */ + class CPODSpectrumPlot extends RawSpectrumPlot { + + public CPODSpectrumPlot(DetectionPlotDisplay detectionPlotDisplay) { + super(detectionPlotDisplay); + // TODO Auto-generated constructor stub + } + + @Override + public void setupAxis(PamDataUnit data, double sR, DetectionPlotProjector plotProjector) { + super.setupAxis(data, sR, plotProjector); + + + CPODClick click = (CPODClick) data; + if (click.getWaveData()==null) { + return; + } + + double lenMS = (1000.*click.getWaveData()[0].length)/FPODReader.FPOD_WAV_SAMPLERATE; + //set the scroller minimum and maximum + plotProjector.setMinScrollLimit(0); + //need this othewriwse the multiple sample rates relaly screw things up. + plotProjector.setMaxScrollLimit(lenMS); + plotProjector.setEnableScrollBar(true); + + plotProjector.setAxisMinMax(0, 250, Side.BOTTOM); + } + + + @Override + public double getSampleRate(PamDataUnit currentDetection) { + return (double) FPODReader.FPOD_WAV_SAMPLERATE; + } + } + + + /** + * Plots CPOD waveform spectrum. + * @author Jamie Macaulay + * + */ + class CPODWaveformPlot extends RawSpectrumPlot { + + public CPODWaveformPlot(DetectionPlotDisplay detectionPlotDisplay) { + super(detectionPlotDisplay); + } + + @Override + public double getSampleRate(PamDataUnit currentDetection) { + return (double) FPODReader.FPOD_WAV_SAMPLERATE; + } + } + + + + +} diff --git a/src/cpod/dataPlotFX/CPODDPlotProvider.java b/src/cpod/dataPlotFX/CPODDPlotProvider.java new file mode 100644 index 00000000..7e6c1716 --- /dev/null +++ b/src/cpod/dataPlotFX/CPODDPlotProvider.java @@ -0,0 +1,26 @@ +package cpod.dataPlotFX; + + +import PamguardMVC.PamDataBlock; +import cpod.CPODClick; +import cpod.CPODControl2; +import detectionPlotFX.data.DDDataProvider; +import detectionPlotFX.layout.DetectionPlotDisplay; + +public class CPODDPlotProvider extends DDDataProvider { + + + private PamDataBlock cpodDataBlock; + + public CPODDPlotProvider(CPODControl2 dlControl, PamDataBlock parentDataBlock) { + super(parentDataBlock); + this.cpodDataBlock= parentDataBlock; + } + + @Override + public CPODDDDataInfo createDataInfo(DetectionPlotDisplay dddisplay) { + return new CPODDDDataInfo(cpodDataBlock, dddisplay); + } + +} + diff --git a/src/cpod/dataPlotFX/CPODPlotInfoFX.java b/src/cpod/dataPlotFX/CPODPlotInfoFX.java index a9446a5b..5e7a5b50 100644 --- a/src/cpod/dataPlotFX/CPODPlotInfoFX.java +++ b/src/cpod/dataPlotFX/CPODPlotInfoFX.java @@ -3,15 +3,21 @@ package cpod.dataPlotFX; import java.awt.geom.Path2D; import PamController.PamControlledUnit; +import PamUtils.PamArrayUtils; import PamUtils.PamUtils; +import PamView.GeneralProjector; import PamView.HoverData; import PamView.GeneralProjector.ParameterType; import PamView.GeneralProjector.ParameterUnits; +import PamView.symbol.PamSymbolChooser; +import PamView.symbol.PamSymbolManager; import PamguardMVC.PamDataUnit; import PamguardMVC.dataSelector.DataSelector; import cpod.CPODClick; import cpod.CPODClickDataBlock; +import dataPlotsFX.TDManagedSymbolChooserFX; import dataPlotsFX.TDSymbolChooserFX; +import dataPlotsFX.data.TDDataInfoFX; import dataPlotsFX.data.TDScaleInfo; import dataPlotsFX.data.generic.GenericDataPlotInfo; import dataPlotsFX.data.generic.GenericScaleInfo; @@ -22,6 +28,7 @@ import javafx.geometry.Orientation; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; +import pamViewFX.fxNodes.PamSymbolFX; /** * CPOD plot info. @@ -94,6 +101,17 @@ public class CPODPlotInfoFX extends GenericDataPlotInfo { } return cpodSettingsPane; } + + @Override + public TDSymbolChooserFX createSymbolChooser() { + PamSymbolManager symbolManager = getDataBlock().getPamSymbolManager(); + if (symbolManager == null) { + return null; + } + GeneralProjector p = this.getTDGraph().getGraphProjector(); + PamSymbolChooser sc = symbolManager.getSymbolChooser(getTDGraph().getUniqueName(), p); + return new CPODSymbolChooserFX(this, sc, TDSymbolChooserFX.DRAW_SYMBOLS); + } // /** @@ -124,45 +142,98 @@ public class CPODPlotInfoFX extends GenericDataPlotInfo { public Polygon drawDataUnit(int plotNumber, PamDataUnit pamDataUnit, GraphicsContext g, double scrollStart, TDProjectorFX tdProjector, int type) { // if (getScaleInfoIndex()==getScaleInfos().indexOf(frequencyInfo)) drawClickFFT( plotNumber, pamDataUnit,g , scrollStart, tdProjector, type); - //System.out.println("ClickPlotInfo: Draw data unit: " + pamDataUnit.getUID()); //draw special data units. Path2D path2D = null; ParameterType paramType = getCurrentScaleInfo().getDataType(); - - // if (getScaleInfoIndex()==getScaleInfos().indexOf(stemScaleInfo)) - if (getCurrentScaleInfo().getDataType() == ParameterType.AMPLITUDE_STEM){ - //draw on a stem plot. + + switch (getCurrentScaleInfo().getDataType()) { + + case AMPLITUDE_STEM: + case NCYCLES: + case AMPLITUDE_LIN: + case BANDWIDTH: + case FREQUENCY: path2D = drawStemClick( plotNumber, pamDataUnit,g , scrollStart, tdProjector, type, paramType); + break; + default: + return super.drawDataUnit(plotNumber, pamDataUnit, g, scrollStart, tdProjector, type); } - else if (getCurrentScaleInfo().getDataType() == ParameterType.NCYCLES){ - //draw CPOD cycles. - path2D = drawStemClick( plotNumber, pamDataUnit,g , scrollStart, tdProjector, type, paramType); - - } - - else if (getCurrentScaleInfo().getDataType() == ParameterType.AMPLITUDE_LIN){ - //draw cpod SPL. - path2D = drawStemClick( plotNumber, pamDataUnit,g , scrollStart, tdProjector, type, paramType); - } - - else if (getCurrentScaleInfo().getDataType() == ParameterType.BANDWIDTH){ - //draw cpod SPL. - path2D = drawStemClick( plotNumber, pamDataUnit,g , scrollStart, tdProjector, type, paramType); - } - - else return super.drawDataUnit(plotNumber, pamDataUnit, g, scrollStart, tdProjector, type); - //add to hover list if special data units. if (path2D!=null && type!=TDSymbolChooserFX.HIGHLIGHT_SYMBOL && type!=TDSymbolChooserFX.HIGHLIGHT_SYMBOL_MARKED ){ tdProjector.addHoverData(new HoverData(path2D, pamDataUnit, 0, plotNumber)); return null; } - return null; } + + /** + * Draw a frequency line + * @param plotNumber + * @param pamDataUnit + * @param g + * @param scrollStart + * @param tdProjector + * @param type + * @return + */ + @Override + public Polygon drawFrequencyData(int plotNumber, PamDataUnit pamDataUnit, GraphicsContext g, double scrollStart, + TDProjectorFX tdProjector, int type) { + + + g.setLineDashes(null); + g.setLineWidth(2); + + double[] f = pamDataUnit.getFrequency(); + if (f == null) { + return null; + } + if (f.length == 1) { + System.out.println("CPODPlot: Single frequency measure in data unit " + pamDataUnit.toString()); + } + + // draw a frequency box. + double y0 = tdProjector.getYPix(f[0]); + double y1 = tdProjector.getYPix(f[1]); + double x0 = tdProjector.getTimePix(pamDataUnit.getTimeMilliseconds()-scrollStart); + double x1 = tdProjector.getTimePix(pamDataUnit.getEndTimeInMilliseconds()-scrollStart); + + + System.out.println("CPODPlotInfoFX: Draw freq data unit: x: " + x0 + " y " + " y1: " + y1 + " to " + y0 + " cpod_data " + ((CPODClick) pamDataUnit).getBw()); + PamArrayUtils.printArray(f); + + g.setStroke(getSymbolChooser().getPamSymbol(pamDataUnit, type).getLineColor()); + + Color fillCol = getSymbolChooser().getPamSymbol(pamDataUnit, type).getFillColor(); + g.setFill(Color.color(fillCol.getRed(), fillCol.getGreen(), fillCol.getBlue(), 0.4)); //add alpha + + g.strokeLine(x0, y0, x0, y1); + + + //create the polygon. + + + + Path2D path2D= new Path2D.Double(0,1); + + // if (Math.abs(x1-x0)>50) { + // + // System.out.println("Generic Data Plot: " + "x0: " + x0 + " x1: " + x1 + " y1: " + y1 + " y: " + y0); + // } + + path2D.moveTo(x0, y0); + path2D.lineTo(x0, y1); + + + tdProjector.addHoverData(new HoverData(path2D, pamDataUnit, 0, plotNumber)); + + + return null; + } + /** * Specific ways to plot CPOD data as NYCLES, AMPLITUDE_LIN and AMPLITUDE_STEM. * @param plotNumber - the plot on which the FFT is drawing. @@ -206,7 +277,9 @@ public class CPODPlotInfoFX extends GenericDataPlotInfo { int[] chanClick=PamUtils.getChannelArray(pamDataUnit.getChannelBitmap()); int[] chanPlot=PamUtils.getChannelArray(getTDGraph().getCurrentScaleInfo().getPlotChannels()[plotNumber]); - Path2D path2D = new Path2D.Double(0,1); + Path2D path2D = new Path2D.Double(0,1); + + CPODClick clk = ((CPODClick) pamDataUnit); double x1 = 0, y1 = 0, x2 = 0, y2 = 0; for (int i=0; i getSettingsPane(){ - dlControl.getSettingsPane().setParams(dlControl.getCPODParam()); - return dlControl.getSettingsPane(); + cpodControl.getSettingsPane().setParams(cpodControl.getCPODParam()); + return cpodControl.getSettingsPane(); } @Override public void updateParams() { - CPODParams newParams=dlControl.getSettingsPane().getParams(dlControl.getCPODParam()); + CPODParams newParams=cpodControl.getSettingsPane().getParams(cpodControl.getCPODParam()); if (newParams!=null) { - dlControl.setCPODParams(newParams); + cpodControl.setCPODParams(newParams); } //setup the controlled unit. - dlControl.setupControlledUnit(); + cpodControl.setupControlledUnit(); } @Override diff --git a/src/cpod/fx/CPODSettingsPane.java b/src/cpod/fx/CPODSettingsPane.java index 59a6ffde..31c2a37b 100644 --- a/src/cpod/fx/CPODSettingsPane.java +++ b/src/cpod/fx/CPODSettingsPane.java @@ -2,6 +2,7 @@ package cpod.fx; import java.io.File; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import javax.swing.SwingUtilities; @@ -11,8 +12,8 @@ import org.apache.commons.io.FileUtils; import PamController.PamGUIManager; import PamController.SettingsPane; import cpod.CPODControl2; -import cpod.CPODImporter.CPODFileType; import cpod.CPODParams; +import cpod.CPODUtils.CPODFileType; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.geometry.Insets; @@ -64,7 +65,8 @@ public class CPODSettingsPane extends SettingsPane { /** * The extension filter. Holds the types of files that can be imported. */ - private ExtensionFilter extensionFilter; + private FileChooser.ExtensionFilter extensionFilterCPOD; + // private FileChooser.ExtensionFilter extensionFilterFPOD; /** @@ -97,7 +99,7 @@ public class CPODSettingsPane extends SettingsPane { private PamButton importButton; - private List> tasks; + private Task task; private boolean running = false; @@ -110,6 +112,10 @@ public class CPODSettingsPane extends SettingsPane { super(null); this.cpodControl = cpodControl2; + + //define the types of files to be imported ("Note: add FP1 and FP3 here) + extensionFilterCPOD = new ExtensionFilter("CPOD file", "*.cp1", "*.cp3", "*.fp1", "*.fp3"); + //file chooser fileChooser = new FileChooser(); fileChooser.getExtensionFilters().addAll(getExtensionFilters()); @@ -117,40 +123,35 @@ public class CPODSettingsPane extends SettingsPane { //folder chooser folderChooser = new DirectoryChooser(); - //define the types of files to be imported ("Note: add FP1 and FP3 here) - extensionFilter = new ExtensionFilter("CPOD file", "*.cp1", "*.cp3"); pathLabel = new TextField("No classifier file selected"); pathLabel.setEditable(false); - + filesInfoLabel = new Label(); // PamButton browsFileButton = new PamButton("", PamGlyphDude.createPamGlyph(MaterialDesignIcon.FILE_MULTIPLE, PamGuiManagerFX.iconSize)); PamButton browsFileButton = new PamButton("", PamGlyphDude.createPamIcon("mdi2f-file-multiple", PamGuiManagerFX.iconSize)); browsFileButton.setMinWidth(30); - browsFileButton.setTooltip(new Tooltip("Browse to select a CP1 or CP3 file")); + browsFileButton.setTooltip(new Tooltip("Browse to select individual or mutliple CP1 or CP3 files or FP1 or FP3 files")); browsFileButton.setOnAction((action)->{ - List files = fileChooser.showOpenMultipleDialog(this.getFXWindow()); - setFileList(files); + List files = new LinkedList(fileChooser.showOpenMultipleDialog(this.getFXWindow())); + setNewFiles(files); - if (this.files.size()>0) { - importButton.setDisable(false); - } }); - - + + // PamButton browsFolderButton = new PamButton("", PamGlyphDude.createPamGlyph(MaterialDesignIcon.FOLDER, PamGuiManagerFX.iconSize)); PamButton browsFolderButton = new PamButton("", PamGlyphDude.createPamIcon("mdi2f-folder", PamGuiManagerFX.iconSize)); browsFolderButton.setMinWidth(30); - browsFolderButton.setTooltip(new Tooltip("Browse to a folder containg CP1 and CP3 files")); + browsFolderButton.setTooltip(new Tooltip("Browse to a folder contaning CP1 and CP3 or FP1 and FP3 files")); browsFolderButton.setOnAction((action)->{ File file = folderChooser.showDialog(this.getFXWindow()); - setNewFile(file); + setNewFolder(file); }); @@ -168,7 +169,7 @@ public class CPODSettingsPane extends SettingsPane { pathLabel.setMaxWidth(Double.MAX_VALUE); pathLabel.prefHeightProperty().bind(browsFolderButton.heightProperty()); - filesPane.getChildren().addAll(pathLabel, browsFolderButton); + filesPane.getChildren().addAll(pathLabel, browsFileButton, browsFolderButton); //time offset pane. startOffset = new PamSpinner(); @@ -293,56 +294,32 @@ public class CPODSettingsPane extends SettingsPane { private boolean importCPODData() { if (files==null) return false; - System.out.println("Import CPOD data: " + files.size()); //begins the import - this.tasks = this.cpodControl.getCpodImporter().importCPODData(files); + // this.tasks = this.cpodControl.getCpodImporter().importCPODData(files); + this.task = this.cpodControl.importPODData(files); - if (tasks ==null) return false; + if (task ==null) return false; + this.progressBar.setProgress(-1.); - this.progressBar.progressProperty().bind(tasks.get(0).progressProperty()); - this.progressLabel.textProperty().bind(tasks.get(0).messageProperty()); - - //binds the progress bar - imports are - for (int i=0; i{ - this.progressBar.progressProperty().bind(tasks.get(ii).progressProperty()); - this.progressLabel.textProperty().bind(tasks.get(ii).messageProperty()); - }); - - tasks.get(tasks.size()-1).setOnSucceeded((worker)->{ - this.progressBar.progressProperty().bind(tasks.get(ii).progressProperty()); - this.progressLabel.textProperty().bind(tasks.get(ii).messageProperty()); - }); - - } - - - //Will be called if the tasks are cancelled or succeed. - tasks.get(tasks.size()-1).setOnCancelled((worker)->{ - System.out.println("Importing cancelled:"); - - importingFinished(); - }); - - tasks.get(tasks.size()-1).setOnSucceeded((worker)->{ - System.out.println("Importing succeeeded:"); - - importingFinished(); - }); - - tasks.get(tasks.size()-1).setOnFailed((worker)->{ - System.out.println("Importing failed:"); - - importingFinished(); - }); + this.progressBar.progressProperty().bind(task.progressProperty()); + this.progressLabel.textProperty().bind(task.messageProperty()); //run the tasks - this.cpodControl.getCpodImporter().runTasks(tasks); + this.cpodControl.getCpodImporter().runTasks(task); + task.setOnCancelled((a)->{ + importingFinished(); + }); + + task.setOnSucceeded((a)->{ + importingFinished(); + }); + + task.setOnFailed((a)->{ + importingFinished(); + }); return true; } @@ -373,19 +350,13 @@ public class CPODSettingsPane extends SettingsPane { progressBar.setProgress(1.); }); - System.out.println("Importing finished 3:"); - } /* * Stop the import. */ private void stopImport() { - if (tasks!=null) { - for (int i=0; i { //have a look through the files and check that there are some files to import. for (int i=0; i cp1 = (List) FileUtils.listFiles(folder, - new String[]{CPODFileType.values()[i].getText()}, this.subFolder.isSelected()); - System.out.println("Files out: " + cp1); - files.addAll(cp1); + List cp1 = (List) FileUtils.listFiles(folder, + new String[]{CPODFileType.values()[i].getText()}, this.subFolder.isSelected()); + System.out.println("Files out: " + cp1); + files.addAll(cp1); } catch (Exception e) { System.err.println("Current directory does not exist: " + folder ); @@ -428,8 +399,13 @@ public class CPODSettingsPane extends SettingsPane { * new ExtensionFilter("Pytorch Model", "*.pk") * @return a list of extension fitlers for the file dialog. */ - public ExtensionFilter getExtensionFilters(){ - return extensionFilter; + public ArrayList getExtensionFilters(){ + ArrayList filters = new ArrayList(); + filters.add(extensionFilterCPOD); + //don't add an exstra filter - just have all as one - otherwise the user has to change the + //file dialog to FPODs to get it to work. + // filters.add(extensionFilterFPOD); + return filters; } @@ -442,7 +418,7 @@ public class CPODSettingsPane extends SettingsPane { @Override public CPODParams getParams(CPODParams currParams) { - currParams.offlineFolder = currentFolder.getAbsolutePath(); + currParams.offlineFolder = currentFolder==null? null:currentFolder.getAbsolutePath(); currParams.subFolders = subFolder.selectedProperty().get(); try { currParams.startOffset = startOffset.getValue(); @@ -464,14 +440,14 @@ public class CPODSettingsPane extends SettingsPane { timeStretch.getValueFactory().setValue(cpodParams.timeStretch); - setNewFile(cpodParams.offlineFolder==null ? null : new File(cpodParams.offlineFolder)); + setNewFolder(cpodParams.offlineFolder==null ? null : new File(cpodParams.offlineFolder)); } /** * Called whenever there is a new selected file or folder. * @param file - the file label to set. */ - private void setNewFile(File file) { + private void setNewFolder(File file) { if (file==null) { this.pathLabel.setText("No folder or file selected"); @@ -480,17 +456,40 @@ public class CPODSettingsPane extends SettingsPane { this.pathLabel.setText(file.getAbsolutePath()); setFileList(file); - if (files.size()>1) { - this.filesInfoLabel.setText(files.size() + " CPOD files to import"); - } - else { - this.filesInfoLabel.setText(files.size() + " CPOD file to import"); - } + setFileInfoLabel(); } importButton.setDisable(this.files==null || this.files.size()<1); } + /** + * Called whenever there is a new selected file or folder. + * @param file - the file label to set. + */ + private void setNewFiles(List files) { + + if (files==null || files.size()==0) { + this.pathLabel.setText("No folder or file selected"); + } + else { + this.pathLabel.setText(files.get(0).getAbsolutePath()); + + this.files = files; + + setFileInfoLabel(); + } + + importButton.setDisable(this.files==null || this.files.size()<1); + } + + private void setFileInfoLabel() { + if (files.size()>=1) { + this.filesInfoLabel.setText(files.size() + " CPOD files to import"); + } + else { + this.filesInfoLabel.setText(files.size() + " CPOD file to import"); + } + } private CPODParams showWarning(String string) { // TODO Auto-generated method stub diff --git a/src/cpod/logging/CPODClickTrainLogging.java b/src/cpod/logging/CPODClickTrainLogging.java new file mode 100644 index 00000000..7e3ee47a --- /dev/null +++ b/src/cpod/logging/CPODClickTrainLogging.java @@ -0,0 +1,86 @@ +package cpod.logging; + +import java.sql.Types; + +import PamguardMVC.PamDataUnit; +import PamguardMVC.superdet.SuperDetDataBlock; +import cpod.CPODClassification; +import cpod.CPODClassification.CPODSpeciesType; +import cpod.CPODClickTrainDataUnit; +import cpod.CPODControl2; +import cpod.CPODUtils; +import generalDatabase.PamTableDefinition; +import generalDatabase.PamTableItem; +import generalDatabase.SQLTypes; +import generalDatabase.SuperDetLogging; + +public class CPODClickTrainLogging extends SuperDetLogging { + + /** + * The pam table items for saving. + */ + private PamTableItem species, quality, isEcho, cpodFileID; + + private CPODControl2 cpodControl; + + public CPODClickTrainLogging(CPODControl2 cpodControl, SuperDetDataBlock pamDataBlock) { + super(pamDataBlock); + this.cpodControl=cpodControl; + setTableDefinition(createBaseTable()); + } + + + /** + * Create the basic table definition for the group detection. + * @return basic table - annotations will be added shortly ! + */ + public PamTableDefinition createBaseTable() { + + PamTableDefinition tableDef = new PamTableDefinition(cpodControl.getUnitName(), UPDATE_POLICY_OVERWRITE); + tableDef.addTableItem(cpodFileID = new PamTableItem("CPOD_file_ID", Types.INTEGER)); + tableDef.addTableItem(species = new PamTableItem("Species", Types.CHAR, 10)); + tableDef.addTableItem(quality = new PamTableItem("Confidence", Types.INTEGER)); + tableDef.addTableItem(isEcho = new PamTableItem("isEcho", Types.BOOLEAN)); + + return tableDef; + } + + @Override + public void setTableData(SQLTypes sqlTypes, PamDataUnit pamDataUnit) { + CPODClickTrainDataUnit ctDataUnit = (CPODClickTrainDataUnit) pamDataUnit; + if (ctDataUnit.getSubDetectionsCount()>0) { + species.setValue(ctDataUnit.getSpecies().toString()); + quality.setValue(ctDataUnit.getConfidence()); + isEcho.setValue(ctDataUnit.isEcho()); + + } + + } + + @Override + protected CPODClickTrainDataUnit createDataUnit(SQLTypes sqlTypes, long timeMilliseconds, int databaseIndex) { + + + CPODClassification classification = new CPODClassification(); + + String speciesStr = species.getStringValue(); + + CPODSpeciesType speciesType = CPODUtils.getSpecies(speciesStr); + + classification.clicktrainID = cpodFileID.getIntegerValue(); + classification.species = speciesType; + classification.clicktrainID = cpodFileID.getIntegerValue(); + classification.isEcho = isEcho.getBooleanValue(); + + CPODClickTrainDataUnit dataUnit = new CPODClickTrainDataUnit(timeMilliseconds, null, classification); + + return dataUnit; + } + + + public CPODControl2 getClickTrainControl() { + return cpodControl; + } + + +} diff --git a/src/cpod/logging/CPODSubDetLogging.java b/src/cpod/logging/CPODSubDetLogging.java new file mode 100644 index 00000000..811c4192 --- /dev/null +++ b/src/cpod/logging/CPODSubDetLogging.java @@ -0,0 +1,30 @@ +package cpod.logging; + +import PamguardMVC.PamDataUnit; +import cpod.CPODClickTrainDataBlock; +import generalDatabase.PamSubtableDefinition; +import generalDatabase.SQLLogging; +import generalDatabase.SQLTypes; + +/** + * Logs the the children of the click train detection. + * @author Jamie Macaulay + * + */ +public class CPODSubDetLogging extends SQLLogging { + + + + public CPODSubDetLogging(CPODClickTrainLogging clickTrainDetLogging, CPODClickTrainDataBlock clickTRainDaatBlock) { + super(clickTRainDaatBlock); + setTableDefinition(new PamSubtableDefinition(clickTrainDetLogging.getClickTrainControl().getUnitName()+"_Children")); + + } + + @Override + public void setTableData(SQLTypes sqlTypes, PamDataUnit pamDataUnit) { + // TODO Auto-generated method stub + + } + + } \ No newline at end of file diff --git a/src/dataGram/DatagramManager.java b/src/dataGram/DatagramManager.java index 5ae8aa66..d82f79dd 100644 --- a/src/dataGram/DatagramManager.java +++ b/src/dataGram/DatagramManager.java @@ -551,6 +551,8 @@ public class DatagramManager { else { PamController.getInstance().notifyTaskProgress( new SimplePamTaskUpdate("Finished Datagram Mapping", PamTaskUpdate.STATUS_DONE)); + publish(new DatagramProgress(PamTaskUpdate.STATUS_DONE, 1, 1)); + } datagramCreator = null; } diff --git a/src/dataGram/DatagramProgress.java b/src/dataGram/DatagramProgress.java index 0a6287d6..3fedf3b8 100644 --- a/src/dataGram/DatagramProgress.java +++ b/src/dataGram/DatagramProgress.java @@ -77,8 +77,7 @@ public class DatagramProgress extends PamTaskUpdate { } @Override - public double getProgress() { -// System.out.println("DatagramProgress: " + processedUnits + " tot: " + totalUnits); + public double getProgress2() { double progress= ProgressIndicator.INDETERMINATE_PROGRESS; switch(getStatus()) { case DatagramProgress.STATUS_BLOCKCOUNT: @@ -95,13 +94,17 @@ public class DatagramProgress extends PamTaskUpdate { else progress=0; break; } + + //System.out.println("DatagramProgress: " + processedUnits + " tot: " + totalUnits + " progress: " + progress); + return progress; } @Override - public double getProgress2(){ + public double getProgress(){ + //seems like the current point is simply the percentage done... double progress; - if (pointsToUpdate>0) progress=((double)currentPoint)/pointsToUpdate; + if (pointsToUpdate>0) progress=((double)currentPoint)/100.; else progress=0; switch(getStatus()) { case DatagramProgress.STATUS_BLOCKCOUNT: @@ -110,14 +113,16 @@ public class DatagramProgress extends PamTaskUpdate { progress=0; break; case DatagramProgress.STATUS_STARTINGFILE: - progress=((double)currentPoint)/pointsToUpdate; + progress=((double)currentPoint)/100.; break; case DatagramProgress.STATUS_ENDINGFILE: - progress=((double)currentPoint)/pointsToUpdate; + progress=((double)currentPoint)/100.; break; case DatagramProgress.STATUS_UNITCOUNT: break; } + //System.out.println("DatagramProgress2: " + currentPoint + " tot: " + pointsToUpdate + " progress: " + progress); + return progress; } diff --git a/src/dataMap/MapOverlap.java b/src/dataMap/MapOverlap.java new file mode 100644 index 00000000..f8df49a1 --- /dev/null +++ b/src/dataMap/MapOverlap.java @@ -0,0 +1,30 @@ +package dataMap; + +public class MapOverlap { + + private long file2Start; + + private long file1End; + + public MapOverlap(long file1End, long file2Start) { + super(); + this.file1End = file1End; + this.file2Start = file2Start; + } + + /** + * @return the file2Start + */ + public long getFile2Start() { + return file2Start; + } + + /** + * @return the file1End + */ + public long getFile1End() { + return file1End; + } + + +} diff --git a/src/dataMap/OfflineDataMap.java b/src/dataMap/OfflineDataMap.java index d51ee120..837bcb38 100644 --- a/src/dataMap/OfflineDataMap.java +++ b/src/dataMap/OfflineDataMap.java @@ -844,4 +844,25 @@ abstract public class OfflineDataMap { obs.updateDataMap(this, mapPoint); } } + + /** + * Check to see if any data maps overlap in time. + * @return list of overlaps. + */ + public ArrayList checkOverlaps() { + long bigLap = Long.MIN_VALUE; + TmapPoint prevPoint = null; + ArrayList overlaps = new ArrayList<>(); + for (TmapPoint mapPoint : mapPoints) { + if (prevPoint != null) { + long olap = prevPoint.getEndTime() - mapPoint.getStartTime(); + if (mapPoint.getStartTime() < prevPoint.getEndTime()) { + bigLap = Math.max(olap, bigLap); + overlaps.add(new MapOverlap(prevPoint.getEndTime(), mapPoint.getStartTime())); + } + } + prevPoint = mapPoint; + } + return overlaps; + } } diff --git a/src/dataMap/layoutFX/DataMapPaneFX.java b/src/dataMap/layoutFX/DataMapPaneFX.java index dfcb284a..82834eae 100644 --- a/src/dataMap/layoutFX/DataMapPaneFX.java +++ b/src/dataMap/layoutFX/DataMapPaneFX.java @@ -10,23 +10,33 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.Side; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.hidingPane.HidingPane; import pamViewFX.fxNodes.internalNode.PamInternalPane; +import pamViewFX.fxNodes.pamAxis.PamDateAxis; +import pamViewFX.fxStyles.PamStylesManagerFX; +import userDisplayFX.UserDisplayControlFX; import userDisplayFX.UserDisplayNodeFX; import userDisplayFX.UserDisplayNodeParams; /** - * Pane which shows users all data currently stored in binary files and databases within PAMGuard. Also allows for navigation to - * different part of the data time series. + * Pane which shows users all data currently stored in binary files and + * databases within PAMGuard. Also allows for navigation to different part of + * the data time series. + * * @author Jamie Macaulay * */ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { + private static final double HIDE_PANE_WIDTH = 400; + /** * Reference to the data map control. */ @@ -57,7 +67,12 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { */ private ScalePaneFX scalePane; - private PamBorderPane topHolder; + private PamVBox settingsPane; + + /** + * Axis which shows the current dates + */ + private PamDateAxis dateAxis; public DataMapPaneFX(DataMapControl dataMapControl){ this.dataMapControl=dataMapControl; @@ -71,50 +86,55 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { //create all the different panes, summaryPane = new SummaryPaneFX(dataMapControl, this); - summaryPane.getStyleClass().add("pane"); scalePane=new ScalePaneFX(dataMapControl,this); - scalePane.getStyleClass().add("pane"); scrollingDataPanel= new ScrollingDataPaneFX(dataMapControl, this); - //create top section - topHolder=new PamBorderPane(); - topHolder.getStyleClass().add("pane"); - topHolder.setLeft(summaryPane); - topHolder.setRight(scalePane); - topHolder.setPadding(new Insets(10,10,10,10)); - topHolder.setPrefHeight(120); - + //create the setting spane + settingsPane=new PamVBox(); +// settingsPane.getChildren().add(summaryPane); + settingsPane.getChildren().add(scalePane); + settingsPane.setPadding(new Insets(40,10,10,10)); + settingsPane.setPrefWidth(HIDE_PANE_WIDTH); + // //have a horizontal scroll pane // PamScrollPane topScrollHolder=new PamScrollPane(topHolder); // topScrollHolder.setPrefHeight(180); -// topScrollHolder.setVbarPolicy(ScrollBarPolicy.NEVER); - - +// topScrollHolder.setVbarPolicy(ScrollBarPolicy.NEVER) //topHolder.prefHeightProperty().bind(summaryPane.prefHeightProperty()); //hiding summary pane - hidingSummaryPane=new HidingPane(Side.TOP, topHolder, this, false); - hidingSummaryPane.getStyleClass().add("pane"); + hidingSummaryPane=new HidingPane(Side.RIGHT, settingsPane, this, true); hidingSummaryPane.setVisibleImmediatly(false); hidingSummaryPane.showHidePane(true); - hidingSummaryPane.getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); //style as a settings pane. - + hidingSummaryPane.getStyleClass().add("pane-trans"); + hidingSummaryPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + StackPane.setAlignment(hidingSummaryPane, Pos.TOP_RIGHT); + hidingSummaryPane.setPrefWidth(HIDE_PANE_WIDTH); + //style the show button. showButton=hidingSummaryPane.getShowButton(); - showButton.getStyleClass().add("transparent-button-square"); - showButton.setStyle("-fx-background-radius: 0 0 10 10;"); + showButton.getStyleClass().add("close-button-left"); + showButton.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); -// showButton.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); - showButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-down", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); - showButton.setPrefWidth(60); - scrollingDataPanel.setTop(showButton); - PamBorderPane.setAlignment(showButton, Pos.TOP_CENTER); + +// showButton.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_DOWN, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize));\ + showButton.setGraphic( PamGlyphDude.createPamIcon("mdi2c-cog", Color.WHITE, PamGuiManagerFX.iconSize)); + showButton.setPrefHeight(60); + scrollingDataPanel.setRight(showButton); - - this.setTop(hidingSummaryPane); - this.setCenter(scrollingDataPanel); + StackPane.setAlignment(showButton, Pos.CENTER_RIGHT); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(scrollingDataPanel, hidingSummaryPane, showButton); + + dateAxis = new PamDateAxis(); + dateAxis.setMinHeight(50); + dateAxis.prefWidthProperty().bind(scrollingDataPanel.widthProperty()); + + this.setTop(dateAxis); + this.setCenter(stackPane); } public void newSettings() { @@ -151,7 +171,7 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { public void newDataSources() { scrollingDataPanel.newDataSources(); summaryPane.newDataSources(); - hidingSummaryPane.resetHideAnimation(); + //hidingSummaryPane.resetHideAnimation(); } /** @@ -258,13 +278,6 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { summaryPane.setSelectedDataTime(timeStart, timeEnd); } - /** - * Pane which holds all top controls. Sits within a hiding pane. - * @return the pane which holds top controls and indicators. - */ - public PamBorderPane getTopHolder() { - return topHolder; - } @Override public boolean isMinorDisplay() { @@ -284,4 +297,10 @@ public class DataMapPaneFX extends PamBorderPane implements UserDisplayNodeFX { } + @Override + public UserDisplayControlFX getUserDisplayControl() { + // TODO Auto-generated method stub + return null; + } + } diff --git a/src/dataMap/layoutFX/DataStreamPaneFX.java b/src/dataMap/layoutFX/DataStreamPaneFX.java index e2c13d04..8d923f0b 100644 --- a/src/dataMap/layoutFX/DataStreamPaneFX.java +++ b/src/dataMap/layoutFX/DataStreamPaneFX.java @@ -17,6 +17,7 @@ import dataPlotsFX.layout.AxisPane; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.geometry.Orientation; +import javafx.geometry.Pos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Label; @@ -31,7 +32,9 @@ import PamController.PamController; import PamUtils.PamCalendar; import PamguardMVC.PamDataBlock; import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.pamAxis.PamAxisFX; +import pamViewFX.fxNodes.pamAxis.PamAxisPane; import pamViewFX.fxNodes.utilsFX.ColourArray; public class DataStreamPaneFX extends PamBorderPane { @@ -140,9 +143,10 @@ public class DataStreamPaneFX extends PamBorderPane { * Create pane which holds datasream label and allows the split pane to collapse */ private Pane createTopPane(){ - Pane topPane=new Pane(); - topPane.getStyleClass().add("pane"); + PamHBox topPane=new PamHBox(); + topPane.getStyleClass().add("pane-opaque"); topPane.getChildren().add(new Label(this.dataBlock.getDataName())); + topPane.setAlignment(Pos.CENTER); return topPane; } @@ -200,7 +204,7 @@ public class DataStreamPaneFX extends PamBorderPane { /** * The wheel scroll factor. */ - private double wheelScrollFactor = 0.2; + private double wheelScrollFactor = 0.1; /** * Writable image for 3D datagram. @@ -210,7 +214,7 @@ public class DataStreamPaneFX extends PamBorderPane { /** * Pane which holds the axis. */ - private AxisPane axisPane; + private PamAxisPane axisPane; /** * Pane which holds the plot canvas. @@ -342,7 +346,7 @@ public class DataStreamPaneFX extends PamBorderPane { datastreamAxis.setFractionalScale(true); datastreamAxis.setLogScale(false); - axisPane=new AxisPane(datastreamAxis); + axisPane=new PamAxisPane(datastreamAxis, Orientation.VERTICAL); axisPane.getStyleClass().add("pane"); axisPane.setOrientation(Orientation.VERTICAL); axisPane.setPrefWidth(DataStreamPaneFX.axisPrefWidth); @@ -366,16 +370,24 @@ public class DataStreamPaneFX extends PamBorderPane { if (timeline!=null) timeline.stop(); timeline = new Timeline(new KeyFrame( Duration.millis(tm), - ae -> paintCanvas(0))); + ae -> { +// System.out.println("Paint Canvas zero"); + paintCanvas(0); + })); timeline.play(); return; } lastTime=currentTime; + long time1 = System.currentTimeMillis(); paintPlotCanvas(plotCanvas.getGraphicsContext2D()); paintDrawCanvas(drawCanvas.getGraphicsContext2D()); + long time2 = System.currentTimeMillis(); + + //System.out.println("Paint Canvas: " + this + " " + System.currentTimeMillis() + " " + (time2-time1)); + } /** @@ -593,14 +605,16 @@ public class DataStreamPaneFX extends PamBorderPane { } datagramImage = new WritableImage(imageData.length, imageData[0].length); PixelWriter writableRaster = datagramImage.getPixelWriter(); + g.setFill(Color.LIGHTGRAY); + g.fillRect(0, 0, nXPoints, nYPoints); for (int i = 0; i < nXPoints; i++) { for (int j = 0; j < nYPoints; j++) { y = nYPoints-j-1; if (imageData[i][j] < 0) { - writableRaster.setColor(i,y,Color.LIGHTGRAY); + //writableRaster.setColor(i,y,Color.LIGHTGRAY); } else if (imageData[i][j] == 0) { - writableRaster.setColor(i,y, Color.LIGHTGRAY); + //writableRaster.setColor(i,y, Color.LIGHTGRAY); } else { iCol = (int) (NCOLOURPOINTS * (Math.log(imageData[i][j]) - minMaxValue[0]) / scaleRange); @@ -1028,10 +1042,6 @@ public class DataStreamPaneFX extends PamBorderPane { repaint(ScrollingDataPaneFX.REPAINTMILLIS); //update at 10 frames per second } - private void repaint() { - this.repaint(0); - } - /** * Get the pane which sits at the top of the datagraph and contains a label showing the datablock being displayed. * @return the pane which sits at the top of the datagraph diff --git a/src/dataMap/layoutFX/ScalePaneFX.java b/src/dataMap/layoutFX/ScalePaneFX.java index 021bee8e..49569edb 100644 --- a/src/dataMap/layoutFX/ScalePaneFX.java +++ b/src/dataMap/layoutFX/ScalePaneFX.java @@ -9,11 +9,15 @@ import PamUtils.PamCalendar; import binaryFileStorage.BinaryStore; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; +import javafx.util.StringConverter; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamGridPane; @@ -21,9 +25,11 @@ import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; import pamViewFX.fxNodes.sliders.PamSlider; +import pamViewFX.fxNodes.utilityPanes.PamToggleSwitch; /** * Allows uses to change the horizontal and vertical scales on the data map. + * * @author Jamie Macaulay * */ @@ -42,7 +48,7 @@ public class ScalePaneFX extends PamBorderPane { /** * The slider which determines time scale. */ - private PamSlider timeSlider; + private Slider timeSlider; /** * Shows the time scale in pix/hour @@ -57,14 +63,17 @@ public class ScalePaneFX extends PamBorderPane { /** * Check box for log vertical scale. */ - private CheckBox logScaleBox; + private PamToggleSwitch logScaleBox; /** * The chosen time values. */ private double[] timeScaleChoices = DataMapParameters.hScaleChoices; - private Pane datagramPane; + /** + * Combo box holding options to channge the datargam bin size + */ + private ComboBox datagramBox; /** * Holds a list of times for the datagram bin size. @@ -74,7 +83,14 @@ public class ScalePaneFX extends PamBorderPane { /** * Holds everything. */ - private PamHBox holder; + private PamVBox holder; + + /** + * Grid pane with settings for the data scales + */ + private PamGridPane scaleSettingsPane; + + private Label dataGramLabel; @@ -82,9 +98,16 @@ public class ScalePaneFX extends PamBorderPane { this.dataMapControl = dataMapControl; this.dataMapPane = dataMapPane; - holder=new PamHBox(); +// //create the holder pane. +// PamVBox vBoxHolder=new PamVBox(); +// vBoxHolder.setSpacing(5); +// Label title=new Label("Scales"); +// PamGuiManagerFX.titleFont2style(title); +// vBoxHolder.getChildren().addAll(title, controlPane); + + holder=new PamVBox(); holder.setSpacing(20); - holder.getChildren().add(createScalePane()); + holder.getChildren().add(scaleSettingsPane = createScalePane()); this.setCenter(holder); @@ -101,31 +124,28 @@ public class ScalePaneFX extends PamBorderPane { public void checkDataGramPane(){ if (BinaryStore.findBinaryStoreControl()!=null){ DatagramManager dataGramManager=BinaryStore.findBinaryStoreControl().getDatagramManager(); - if (dataGramManager!=null && datagramPane==null) { - datagramPane=createDatagramPane(dataGramManager); - holder.getChildren().add(datagramPane); + if (dataGramManager!=null && datagramBox==null) { + datagramBox=createDatagramPane(dataGramManager); + scaleSettingsPane.add(dataGramLabel= new Label("Datagram bin size"), 0, 2); + scaleSettingsPane.add(dataGramComboBox, 1, 2); } } else { - holder.getChildren().remove(datagramPane); - datagramPane=null; + scaleSettingsPane.getChildren().remove(dataGramLabel); + scaleSettingsPane.getChildren().remove(dataGramComboBox); + + datagramBox=null; } } /** - * Create the pane to reset the datagram pane bin size. + * Create the a datagram combo box to change the size of the datagram * @param dataGramManager - the datagram manager for the current biinary store. - * @return + * @return a combo box with datagram bin sizes. */ - private Pane createDatagramPane(DatagramManager dataGramManager){ + private ComboBox createDatagramPane(DatagramManager dataGramManager){ - - PamGridPane dataGramOptions=new PamGridPane(); - dataGramOptions.setHgap(5); - dataGramOptions.setVgap(5); - - dataGramOptions.add(new Label("Datagram Bin Size"),0,0); dataGramComboBox=new ComboBox(createDurationList(dataGramManager)); dataGramComboBox.getSelectionModel().select(durationToString(dataGramManager.getDatagramSettings().datagramSeconds*1000L)); @@ -145,19 +165,7 @@ public class ScalePaneFX extends PamBorderPane { } }); - dataGramOptions.add(dataGramComboBox,1,0); - - //create the holder pane. - PamVBox vBoxHolder=new PamVBox(); - vBoxHolder.setSpacing(5); - Label title=new Label("Datagram"); -// title.setFont(PamGuiManagerFX.titleFontSize2); - PamGuiManagerFX.titleFont2style(title); - - vBoxHolder.getChildren().addAll(title, dataGramOptions); - - - return vBoxHolder; + return dataGramComboBox; } @@ -182,17 +190,25 @@ public class ScalePaneFX extends PamBorderPane { } - private Node createScalePane() { + private PamGridPane createScalePane() { PamGridPane controlPane=new PamGridPane(); controlPane.setHgap(5); controlPane.setVgap(5); //create time scale controls - controlPane.add(new Label("Time"),0,1); + controlPane.add(new Label("Time window"),0,1); //create time slider - timeSlider=new PamSlider(0, timeScaleChoices.length-1, 1); + timeSlider=new Slider(0, timeScaleChoices.length-1, 1); + timeSlider.setShowTickLabels(true); + timeSlider.setShowTickMarks(true); + timeSlider.setMajorTickUnit(1); + + timeSlider.setLabelFormatter(new ScaleStringConverter()); + +// PamGridPane.setHalignment(timeSlider, Pos.BOTTOM_CENTER); + controlPane.add(timeSlider,1,1); //add listener to time slider to change datamap. timeSlider.valueProperty().addListener((ov, oldVal, newVal)->{ @@ -200,10 +216,11 @@ public class ScalePaneFX extends PamBorderPane { dataMapPane.scaleChanged(); }); - controlPane.add(timeScaleLabel=new Label(""),2,1); + controlPane.add(timeScaleLabel=new Label(""),3,1); //create vertical scale controls - controlPane.add(new Label("Data Counts"),0,0); + controlPane.add(new Label("Data counts"),0,0); + scaleBox=new ComboBox (); scaleBox.getItems().add("No Scaling"); @@ -212,28 +229,37 @@ public class ScalePaneFX extends PamBorderPane { scaleBox.getItems().add("per Hour"); scaleBox.getItems().add("per Day"); controlPane.add(scaleBox,1,0); + scaleBox.setPrefWidth(200); scaleBox.valueProperty().addListener((ov, oldVal, newVal)->{ dataMapPane.scaleChanged(); }); - logScaleBox=new CheckBox("Log Scale"); - logScaleBox.setOnAction((action)->{ + logScaleBox=new PamToggleSwitch("Log Scale"); + logScaleBox.selectedProperty().addListener((obsVal, oldVal, newVal)->{ dataMapPane.scaleChanged(); - });; - controlPane.add(logScaleBox,2,0); + }); - //create the holder pane. - PamVBox vBoxHolder=new PamVBox(); - vBoxHolder.setSpacing(5); - Label title=new Label("Scales"); - PamGuiManagerFX.titleFont2style(title); - vBoxHolder.getChildren().addAll(title, controlPane); + + controlPane.add(logScaleBox,3,0); + return controlPane; + } + + class ScaleStringConverter extends StringConverter { + + @Override + public String toString(Double object) { + return String.valueOf(timeScaleChoices[object.intValue()]); + } + + @Override + public Double fromString(String string) { + return Double.valueOf(string); + } - return vBoxHolder; } /** diff --git a/src/dataMap/layoutFX/ScrollingDataPaneFX.java b/src/dataMap/layoutFX/ScrollingDataPaneFX.java index c518b36d..2faffbdb 100644 --- a/src/dataMap/layoutFX/ScrollingDataPaneFX.java +++ b/src/dataMap/layoutFX/ScrollingDataPaneFX.java @@ -8,28 +8,32 @@ import PamUtils.PamCalendar; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.geometry.Insets; +import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollBar; -import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.paint.Color; -import javafx.scene.text.Text; import javafx.util.Duration; import PamguardMVC.PamDataBlock; import dataMap.DataMapControl; -import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; -import pamViewFX.fxNodes.PamButton; import pamViewFX.fxNodes.PamColorsFX; import pamViewFX.fxNodes.PamScrollPane; -import pamViewFX.fxNodes.sashPane.SashPane; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.pamAxis.PamDateAxis; public class ScrollingDataPaneFX extends PamBorderPane { /** * Standard millis to wait for repaint. + * Do not ake this too high i.e. above 50 or the display gets very jerky */ - public static final long REPAINTMILLIS = 200; + public static final long REPAINTMILLIS = 50; + + /** + * The default expanded hieght for each pane. + */ + private static final int DATASTREAMPANE_HEIGHT = 200; /** * Reference to the DataMapControl. @@ -54,7 +58,7 @@ public class ScrollingDataPaneFX extends PamBorderPane { /** * Split pane whihc holds different graphs. */ - private SashPane dataPanePanes; + private PamVBox dataPanePanes; private ArrayList offlineDataStores; @@ -92,18 +96,10 @@ public class ScrollingDataPaneFX extends PamBorderPane { private PamBorderPane holder; - - /** - * Height at which a DataStreamPaneFX is considered to be 'collapsed' + * Axis which shows the current dates */ - private double collapseHeight=15; - - /* - * Height to expand a split to if the 'expand' button action is used. - */ - private double expandHeight=200; - + private PamDateAxis dateAxis; /** * Constructor for the ScrollingDataPaneFX @@ -131,15 +127,14 @@ public class ScrollingDataPaneFX extends PamBorderPane { //create the split pane to hold the graphs. - dataPanePanes=new SashPane(); - dataPanePanes.setHorizontal(false); + dataPanePanes=new PamVBox(); //dataPanePanes.setOrientation(Orientation.VERTICAL); dataPanePanes.prefWidthProperty().bind(mainScrollPane.widthProperty()); - dataPanePanes.prefHeightProperty().bind(mainScrollPane.heightProperty()); + //dataPanePanes.prefHeightProperty().bind(mainScrollPane.heightProperty()); mainScrollPane.setContent(dataPanePanes); //we have a custom scroll bar for horizontal stuff. - mainScrollPane.setHbarPolicy(ScrollBarPolicy.NEVER); +// mainScrollPane.setHbarPolicy(ScrollBarPolicy.NEVER); // ///TEMP/// // Button buttonTest=new Button("Test Map"); @@ -152,11 +147,11 @@ public class ScrollingDataPaneFX extends PamBorderPane { holder.setCenter(mainScrollPane); holder.setBottom(createScrollBar()); - PamButton test = new PamButton("Test"); - test.setOnAction((action)->{ - updateScrollBar(); - }); - holder.setLeft(test); +// PamButton test = new PamButton("Test"); +// test.setOnAction((action)->{ +// updateScrollBar(); +// }); +// holder.setLeft(test); setupScrollBar(); @@ -166,6 +161,18 @@ public class ScrollingDataPaneFX extends PamBorderPane { notifyScrollChange(300); }); + + dateAxis = new PamDateAxis(); + dateAxis.setAutoRanging(false); + dateAxis.setLabel("Time"); + dateAxis.setSide(Side.TOP); + dateAxis.setAnimated(false); + dateAxis.setMinHeight(50); +// dateAxis.prefWidthProperty().bind(scrollingDataPanel.widthProperty()); +// dateAxis.setStyle("-fx-background-color: ORANGE;"); + dateAxis.setForceZeroInRange(false); + holder.setTop(dateAxis); + return holder; } @@ -208,6 +215,8 @@ public class ScrollingDataPaneFX extends PamBorderPane { notifyScrollChange(); }); + timeScrollBar.setPrefHeight(20); + holder.setCenter(timeScrollBar); holder.setBottom(timeLabelPane); @@ -240,10 +249,13 @@ public class ScrollingDataPaneFX extends PamBorderPane { dataStreamPanels.get(i).scrollChanged(); } settingsStrip.scrollChanged(); + + updateDateAxis(); } Timeline timeline; long lastTime = 0; + /** * Notify all panels and the settings strip that the scroll bar moved - but have a timer to wait to not call too often. */ @@ -263,9 +275,19 @@ public class ScrollingDataPaneFX extends PamBorderPane { } lastTime=currentTime; + + updateDateAxis(); } + private void updateDateAxis() { + calcStartEndMillis(); + dateAxis.setUpperBound(screenEndMillis); + dateAxis.setLowerBound(screenStartMillis); + double[] ticks = dateAxis.recalculateTicks(); +// System.out.println("Ticks: " + (ticks[3]/1000/3600) + "hours"); + dateAxis.setTickUnit(ticks[3]); + } /** * Create the data graphs to go into the pane. @@ -274,7 +296,7 @@ public class ScrollingDataPaneFX extends PamBorderPane { public synchronized int createDataGraphs() { //clear the panes from list and split pane. dataStreamPanels.clear(); - dataPanePanes.getItems().clear(); + dataPanePanes.getChildren().clear(); //now create new set of data stream panes. ArrayList dataBlocks = dataMapControl.getMappedDataBlocks(); @@ -285,65 +307,18 @@ public class ScrollingDataPaneFX extends PamBorderPane { DataStreamPaneFX aStreamPanel; for (int i = 0; i < dataBlocks.size(); i++) { aStreamPanel = new DataStreamPaneFX(dataMapControl, this, dataBlocks.get(i)); - addCollapseButton(aStreamPanel); dataStreamPanels.add(aStreamPanel); + dataStreamPanels.get(i).setMinHeight(DATASTREAMPANE_HEIGHT); //now add to a split pane. //SplitPane.setResizableWithParent(aStreamPanel, true); - dataPanePanes.getItems().add(aStreamPanel); + dataPanePanes.getChildren().add(aStreamPanel); //dataPanePanes.setDividerPosition(0,1.0/dataBlocks.size()); } return dataBlocks.size(); } - /** - * Add a button to the top of a DataStreamPaneFX which allows the pane to collapse inside the split pane. - * @param datastreamPane- the DataStreamPaneFX to add the button to. - */ - private void addCollapseButton(DataStreamPaneFX datastreamPane){ - final CollapseButton collapseButton=new CollapseButton(); - collapseButton.getStyleClass().add("close-button-bottom"); - collapseButton.setOnAction((action)->{ - int index= dataStreamPanels.indexOf(datastreamPane); - if (index>=0){ - if (datastreamPane.getDataGraph().getHeight()<=collapseHeight) { - expandSPlitPane(datastreamPane, true); - } - else { - expandSPlitPane(datastreamPane, false); - } - } - }); - - datastreamPane.getTopPane().getChildren().add(collapseButton); - //set button on the center of the pane. - collapseButton.layoutXProperty().bind(datastreamPane.getTopPane().widthProperty().divide(2)); - - -// PamButton testButton=new PamButton("Test"); -// testButton.setOnAction( (action)->{ -// double[] dividersPos=dataPanePanes.getDividerPositions(); -// for (int i=0; i{ - collapseButton.setCollapseButtonGraphic(collapseButton, newVal.doubleValue()); - //make sure collapse flag is changed if the pane is dragged. - //System.out.println("datastreamPane: " + newVal.doubleValue() + " "+datastreamPane); - if (newVal.doubleValue()>collapseHeight+1){ - datastreamPane.setCollapsed(false); - } - else { - datastreamPane.setCollapsed(true); - } - }); - } - /*** * Get the number of panes which are expanded. * @return the number of expanded panes. @@ -356,113 +331,7 @@ public class ScrollingDataPaneFX extends PamBorderPane { } return nExpanded; } - - /** - * Expanding a split pane is a little involved. You have to resize all the other panes too. - */ - private void expandSPlitPane(DataStreamPaneFX datastreamPane, boolean expand){ - //System.out.println("---------------------"); - - //work out the number of expanded dividers - int nexpanded=getNExpandedPanes(); - if (nexpanded<=1 && !expand) { - //cannot collapse the last pane. - return; - } - - int[] formerWeights=dataPanePanes.getWeights(); - -// for (int i=0; icollapseHeight){ - //collapse graphic - graphic=imageDown; - } - else { - //expand graphic - graphic=imageUp; - } - if (collapseButton.getGraphic()!=graphic) collapseButton.setGraphic(graphic); - - } - } - /** * Called whenever new data sources are added. diff --git a/src/dataMap/layoutFX/SummaryPaneFX.java b/src/dataMap/layoutFX/SummaryPaneFX.java index 7751ba14..7476b81f 100644 --- a/src/dataMap/layoutFX/SummaryPaneFX.java +++ b/src/dataMap/layoutFX/SummaryPaneFX.java @@ -159,10 +159,6 @@ public class SummaryPaneFX extends PamBorderPane { long[] dataExtent; dataStorePane.getChildren().clear(); - - this.setPrefHeight(n*30+40); //set preferred height for hiding pane. - dataMapPaneFX.getTopHolder().setPrefHeight(n*30+40); - for (int i = 0; i < n; i++) { dataStorePane.add( dataNames[i], 0, i); dataStorePane.add( dataStarts[i] , 1, i); diff --git a/src/dataModelFX/DataModelConnectPane.java b/src/dataModelFX/DataModelConnectPane.java index 21883cb6..a7315723 100644 --- a/src/dataModelFX/DataModelConnectPane.java +++ b/src/dataModelFX/DataModelConnectPane.java @@ -174,7 +174,7 @@ public class DataModelConnectPane extends ConnectionPane { PamController pamController = PamController.getInstance(); ArrayList connectionNodes = getAllConnectionNodes(); - System.out.println("checkModulesAdded: Number of controlled units: " + pamController.getNumControlledUnits() ); + //System.out.println("checkModulesAdded: Number of controlled units: " + pamController.getNumControlledUnits() ); for (int i = 0; i <= pamController.getNumControlledUnits(); i++) { checkModuleAdded(pamController.getControlledUnit(i), connectionNodes); } @@ -190,7 +190,7 @@ public class DataModelConnectPane extends ConnectionPane { */ public boolean checkModuleAdded(PamControlledUnit pamControlledUnit, ArrayList connectionNodes) { - System.out.println("datamodelConnectPane: checkModuleAdded: " + pamControlledUnit + " No. nodes: " + connectionNodes.size()); + //System.out.println("datamodelConnectPane: checkModuleAdded: " + pamControlledUnit + " No. nodes: " + connectionNodes.size()); if (pamControlledUnit==null) return false; @@ -204,17 +204,17 @@ public class DataModelConnectPane extends ConnectionPane { ModuleConnectionNode moduleConnectionNode; for (int j = 0; j < connectionNodes.size(); j++) { moduleConnectionNode = (ModuleConnectionNode) connectionNodes.get(j); - System.out.println("Looking for node: " + moduleConnectionNode.getPamControlledUnit()); + //System.out.println("Looking for node: " + moduleConnectionNode.getPamControlledUnit()); if (moduleConnectionNode.getPamControlledUnit()!=null && moduleConnectionNode.getPamControlledUnit().equals(pamControlledUnit)) { - System.out.println("There is already a node for : " + moduleConnectionNode.getPamControlledUnit().getUnitName()); + //System.out.println("There is already a node for : " + moduleConnectionNode.getPamControlledUnit().getUnitName()); return true; } else if (moduleConnectionNode.getConnectionNodeParams()!=null && moduleConnectionNode.getConnectionNodeParams().unitName!=null && moduleConnectionNode.getConnectionNodeParams().unitName.equals(pamControlledUnit.getUnitName())){ //set the pamcontrolled unit reference within the connection node. node. moduleConnectionNode.setPamControlledUnit(pamControlledUnit); - System.out.println("There is already a node waiting for : " + moduleConnectionNode.getPamControlledUnit().getUnitName()); + //System.out.println("There is already a node waiting for : " + moduleConnectionNode.getPamControlledUnit().getUnitName()); if (PamController.getInstance().isInitializationComplete()) { //only to be used on drag and dropped nodes. @@ -224,7 +224,7 @@ public class DataModelConnectPane extends ConnectionPane { } } - System.out.println("Could not find the node: " + pamControlledUnit.getUnitName()); + //System.out.println("Could not find the node: " + pamControlledUnit.getUnitName()); //possibility 3 ModuleConnectionNode newNode; @@ -309,7 +309,7 @@ public class DataModelConnectPane extends ConnectionPane { * Check module connections. Attempts to make the GUI data model reflect the current pamDataModel. For example * use this function if source data is changed in external dialogs. */ - protected void dataModeltoPamModel(){ + public void dataModeltoPamModel(){ dataModelPaneFX.dataModeltoPamModel(); } diff --git a/src/dataModelFX/DataModelModulePane.java b/src/dataModelFX/DataModelModulePane.java index 5c6d4d62..bedd9763 100644 --- a/src/dataModelFX/DataModelModulePane.java +++ b/src/dataModelFX/DataModelModulePane.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import PamController.PamController; import PamModel.PamModuleInfo; -import atlantafx.base.theme.PrimerDark; import dataModelFX.ConnectionNodeParams.PAMConnectionNodeType; import dataModelFX.connectionNodes.ModuleIconFactory; import javafx.beans.property.ObjectProperty; @@ -13,7 +12,6 @@ import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.SnapshotParameters; -import javafx.scene.control.Button; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import javafx.scene.input.ClipboardContent; @@ -29,7 +27,6 @@ import pamViewFX.fxNodes.PamTilePane; import pamViewFX.fxNodes.PamTitledPane; import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.connectionPane.structures.ConnectionGroupBody; -import pamViewFX.fxNodes.connectionPane.structures.ConnectionStructure.ConnectionStructureType; import pamViewFX.fxStyles.PamStylesManagerFX; import pamViewFX.fxNodes.connectionPane.structures.ExtensionSocketStructure; @@ -75,7 +72,7 @@ public class DataModelModulePane extends PamBorderPane { private PamScrollPane createPane(){ moduleSelectPane=new PamScrollPane(); - moduleSelectPane.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + moduleSelectPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); moduleSelectPane.setPrefWidth(250); moduleSelectPane.getStyleClass().add("scroll-pane-dark"); diff --git a/src/dataModelFX/DataModelPaneFX.java b/src/dataModelFX/DataModelPaneFX.java index 91b4aa20..74635850 100644 --- a/src/dataModelFX/DataModelPaneFX.java +++ b/src/dataModelFX/DataModelPaneFX.java @@ -312,8 +312,8 @@ public class DataModelPaneFX extends PamBorderPane { * reflect the current pamDataModel. For example use this function if source * data is changed in external dialogs. */ - protected void dataModeltoPamModel() { - // System.out.println("************DATAMODELTOPAMMODEL*************"); + public void dataModeltoPamModel() { +// System.out.println("************DATAMODELTOPAMMODEL*************"); if (disableNotification) return; @@ -328,8 +328,8 @@ public class DataModelPaneFX extends PamBorderPane { childNode = (ModuleConnectionNode) dataModelPane.getConnectionNodes().get(i); if (childNode.getPamControlledUnit() == null) continue; - // System.out.println("Children of " + ((ModuleConnectionNode) - // dataModelPane.getConnectionNodes().get(i)).getPamControlledUnit().getUnitType()); + //System.out.println("Children of " + ((ModuleConnectionNode) +// dataModelPane.getConnectionNodes().get(i)).getPamControlledUnit().getUnitType()); checkParentChildConnections(childNode, true); } enableConectionListerner(true); @@ -356,7 +356,7 @@ public class DataModelPaneFX extends PamBorderPane { * modules with dependencies are added. */ protected void pamModeltoDataModel(boolean disconnect) { - // System.out.println("************PAMMODELTODATAMODEL*************"); +// System.out.println("************PAMMODELTODATAMODEL*************"); if (disableNotification) return; disableNotification = true; @@ -367,10 +367,13 @@ public class DataModelPaneFX extends PamBorderPane { */ for (int i = 0; i < this.dataModelPane.getConnectionNodes().size(); i++) { childNode = (ModuleConnectionNode) dataModelPane.getConnectionNodes().get(i); +// System.out.println("Node " + i); + if (childNode.getPamControlledUnit() == null) continue; - // System.out.println("Children of " + ((ModuleConnectionNode) - // dataModelPane.getConnectionNodes().get(i)).getPamControlledUnit().getUnitType()); +// System.out.println("Children of " + ((ModuleConnectionNode) +// dataModelPane.getConnectionNodes().get(i)).getPamControlledUnit().getUnitType()); + checkModuleConnection(childNode); checkParentChildConnections(childNode, !disconnect); } @@ -396,7 +399,7 @@ public class DataModelPaneFX extends PamBorderPane { * error. */ private boolean checkModuleConnection(ModuleConnectionNode childNode) { - + // find all parent nodes. ArrayList parentNodesS = dataModelPane.getParentConnectionNodes(childNode); @@ -413,13 +416,13 @@ public class DataModelPaneFX extends PamBorderPane { } else { - // System.out.println(" CHILD NODE: " - // +childNode.getPamControlledUnit().getUnitName()); - // for (int i=0; i{ - if (this.pamControlledUnit.getPamModuleInfo().canRemove()){ + if (this.pamControlledUnit==null || this.pamControlledUnit.getPamModuleInfo()== null || this.pamControlledUnit.getPamModuleInfo().canRemove()){ connectionPane.removeModuleNode(this); } }); @@ -422,7 +424,7 @@ public class ModuleConnectionNode extends StandardConnectionNode implements PAMC this.holderPane = HolderPane; this.top= top; - this.getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); + this.getStylesheets().addAll(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); showButton=new PamButton(); showButton.setMaxWidth(Double.MAX_VALUE); @@ -620,7 +622,7 @@ public class ModuleConnectionNode extends StandardConnectionNode implements PAMC //add tool tip if (pamControlledUnit!=null){ - //sometimes seems to cause an issue woith dialogs disappearing. + //sometimes seems to cause an issue with dialogs disappearing. // Tooltip tp = new Tooltip(pamControlledUnit.getUnitType()); // tp.getStyleClass().removeAll(tp.getStyleClass()); // Tooltip.install(this, tp); @@ -639,16 +641,26 @@ public class ModuleConnectionNode extends StandardConnectionNode implements PAMC } + Tooltip tooltip = ModuleToolTipFactory.getToolTip(pamControlledUnit); + + if (tooltip!=null) { + Tooltip. install(this, tooltip); + } + } - Node icon = ModuleIconFactory.getInstance(). - getModuleNode(pamControlledUnit.getPamModuleInfo().getClassName()); - if (pamControlledUnit.getPamModuleInfo()!=null && icon!=null){ - StackPane iconPane = new StackPane(icon); - iconPane.setPrefSize(DataModelStyle.iconSize, DataModelStyle.iconSize); - iconPane.setAlignment(Pos.CENTER); - StackPane.setAlignment(iconPane, Pos.CENTER); //make sure the image or node is centered. - this.getConnectionNodeBody().getChildren().add(iconPane); + System.out.println("Add module: " + pamControlledUnit.getUnitName() + " " + pamControlledUnit.getPamModuleInfo()); + + if (pamControlledUnit.getPamModuleInfo()!=null) { + Node icon = ModuleIconFactory.getInstance(). + getModuleNode(pamControlledUnit.getPamModuleInfo().getClassName()); + if (icon!=null){ + StackPane iconPane = new StackPane(icon); + iconPane.setPrefSize(DataModelStyle.iconSize, DataModelStyle.iconSize); + iconPane.setAlignment(Pos.CENTER); + StackPane.setAlignment(iconPane, Pos.CENTER); //make sure the image or node is centered. + this.getConnectionNodeBody().getChildren().add(iconPane); + } } @@ -965,7 +977,9 @@ public class ModuleConnectionNode extends StandardConnectionNode implements PAMC } - + public DataModelConnectPane getDataModelConnectionPane() { + return this.connectionPane; + } } diff --git a/src/dataModelFX/connectionNodes/ModuleIconFactory.java b/src/dataModelFX/connectionNodes/ModuleIconFactory.java index 8f4ddaab..4056f730 100644 --- a/src/dataModelFX/connectionNodes/ModuleIconFactory.java +++ b/src/dataModelFX/connectionNodes/ModuleIconFactory.java @@ -2,6 +2,7 @@ package dataModelFX.connectionNodes; import dataModelFX.DataModelStyle; import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; @@ -34,7 +35,8 @@ public class ModuleIconFactory { */ public enum ModuleIcon { DATAMAP, NMEA, GPS, MAP, SOUND_AQ, SOUND_OUTPUT, FFT, FILTER, CLICK, CLICK_TRAIN, RECORDER, WHISTLE_MOAN, - NOISE_BAND, NOISE_FILT, DATABASE, BINARY, TIME_DISPLAY, DETECTION_DISPLAY, ARRAY, DEEP_LEARNING + NOISE_BAND, NOISE_FILT, DATABASE, BINARY, TIME_DISPLAY, DETECTION_DISPLAY, ARRAY, DEEP_LEARNING, MATCHED_CLICK_CLASSIFIER, + DECIMATOR, CPOD, TETHYS } /** @@ -43,77 +45,129 @@ public class ModuleIconFactory { * @return the icon for the controlled unit */ public Node getModuleNode(ModuleIcon icon){ + long time1 = System.currentTimeMillis(); + Node iconNode =null; + switch (icon){ case ARRAY: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/array_manager.png"))); +// return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/array_manager.png"))); + iconNode = getSVGIcon("/Resources/modules/Array Icon2.svg",Color.BLACK, 3); + break; case BINARY: // return PamGlyphDude.createModuleGlyph(OctIcon.FILE_BINARY); - return PamGlyphDude.createModuleIcon("mdi2f-file-star-outline"); + iconNode = PamGlyphDude.createModuleIcon("mdi2f-file-star-outline"); + break; case CLICK: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/click.png"))); +// return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/click.png"))); + iconNode = getSVGIcon("/Resources/modules/Click Detector Icon.svg", Color.BLACK, 2); + break; case CLICK_TRAIN: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/clicktrain.png"))); +// return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/clicktrain.png"))); + iconNode = getSVGIcon("/Resources/modules/clicktrain.svg",Color.BLACK, 2); + break; case DATABASE: // return PamGlyphDude.createModuleGlyph(FontAwesomeIcon.DATABASE); - return PamGlyphDude.createModuleIcon("mdi2d-database"); + iconNode = PamGlyphDude.createModuleIcon("mdi2d-database"); + break; case DATAMAP: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/dataMap.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/dataMap.png"))); + break; case FFT: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/fft.png"))); +// return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/fft.png"))); + iconNode = getSVGIcon("/Resources/modules/fft.svg",Color.BLACK, 2); + break; case FILTER: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filters.png"))); +// return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filters.png"))); + iconNode = getSVGIcon("/Resources/modules/filters.svg",Color.BLACK, 2); + break; case GPS: // return PamGlyphDude.createModuleGlyph(MaterialIcon.GPS_FIXED); - return PamGlyphDude.createModuleIcon("mdi2c-crosshairs-gps"); + iconNode = PamGlyphDude.createModuleIcon("mdi2c-crosshairs-gps"); + break; case MAP: // return PamGlyphDude.createModuleGlyph(MaterialIcon.MAP); - return PamGlyphDude.createModuleIcon("mdi2m-map"); + iconNode = PamGlyphDude.createModuleIcon("mdi2m-map"); + break; case NMEA: - return createNMEASymbol(); + iconNode = createNMEASymbol(); case NOISE_BAND: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filterdNoiseMeasurementBank.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filterdNoiseMeasurementBank.png"))); + break; case NOISE_FILT: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filterdNoiseMeasurement.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/filterdNoiseMeasurement.png"))); + break; case RECORDER: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/recorder.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/recorder.png"))); + break; case SOUND_AQ: - return getSVGIcon("/Resources/modules/noun_Soundwave_1786340.svg"); + iconNode = getSVGIcon("/Resources/modules/noun_Soundwave_1786340.svg"); + break; + case MATCHED_CLICK_CLASSIFIER: + iconNode = getSVGIcon("/Resources/modules/matched_click_classifier.svg",Color.BLACK, 2); + break; //return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/aquisition.png"))); case SOUND_OUTPUT: // return PamGlyphDude.createModuleGlyph(MaterialDesignIcon.HEADPHONES); - return PamGlyphDude.createModuleIcon("mdi2h-headphones"); - //return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/playback.png"))); + iconNode = PamGlyphDude.createModuleIcon("mdi2h-headphones"); + break; +//return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/playback.png"))); case WHISTLE_MOAN: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/whistles.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/whistles.png"))); + break; case TIME_DISPLAY: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/timeDisplay.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/timeDisplay.png"))); + break; case DETECTION_DISPLAY: - return new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/detectionDisplay.png"))); + iconNode = new ImageView(new Image(getClass().getResourceAsStream("/Resources/modules/detectionDisplay.png"))); + break; case DEEP_LEARNING: - //System.out.println("------GET THE SVG ICON FOR DEEP LEARNING--------"); - return getSVGIcon("/Resources/modules/noun_Deep Learning_2486374.svg"); + iconNode = getSVGIcon("/Resources/modules/noun_Deep Learning_2486374.svg"); + break; + case DECIMATOR: + iconNode = getSVGIcon("/Resources/modules/decimator.svg"); + break; + case CPOD: + iconNode = new Label("CPOD"); //TEMP + break; + case TETHYS: + iconNode = PamGlyphDude.createModuleIcon("file-codemeta"); + System.out.println("Get module Tethys " + iconNode); + break; default: break; - } - return null; + + long time2 = System.currentTimeMillis(); + return iconNode; }; - + /** * Get an SVG icon. * @param resourcePath - the path from the src folder * @return a node for the SVG icon. */ private Node getSVGIcon(String resourcePath) { + return getSVGIcon( resourcePath, Color.BLACK, 1); + } + + + + /** + * Get an SVG icon. + * @param resourcePath - the path from the src folder + * @return a node for the SVG icon. + */ + private Node getSVGIcon(String resourcePath, Color colour, double lineWidth) { try { PamSVGIcon iconMaker= new PamSVGIcon(); - PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), Color.BLACK); - svgsprite.getSpriteNode().setStyle("-fx-text-color: black"); - svgsprite.getSpriteNode().setStyle("-fx-fill: black"); + PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), colour, lineWidth); +// svgsprite.getSpriteNode().setStyle("-fx-text-color: black"); +// svgsprite.getSpriteNode().setStyle("-fx-fill: black"); svgsprite.setFitHeight(DataModelStyle.iconSize-10); svgsprite.setFitWidth(DataModelStyle.iconSize-10); + return svgsprite.getSpriteNode(); } catch (Exception e) { @@ -162,7 +216,6 @@ public class ModuleIconFactory { * @return the module icon enum */ public ModuleIcon getModuleIcon(String className) { - //System.out.println("CLASS NAME: " + className); ModuleIcon icon = null; switch (className) { case "Acquisition.AcquisitionControl": @@ -196,6 +249,7 @@ public class ModuleIconFactory { icon=ModuleIcon.TIME_DISPLAY; break; case "detectionPlotFX.DetectionDisplayControl": + case "detectionPlotFX.DetectionDisplayControl2": icon=ModuleIcon.DETECTION_DISPLAY; break; case "dataMap.DataMapControl": @@ -207,7 +261,21 @@ public class ModuleIconFactory { case "rawDeepLearningClassifier.DLControl": icon=ModuleIcon.DEEP_LEARNING; break; + case "matchedTemplateClassifer.MTClassifierControl": + icon=ModuleIcon.MATCHED_CLICK_CLASSIFIER; + break; + case "decimator.DecimatorControl": + icon=ModuleIcon.DECIMATOR; + break; + case "cpod.CPODControl2": + icon=ModuleIcon.CPOD; + break; + case "Meta Data": + icon = ModuleIcon.TETHYS; + break; } + System.out.println("Get module icon: " + className + " icon " + icon); + return icon; } diff --git a/src/dataModelFX/connectionNodes/ModuleProcessDiagram.java b/src/dataModelFX/connectionNodes/ModuleProcessDiagram.java index 7134e542..7369cb76 100644 --- a/src/dataModelFX/connectionNodes/ModuleProcessDiagram.java +++ b/src/dataModelFX/connectionNodes/ModuleProcessDiagram.java @@ -81,7 +81,7 @@ public class ModuleProcessDiagram extends PamBorderPane { private void createControlDataModel(){ this.setMinWidth(100); this.setPrefWidth(USE_COMPUTED_SIZE); - //this.setStyle("-fx-background-color: -fx-darkbackground"); + this.setStyle("-fx-background-color: -fx-darkbackground"); this.populateDataModel(); } diff --git a/src/dataModelFX/connectionNodes/ModuleToolTipFactory.java b/src/dataModelFX/connectionNodes/ModuleToolTipFactory.java new file mode 100644 index 00000000..43185a4e --- /dev/null +++ b/src/dataModelFX/connectionNodes/ModuleToolTipFactory.java @@ -0,0 +1,16 @@ +package dataModelFX.connectionNodes; + +import PamController.PamControlledUnit; +import javafx.scene.control.Tooltip; + +public class ModuleToolTipFactory { + + public static Tooltip getToolTip(PamControlledUnit pamControlledUnit) { + + if (pamControlledUnit.getPamModuleInfo()==null) return null; + + return new Tooltip(pamControlledUnit.getPamModuleInfo().getToolTipText()); + + } + +} 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/dataPlotsFX/TDControl.java b/src/dataPlotsFX/TDControl.java index 543301e3..8125955f 100644 --- a/src/dataPlotsFX/TDControl.java +++ b/src/dataPlotsFX/TDControl.java @@ -13,6 +13,7 @@ import dataPlots.TDParameters; import dataPlotsFX.data.TDDataInfoFX; import dataPlotsFX.layout.TDDisplayFX; import dataPlotsFX.layout.TDGraphFX; +import detectiongrouplocaliser.DetectionGroupSummary; /** * The main class for the TDDisplay. @@ -222,5 +223,13 @@ public abstract class TDControl implements PamSettings { this.uniqueDisplayName = uniqueName; } + /** + * A new detection group has been selected. + * @param detectionGroup - the detection group that has been selected + */ + public void newSelectedDetectionGroup(DetectionGroupSummary detectionGroup, TDGraphFX tdGraph) { + + } + } diff --git a/src/dataPlotsFX/TDControlAWT.java b/src/dataPlotsFX/TDControlAWT.java index f4f4f8ce..e79e87b9 100644 --- a/src/dataPlotsFX/TDControlAWT.java +++ b/src/dataPlotsFX/TDControlAWT.java @@ -85,7 +85,7 @@ public class TDControlAWT extends TDControl implements UserDisplayComponent { Group root= new Group(); Scene scene = new Scene(root, Color.GRAY); scene.getStylesheets().clear(); - scene.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); + scene.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); setTDDisplay(new TDDisplayFX(tdControlfx)); root.getChildren().add(getTDDisplay()); diff --git a/src/dataPlotsFX/TDControlFX.java b/src/dataPlotsFX/TDControlFX.java index 089edd93..493b9fe0 100644 --- a/src/dataPlotsFX/TDControlFX.java +++ b/src/dataPlotsFX/TDControlFX.java @@ -6,12 +6,15 @@ import java.util.ArrayList; import javafx.application.Platform; import javafx.scene.layout.Region; import pamViewFX.fxNodes.internalNode.PamInternalPane; +import userDisplayFX.UserDisplayControlFX; import userDisplayFX.UserDisplayNodeFX; import userDisplayFX.UserDisplayNodeParams; import dataPlotsFX.data.TDDataInfoFX; import dataPlotsFX.data.TDDataProviderFX; import dataPlotsFX.data.TDDataProviderRegisterFX; import dataPlotsFX.layout.TDDisplayFX; +import dataPlotsFX.layout.TDGraphFX; +import detectiongrouplocaliser.DetectionGroupSummary; import PamController.PamController; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; @@ -20,6 +23,7 @@ import PamguardMVC.PamObserverAdapter; import PamguardMVC.PamRawDataBlock; /** + * * The controller for the display if the main PAMGuard GUI is in JavaFX mode. * * @author Jamie Macaulay @@ -110,17 +114,19 @@ 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"); +// System.out.println("TDControldFX: parent datablock null"); return dataBlocks; } + for (int i=0; i dataBlocks){ -// System.out.println("Set parent datablocks: " + dataBlocks.size()); allowProcessNotify=false; while (getUserDisplayProcess().getNumMuiltiplexDataBlocks()>0){ @@ -80,6 +92,7 @@ public class TDDisplayController extends UserDisplayControlFX { //disables notifications. boolean allowProcessNotify=true; + @Override public void notifyModelChanged(int type){ // System.out.println("---------------------------------" ); @@ -145,6 +158,14 @@ public class TDDisplayController extends UserDisplayControlFX { System.out.println(tdPlotProviders.get(i).getDataBlock().getUnitClass()); } } + + /** + * Get the datablock for selected data units from the display. + * @return datablock for selected data units. + */ + public PamDataBlock getDisplayDataBlock() { + return selectedDataUnits; + } } diff --git a/src/dataPlotsFX/TDManagedSymbolChooserFX.java b/src/dataPlotsFX/TDManagedSymbolChooserFX.java index 5dce48fc..b522f90e 100644 --- a/src/dataPlotsFX/TDManagedSymbolChooserFX.java +++ b/src/dataPlotsFX/TDManagedSymbolChooserFX.java @@ -8,7 +8,7 @@ import dataPlotsFX.data.TDDataInfoFX; import pamViewFX.fxNodes.PamSymbolFX; /** - * Default symbol chooser for the display. + * Default symbol chooser for the display. This based on the data blocks symbol chooser. * * @author Jamie Maaulay * @@ -55,7 +55,6 @@ public class TDManagedSymbolChooserFX implements TDSymbolChooserFX{ } else if (type==TDSymbolChooserFX.HIGHLIGHT_SYMBOL) { symbol=ClickSymbolChooserFX.highLightClick; - } return symbol; } diff --git a/src/dataPlotsFX/clickPlotFX/ClickPlotInfoFX.java b/src/dataPlotsFX/clickPlotFX/ClickPlotInfoFX.java index 13a5216c..59924c42 100644 --- a/src/dataPlotsFX/clickPlotFX/ClickPlotInfoFX.java +++ b/src/dataPlotsFX/clickPlotFX/ClickPlotInfoFX.java @@ -517,7 +517,7 @@ public class ClickPlotInfoFX extends TDDataInfoFX { if (pamDataUnit.getSuperDetectionsCount()==0) return null; - ListIterator iterator = this.getPamDataBlock().getListIterator(PamDataBlock.ITERATOR_END); + ListIterator iterator = this.getDataBlock().getListIterator(PamDataBlock.ITERATOR_END); PamDataUnit testDataUnit; while (iterator.hasPrevious()) { diff --git a/src/dataPlotsFX/data/TDDataInfoFX.java b/src/dataPlotsFX/data/TDDataInfoFX.java index ca279fd9..c070ed31 100644 --- a/src/dataPlotsFX/data/TDDataInfoFX.java +++ b/src/dataPlotsFX/data/TDDataInfoFX.java @@ -12,6 +12,7 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.shape.Polygon; import PamController.PamController; import PamUtils.Coordinate3d; +import PamUtils.PamCalendar; import PamUtils.PamUtils; import PamView.HoverData; import PamView.GeneralProjector.ParameterType; @@ -373,8 +374,8 @@ public abstract class TDDataInfoFX { */ public void drawAllDataUnits(int plotNumber, GraphicsContext g, double scrollStart, TDProjectorFX tdProjector) { -// System.out.println("Max double value: " + PamCalendar.formatDateTime((long) Double.MAX_EXPONENT)); -// System.out.println("Max long value: " + PamCalendar.formatDateTime((long) Long.MAX_VALUE)); +// System.out.println("Max double value: " + this.getDataName() + PamCalendar.formatDateTime((long) Double.MAX_EXPONENT)); +// System.out.println("Max long value: " + this.getDataName() + PamCalendar.formatDateTime((long) Long.MAX_VALUE)); PamDataUnit dataUnit = null; @@ -391,7 +392,10 @@ public abstract class TDDataInfoFX { // scrollStart = PamCalendar.getTimeInMillis(); //work out start and stop times long loopEnd = (long) (scrollStart + (tdProjector.getVisibleTime() * 1.5)); - long loopStart = (long) (scrollStart - (tdProjector.getVisibleTime() * .5)); + long loopStart = (long) (scrollStart - (tdProjector.getVisibleTime() * 1.5)); + +// System.out.println("Loop start: " + this.getDataName() + PamCalendar.formatDateTime((long) loopStart)); +// System.out.println("Loop end: " + this.getDataName() + PamCalendar.formatDateTime((long) loopEnd)); //find a number close to the index start, ListIterator it = getUnitIterator( loopStart, plotNumber); @@ -402,6 +406,7 @@ public abstract class TDDataInfoFX { dataUnit = it.next(); count++; if (dataUnit!=null && shouldDraw(plotNumber, dataUnit)) { + if (dataUnit.getEndTimeInMilliseconds() < loopStart) { continue; } @@ -409,11 +414,13 @@ public abstract class TDDataInfoFX { break; } + drawCalls++; drawDataUnit(plotNumber, dataUnit, g, scrollStart, tdProjector ,TDSymbolChooserFX.NORMAL_SYMBOL); } } +// System.out.println("Found: " + drawCalls + " " + this.getDataName() + " to draw"); lastUnitDrawn(g, scrollStart, tdProjector, plotNumber); } //System.out.println("Total data units: " + count+ " draw calls: " +drawCalls ); @@ -645,8 +652,8 @@ public abstract class TDDataInfoFX { drawCalls++; if ((symbolchoser.getDrawTypes(pamDataUnit) & TDSymbolChooserFX.DRAW_LINES) != 0) { //WARNING<- the getPreviousDataUnit function is currently very slow and caused serious issue - //in display speed. Onyl use draw lines with super detections if data units are very sparse. - // For example not suitbale for the the click detector. + //in display speed. Only use draw lines with super detections if data units are very sparse. + // For example not suitable for the the click detector. Point2D lastDrawPoint=getPreviousDataUnit(tdProjector, pamDataUnit); if (lastDrawPoint!= null) g.setFill(symbolchoser.getPamSymbol(pamDataUnit,type).getLineColor()); @@ -731,13 +738,6 @@ public abstract class TDDataInfoFX { return false; } - /** - * @return the pamDataBlock - */ - protected PamDataBlock getPamDataBlock() { - return pamDataBlock; - } - /** * @return the tdDataProvider */ diff --git a/src/dataPlotsFX/data/TDDataProviderRegisterFX.java b/src/dataPlotsFX/data/TDDataProviderRegisterFX.java index da0a3beb..8901845e 100644 --- a/src/dataPlotsFX/data/TDDataProviderRegisterFX.java +++ b/src/dataPlotsFX/data/TDDataProviderRegisterFX.java @@ -80,6 +80,7 @@ public class TDDataProviderRegisterFX { */ synchronized public TDDataProviderFX findDataProvider(PamDataBlock pamDataBlock) { for (TDDataProviderFX aProvider:dataProviders) { +// System.out.println("Data providers: " + aProvider.getName() + " | " + aProvider.getDataBlock() + " | " +pamDataBlock); if (aProvider.getDataBlock()==pamDataBlock) { return aProvider; } diff --git a/src/dataPlotsFX/data/generic/GenericDataPlotInfo.java b/src/dataPlotsFX/data/generic/GenericDataPlotInfo.java index f479b86c..eddf4d7b 100644 --- a/src/dataPlotsFX/data/generic/GenericDataPlotInfo.java +++ b/src/dataPlotsFX/data/generic/GenericDataPlotInfo.java @@ -99,6 +99,7 @@ public class GenericDataPlotInfo extends TDDataInfoFX { } else { return super.drawDataUnit(plotNumber, pamDataUnit, g, scrollStart, tdProjector, type); + } } @@ -266,7 +267,7 @@ public class GenericDataPlotInfo extends TDDataInfoFX { return managedSymbolChooser; } - private TDSymbolChooserFX createSymbolChooser() { + public TDSymbolChooserFX createSymbolChooser() { PamSymbolManager symbolManager = getDataBlock().getPamSymbolManager(); if (symbolManager == null) { return null; @@ -293,7 +294,7 @@ public class GenericDataPlotInfo extends TDDataInfoFX { * do some which may be associated with other annotations ? */ protected void updateAvailability() { - LocalisationInfo locInfo = getPamDataBlock().getLocalisationContents(); + LocalisationInfo locInfo = getDataBlock().getLocalisationContents(); bearingScaleInfo.setAvailable(locInfo.hasLocContent(LocContents.HAS_BEARING)); slantScaleInfo.setAvailable(locInfo.hasLocContent(LocContents.HAS_BEARING)); } diff --git a/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java b/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java index affeb7b8..98b39929 100644 --- a/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java +++ b/src/dataPlotsFX/data/generic/GenericLinePlotInfo.java @@ -2,6 +2,7 @@ package dataPlotsFX.data.generic; import PamController.PamController; import PamUtils.Coordinate3d; +import PamUtils.PamCalendar; import PamView.GeneralProjector; import PamView.HoverData; import PamView.symbol.PamSymbolChooser; @@ -23,8 +24,9 @@ import rawDeepLearningClassifier.dataPlotFX.LineInfo; /** - * A Data Info which plots 1D line data i.e. usually used to plot continuous 1D data e.g. deep learning predictions, - * Ishmael data, click trigger data. + * A Data Info which plots 1D line data i.e. usually used to plot continuous 1D + * data e.g. deep learning predictions, Ishmael data, click trigger data. + * * @author Jamie Macaulay * */ @@ -46,21 +48,18 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { } - - /* (non-Javadoc) * @see dataPlotsFX.data.TDDataInfoFX#drawDataUnit(int, PamguardMVC.PamDataUnit, javafx.scene.canvas.GraphicsContext, long, dataPlotsFX.projector.TDProjectorFX, int) */ @Override public Polygon drawDataUnit(int plotNumber, PamDataUnit pamDataUnit, GraphicsContext g, double scrollStart, TDProjectorFX tdProjector, int type) { - return drawPredicition(plotNumber, pamDataUnit, g, scrollStart, tdProjector, type); - } + /** - * Get the line data. Each double[] is a seperate line with N evenly spaced data points. + * Get the line data. Each double[] is a separate line with N evenly spaced data points. * @param pamDataUnit - the pam data unit containing the data. * @return the line data. */ @@ -87,25 +86,22 @@ 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]; } //use the center of the window for plotting - double timeMillis = pamDataUnit.getTimeMilliseconds(); + double timeMillis = pamDataUnit.getTimeMilliseconds()+pamDataUnit.getDurationInMilliseconds()/2; double tC=tdProjector.getTimePix(timeMillis-scrollStart); - //draws lines so tc should be some slop in pixels. if (tC < -1000 || tC>tdProjector.getWidth()+1000) { + //System.out.println("Line is outside display " + tC); return null; } - //TODO -must sort out wrap - //dlControl.getDLParams().sampleHop; - double dataPixel; Coordinate3d c; Color color; @@ -127,16 +123,13 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { //brighten the colour up. //color = Color.color(color.getRed()*0.8, color.getGreen()*0.8, color.getBlue()*0.8); - - //System.out.println("TDDataInfoFX: tc: "+tC+ " dataUnitTime: "+PamCalendar.formatTime(timeMillis)+" scrollStart: " - //+PamCalendar.formatTime((long) scrollStart)+" (timeMillis-scrollStart)/1000. "+((timeMillis-scrollStart)/1000.)); - - +// System.out.println("TDDataInfoFX: tc: "+tC+ " dataUnitTime: "+PamCalendar.formatTime((long) timeMillis)+" scrollStart: " +// +PamCalendar.formatTime((long) scrollStart)+" (timeMillis-scrollStart)/1000. "+((timeMillis-scrollStart)/1000.)); + c = tdProjector.getCoord3d(timeMillis, detData[i][j], 0); dataPixel = tdProjector.getYPix(detData[i][j]); - if (lastUnits[chan][i]==null) { lastUnits[chan][i] = new Point2D(tC, dataPixel); g.fillOval(tC, dataPixel, 5,5); @@ -146,7 +139,6 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { if (tC>lastUnits[chan][i].getX() && (!this.getTDGraph().isWrap() || (tC=0 && lastUnits[chan][i].getX()0)) { //in wrap mode we can get some weird effects with tC co-ordintates. Still have not quite cracked this... - // if (Math.abs(tC - lastUnits[chan][i].getX())>100) { // System.out.println("tC: " + tC + " lastUnits[i].getX(): " + lastUnits[chan][i].getX() // + " " + tdProjector. getTimeAxis().getPosition((timeMillis-scrollStart)/1000.) + " " + tdProjector.getWidth()); @@ -154,12 +146,9 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { g.strokeLine(tC, dataPixel, lastUnits[chan][i].getX(), lastUnits[chan][i].getY()); } lastUnits[chan][i] = new Point2D(tC, dataPixel); - } tdProjector.addHoverData(new HoverData(c , pamDataUnit, 0, plotNumber)); - } - //getSymbolChooser().getPamSymbol(pamDataUnit,type).draw(g, new Point2D(tC, dataPixel)); } } @@ -191,7 +180,8 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { * @return the color for that prediciton */ public abstract LineInfo getColor(int i); - + + @Override public Double getDataValue(PamDataUnit pamDataUnit) { //this is not used because we have overridden the super drawing class. @@ -205,15 +195,13 @@ public abstract class GenericLinePlotInfo extends TDDataInfoFX { * @param changeType - notification flag. */ public void notifyChange(int changeType) { - //System.out.println("Prediction NOTIFYMODELCHANGED: "); +// System.out.println("Prediction NOTIFYMODELCHANGED: " + changeType); switch (changeType) { case PamController.CHANGED_PROCESS_SETTINGS: lastUnits = new Point2D[PamConstants.MAX_CHANNELS][]; -; break; case PamController.RUN_NORMAL: lastUnits = new Point2D[PamConstants.MAX_CHANNELS][]; - break; case PamController.PAM_STOPPING: lastUnits = new Point2D[PamConstants.MAX_CHANNELS][]; diff --git a/src/dataPlotsFX/data/generic/GenericSettingsPane.java b/src/dataPlotsFX/data/generic/GenericSettingsPane.java index ea24fb3d..4947d7d9 100644 --- a/src/dataPlotsFX/data/generic/GenericSettingsPane.java +++ b/src/dataPlotsFX/data/generic/GenericSettingsPane.java @@ -33,7 +33,7 @@ public class GenericSettingsPane extends PamBorderPane implements TDSettingsPane /* * The raw clip info. */ - private TDDataInfoFX rawClipDataInfo; + private TDDataInfoFX tdDataInfoFX; /** * The icon for the pane. @@ -52,8 +52,8 @@ public class GenericSettingsPane extends PamBorderPane implements TDSettingsPane /** * The clip plot pane. */ - public GenericSettingsPane(TDDataInfoFX rawClipDataInfo){ - this.rawClipDataInfo = rawClipDataInfo; + public GenericSettingsPane(TDDataInfoFX tdDataInfoFX){ + this.tdDataInfoFX = tdDataInfoFX; createPane(); this.setPrefWidth(PREF_WIDTH); setParams(); @@ -81,7 +81,7 @@ public class GenericSettingsPane extends PamBorderPane implements TDSettingsPane private void newSettings(long milliswait) { getParams(); - this.rawClipDataInfo.getTDGraph().repaint(milliswait); + this.tdDataInfoFX.getTDGraph().repaint(milliswait); } @@ -112,10 +112,10 @@ public class GenericSettingsPane extends PamBorderPane implements TDSettingsPane */ private StandardSymbolOptionsPane createSymbolOptionsPane(){ - PamSymbolManager pamSymbolManager= rawClipDataInfo.getDataBlock().getPamSymbolManager(); + PamSymbolManager pamSymbolManager= tdDataInfoFX.getDataBlock().getPamSymbolManager(); - symbolOptionsPane= pamSymbolManager.getFXOptionsPane(rawClipDataInfo.getTDGraph().getUniqueName(), - rawClipDataInfo.getTDGraph().getGraphProjector()); + symbolOptionsPane= pamSymbolManager.getFXOptionsPane(tdDataInfoFX.getTDGraph().getUniqueName(), + tdDataInfoFX.getTDGraph().getGraphProjector()); //create a new settings listener symbolOptionsPane.addSettingsListener(()->{ diff --git a/src/dataPlotsFX/layout/TDControlPaneFX.java b/src/dataPlotsFX/layout/TDControlPaneFX.java index b9340535..bd5fd94d 100644 --- a/src/dataPlotsFX/layout/TDControlPaneFX.java +++ b/src/dataPlotsFX/layout/TDControlPaneFX.java @@ -110,7 +110,7 @@ public class TDControlPaneFX extends Pane { this.tdDisplay=tdMainDsiplay; //CSS styling- pane has the standard PAMGUARD settings pane styling. - this.getStylesheets().add(tdMainDsiplay.getCSSSettingsResource()); + this.getStylesheets().addAll(tdMainDsiplay.getCSSSettingsResource()); this.getStyleClass().add("pane"); PamBorderPane mainPane=new PamBorderPane(); diff --git a/src/dataPlotsFX/layout/TDDataSelPaneFX.java b/src/dataPlotsFX/layout/TDDataSelPaneFX.java index 2b1de7d7..8ba6609c 100644 --- a/src/dataPlotsFX/layout/TDDataSelPaneFX.java +++ b/src/dataPlotsFX/layout/TDDataSelPaneFX.java @@ -531,7 +531,7 @@ public class TDDataSelPaneFX extends PamVBox { //makes the arrow black ((Parent) popOver.getSkin().getNode()).getStylesheets() - .add(tdGraph.getTDDisplay().getCSSSettingsResource()); + .addAll(tdGraph.getTDDisplay().getCSSSettingsResource()); // show the graph add and remove menu // this.showingProperty().addListener(new ShowTDGraphMenu(this)); diff --git a/src/dataPlotsFX/layout/TDDisplayFX.java b/src/dataPlotsFX/layout/TDDisplayFX.java index 9c6fc66c..6cc2f259 100644 --- a/src/dataPlotsFX/layout/TDDisplayFX.java +++ b/src/dataPlotsFX/layout/TDDisplayFX.java @@ -242,7 +242,7 @@ public class TDDisplayFX extends PamBorderPane { hidingControlPane=new HidingPane(Side.TOP, controlPane, this, false ); hidingControlPane.showHidePane(tdParametersFX.showControl); - hidingControlPane.getStylesheets().add(this.getCSSSettingsResource()); //style as a settings pane. + hidingControlPane.getStylesheets().addAll(this.getCSSSettingsResource()); //style as a settings pane. hidingControlPane.showingProperty().addListener((valProp, oldVal, newVal)->{ //set correct showing property. tdParametersFX.showControl=newVal; @@ -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); @@ -615,9 +616,10 @@ public class TDDisplayFX extends PamBorderPane { * @param milliSeconds - the master clock position. */ public void scrollDisplayEnd(long milliSeconds) { + - if (!isViewer() && milliSeconds <= lastUpdate && milliSeconds > lastUpdate - tdParametersFX.visibleTimeRange) { - //System.out.println("milliSeconds <= lastUpdate && milliSeconds > lastUpdate - visibleRange"); + if (!isViewer() && lastUpdate>0 && milliSeconds <= lastUpdate && milliSeconds > lastUpdate - tdParametersFX.visibleTimeRange) { +// System.out.println("milliSeconds <= lastUpdate && milliSeconds > lastUpdate - visibleRange"); return; } @@ -849,7 +851,6 @@ public class TDDisplayFX extends PamBorderPane { @Override public void scrollValueChanged(AbstractPamScroller pamScroller) { - // System.out.println("TDDisplayFX: Scroll Value changed: " + System.currentTimeMillis()); // System.out.println(String.format("Scroller value changed get start %s, End %s, pos %s", PamCalendar.formatTime(timeScrollerFX.getMinimumMillis()), // PamCalendar.formatTime(timeScrollerFX.getMaximumMillis()),PamCalendar.formatTime(timeScrollerFX.getValueMillis()))); //have to set the repaint wait millis param to zero or else when scroll bar is moved quickly painting does not occur correctly. @@ -1027,7 +1028,7 @@ public class TDDisplayFX extends PamBorderPane { return tdGraphs; } - public String getCSSSettingsResource() { + public ArrayList getCSSSettingsResource() { return PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS(); } @@ -1183,11 +1184,15 @@ public class TDDisplayFX extends PamBorderPane { break; } + //forces a repaint after any changes. + lastUpdate=-1; + if (tdGraphs != null) { for (TDGraphFX tdg:tdGraphs) { tdg.notifyModelChanged(changeType); } } + } /** diff --git a/src/dataPlotsFX/layout/TDGraphFX.java b/src/dataPlotsFX/layout/TDGraphFX.java index 661e4fc2..a0fa1dc5 100644 --- a/src/dataPlotsFX/layout/TDGraphFX.java +++ b/src/dataPlotsFX/layout/TDGraphFX.java @@ -349,6 +349,7 @@ public class TDGraphFX extends PamBorderPane { // set the default overlay style. setOverlayColour(LIGHT_TD_DISPLAY); + //show the left hiding pane byu default. stackPane.getLeftHidingPane().showHidePane(true); } @@ -417,6 +418,8 @@ public class TDGraphFX extends PamBorderPane { Text chevronRight = null; Text settingsRight = null; switch (displayCol) { + //Note that this is now redundant as the buttons have a dark background - keeping for now just incase. + case LIGHT_TD_DISPLAY: case DARK_TD_DISPLAY: // System.out.println("SET DARK THEME FOR HIDING BUTTONS"); // chevronRight = PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, Color.WHITE, @@ -424,13 +427,13 @@ public class TDGraphFX extends PamBorderPane { // settingsRight = PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS, Color.WHITE, PamGuiManagerFX.iconSize); settingsRight = PamGlyphDude.createPamIcon("mdi2c-cog", Color.WHITE, PamGuiManagerFX.iconSize); break; - case LIGHT_TD_DISPLAY: - //System.out.println("SET LIGHT THEME FOR HIDING BUTTONS"); -// chevronRight = PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, PamGuiManagerFX.iconColor, - chevronRight = PamGlyphDude.createPamIcon("mdi2c-chevron-right", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize); -// settingsRight = PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS, PamGuiManagerFX.iconColor, - settingsRight = PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize); - break; +// case LIGHT_TD_DISPLAY: +// //System.out.println("SET LIGHT THEME FOR HIDING BUTTONS"); +//// chevronRight = PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, PamGuiManagerFX.iconColor, +// chevronRight = PamGlyphDude.createPamIcon("mdi2c-chevron-right", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize); +//// settingsRight = PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS, PamGuiManagerFX.iconColor, +// settingsRight = PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize); +// break; default: setOverlayColour(LIGHT_TD_DISPLAY); break; @@ -1121,7 +1124,7 @@ public class TDGraphFX extends PamBorderPane { * */ public synchronized void repaint(long tm, int flag) { - + // clear the current canvas's if (hasCanvas(flag, BASE_CANVAS)) { baseCanvas.getGraphicsContext2D().clearRect(0, 0, baseCanvas.getWidth(), baseCanvas.getHeight()); @@ -1148,16 +1151,18 @@ public class TDGraphFX extends PamBorderPane { boolean hasBase = false; synchronized (dataList) { for (TDDataInfoFX dataInfo : dataList) { + base = false; + if (!dataInfo.isShowing()) { - // System.out.println("dataInfo.isShowing(): " + dataInfo.getDataName()); + //System.out.println("!dataInfo.isShowing(): " + dataInfo.getDataName()); continue; } scaleInfo = dataInfo.getScaleInfo(); if (scaleInfo == null) { - // System.out.println("scale info null " + dataInfo.getDataName() + "index: - // "+dataInfo.getScaleInfoIndex()); +// System.out.println("scale info null " + dataInfo.getDataName() + "index: +// "+dataInfo.getScaleInfoIndex()); continue; } @@ -1180,7 +1185,7 @@ public class TDGraphFX extends PamBorderPane { } ; - // ok so only repaint if we have the right CANVAS + // OK so only repaint if we have the right CANVAS if (base && hasCanvas(flag, BASE_CANVAS)) { paintDataUnits(gc, dataInfo, false); } else if (!base && hasCanvas(flag, FRONT_CANVAS)) { @@ -1284,6 +1289,7 @@ public class TDGraphFX extends PamBorderPane { // PamDataBlock dataBlock = dataInfo.getDataBlock(); // scroll start is the end of the display i.e. the last visible time in the past + // in real time mode. scrollStart = tdDisplay.getTimeScroller().getValueMillisD(); @@ -1521,7 +1527,7 @@ public class TDGraphFX extends PamBorderPane { * @param tm- if within millis of last repaint don't repaint */ public synchronized void repaint(long tm, int flag) { - + // Start of block moved over from the panel repaint(tm) function. long currentTime = System.currentTimeMillis(); if (currentTime - lastTime < tm) { diff --git a/src/dataPlotsFX/layout/TDGraphSettingsPane.java b/src/dataPlotsFX/layout/TDGraphSettingsPane.java index 46c15b6d..b545c654 100644 --- a/src/dataPlotsFX/layout/TDGraphSettingsPane.java +++ b/src/dataPlotsFX/layout/TDGraphSettingsPane.java @@ -71,7 +71,7 @@ public class TDGraphSettingsPane extends DynamicSettingsPane { -// @Override -// public void handle(MouseEvent event) { -//// System.out.println("Mouse Clicked: " + event.toString()); -// overlayMarkers.get(currentMarker).mouseClicked(event); -// } -// } -// -// class MousePressed implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -//// System.out.println("Mouse Pressed: " + event.toString()); -// overlayMarkers.get(currentMarker).mousePressed(event); -// } -// } -// class MouseReleased implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -//// System.out.println("Mouse Released: " + event.toString()); -// overlayMarkers.get(currentMarker).mouseReleased(event); -// } -// } -// class MouseMoved implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -// overlayMarkers.get(currentMarker).mouseMoved(event); -// } -// } -// class MouseDragged implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -// overlayMarkers.get(currentMarker).mouseDragged(event); -// } -// } -// -// class MouseEntered implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -//// System.out.println("Mouse Entered: " + event.toString()); -// overlayMarkers.get(currentMarker).mouseEntered(event); -// } -// } -// -// class MouseExited implements EventHandler { -// @Override -// public void handle(MouseEvent event) { -//// System.out.println("Mouse Exited: " + event.toString()); -// overlayMarkers.get(currentMarker).mouseExited(event); -// } -// } - public ArrayList getOverlayMarkers() { return markerAdapters; diff --git a/src/dataPlotsFX/overlaymark/TDMarkerAdapter.java b/src/dataPlotsFX/overlaymark/TDMarkerAdapter.java index d0d0f696..c10b8c3b 100644 --- a/src/dataPlotsFX/overlaymark/TDMarkerAdapter.java +++ b/src/dataPlotsFX/overlaymark/TDMarkerAdapter.java @@ -2,9 +2,15 @@ package dataPlotsFX.overlaymark; import java.util.List; +import javax.swing.JPopupMenu; + +import PamView.GeneralProjector.ParameterType; +import PamView.paneloverlay.overlaymark.MarkDataSelector; import PamView.paneloverlay.overlaymark.MarkRelationships; import PamView.paneloverlay.overlaymark.OverlayMark; +import PamView.paneloverlay.overlaymark.OverlayMarkObserver; import PamView.paneloverlay.overlaymark.OverlayMarkProviders; +import PamView.paneloverlay.overlaymark.OverlayMarker; import dataPlotsFX.layout.TDGraphFX; import dataPlotsFX.layout.TDGraphFX.TDPlotPane; import detectiongrouplocaliser.DetectionGroupSummary; @@ -30,7 +36,7 @@ public class TDMarkerAdapter extends TDOverlayAdapter { /** * The overlay marker with hooks into other modules which subscribe to the display. */ - private StandardOverlayMarker pamMarker; + private TDMarkerFX pamMarker; public TDMarkerAdapter(TDGraphFX tdGraphFX){ @@ -51,16 +57,20 @@ public class TDMarkerAdapter extends TDOverlayAdapter { MarkRelationships.getInstance().subscribeObservers(pamMarker); } pamMarker.setProjector(tdGraphFX.getGraphProjector()); + + pamMarker.addDetectionGroupListener((detectionGroup)->{ + tdGraphFX.getTDDisplay().getTDControl().newSelectedDetectionGroup(detectionGroup, tdGraphFX); + }); } + + /** * Get the selected detections * @return class containing selected detections */ public DetectionGroupSummary getSelectedDetectionGroup(){ //get the currently selected data units. - - //System.out.println("Get selected detection group: " + pamMarker.getCurrentDetectionGroup()); return pamMarker.getCurrentDetectionGroup(); } diff --git a/src/dataPlotsFX/overlaymark/TDMarkerFX.java b/src/dataPlotsFX/overlaymark/TDMarkerFX.java index 173289e3..86862b59 100644 --- a/src/dataPlotsFX/overlaymark/TDMarkerFX.java +++ b/src/dataPlotsFX/overlaymark/TDMarkerFX.java @@ -1,6 +1,7 @@ package dataPlotsFX.overlaymark; import java.awt.Point; +import java.util.ArrayList; import java.util.List; import javax.swing.JPopupMenu; @@ -36,17 +37,20 @@ public class TDMarkerFX extends StandardOverlayMarker { * The current detection group. */ private DetectionGroupSummary currentDetectionGroup; + + /** + * The detection groups. + */ + public ArrayList detectionGroupListeners = new ArrayList(); /** * The maximum distance a click can be from a data unit to be selected. */ private static double maxMarkDistance=15; - - + public TDMarkerFX(TDGraphFX tdGraphFX) { super(tdGraphFX); - } /* @@ -57,6 +61,23 @@ public class TDMarkerFX extends StandardOverlayMarker { return true; } + /** + * Add the detection group listener. + * @param detectionGroupListener - the detection group listener + */ + public void addDetectionGroupListener(DetectionGroupListener detectionGroupListener) { + detectionGroupListeners.add(detectionGroupListener); + } + + /** + * Notify all listeners that there is a new detection group. + * @param detectionGroup - the detection groups. + */ + private void notifyNewDetectionGroup(DetectionGroupSummary detectionGroup) { + for (DetectionGroupListener aDetectionGroupListener : detectionGroupListeners) { + aDetectionGroupListener.newSelectedGroup(detectionGroup); + } + } /** * Override to allow a highlighting of selected data units. @@ -110,7 +131,7 @@ public class TDMarkerFX extends StandardOverlayMarker { currentDetectionGroup = new DetectionGroupSummary(e, this, this.getCurrentMark(), getSelectedMarkedDataUnits(this.getCurrentMark(), null) ); - + notifyNewDetectionGroup(currentDetectionGroup); // Debug.out.println("TDMarkerFX: Marked data units: " + currentDetectionGroup.getNumDataUnits()); this.setNowMarking(false); @@ -160,6 +181,7 @@ public class TDMarkerFX extends StandardOverlayMarker { //System.out.println("PamMarkerFX: Found a data unit @ distance: " + foundDataUnit.distance + " " // + PamCalendar.formatDateTime(foundDataUnit.dataUnit.getTimeMilliseconds())); currentDetectionGroup = new DetectionGroupSummary(e, this, this.getCurrentMark(), foundDataUnit.dataUnit); + notifyNewDetectionGroup(currentDetectionGroup); return true; } else{ @@ -168,6 +190,7 @@ public class TDMarkerFX extends StandardOverlayMarker { //only do this if the primary mouse button. Otherwise we might be right clicking on a markout out area in which //all saved markes will be removed and the pop up menu will show nothing. So the user can only use the primary button to select data units. currentDetectionGroup =null; + notifyNewDetectionGroup(currentDetectionGroup); } foundDataUnit=null; return false; diff --git a/src/dataPlotsFX/overlaymark/TDOverlayAdapter.java b/src/dataPlotsFX/overlaymark/TDOverlayAdapter.java index 25ebc518..313ed2d7 100644 --- a/src/dataPlotsFX/overlaymark/TDOverlayAdapter.java +++ b/src/dataPlotsFX/overlaymark/TDOverlayAdapter.java @@ -9,7 +9,7 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Tooltip; /** - * An overlay adapter essentially allows user mouse and touch interactions with the display. + * An overlay adapter allows user mouse and touch interactions with the display. * * @author Jamie Macaulay * diff --git a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportManager.java b/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportManager.java deleted file mode 100644 index 1e64519f..00000000 --- a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportManager.java +++ /dev/null @@ -1,5 +0,0 @@ -package dataPlotsFX.overlaymark.menuOptions.CSVExport; - -public class CSVExportManager { - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/CSVExportOverlayMenu.java similarity index 88% rename from src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportOverlayMenu.java rename to src/dataPlotsFX/overlaymark/menuOptions/CSVExportOverlayMenu.java index d70dd167..0a890053 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/CSVExportOverlayMenu.java @@ -1,8 +1,6 @@ -package dataPlotsFX.overlaymark.menuOptions.CSVExport; +package dataPlotsFX.overlaymark.menuOptions; import PamView.paneloverlay.overlaymark.OverlayMark; -import dataPlotsFX.overlaymark.menuOptions.ExportOverlayMenu; -import dataPlotsFX.overlaymark.menuOptions.OverlayMenuItem; import detectiongrouplocaliser.DetectionGroupSummary; import javafx.scene.control.Labeled; import javafx.scene.control.Tooltip; diff --git a/src/dataPlotsFX/overlaymark/menuOptions/ExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/ExportOverlayMenu.java index dd3e4af1..d26eb63d 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/ExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/ExportOverlayMenu.java @@ -67,7 +67,7 @@ public abstract class ExportOverlayMenu implements OverlayMenuItem { /** - * Allows files to be copied ot clipboard. + * Allows files to be copied to clipboard. * */ public static class FileTransferable implements Transferable { diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLClickExport.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLClickExport.java deleted file mode 100644 index d86e3d7a..00000000 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLClickExport.java +++ /dev/null @@ -1,50 +0,0 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; - -import com.jmatio.types.MLInt32; -import com.jmatio.types.MLInt8; -import com.jmatio.types.MLStructure; - -import PamguardMVC.PamDataUnit; -import clickDetector.ClickDetection; - -/** - * Export a click to a MATLAB structure. - * @author Jamie Macaulay - * - */ -public class MLClickExport extends MLRawExport { - - @Override - public MLStructure addDetectionSpecificFields(MLStructure mlStruct, PamDataUnit dataUnit, int index) { - - mlStruct = super.addDetectionSpecificFields(mlStruct, dataUnit, index); - - ClickDetection clickDetection = (ClickDetection) dataUnit; - - //the trigger map - MLInt32 triggerMap = new MLInt32(null, new Integer[]{clickDetection.getTriggerList()}, 1); - - MLInt8 type = new MLInt8(null, new Byte[]{clickDetection.getClickType()}, 1); - - - MLInt32 flag = new MLInt32(null, new Integer[]{clickDetection.getClickFlags()},1); - - //add to the structure. - mlStruct.setField("triggerMap", triggerMap, index); - mlStruct.setField("type", type, index); - mlStruct.setField("flag", flag, index); - - return mlStruct; - } - - @Override - public Class getUnitClass() { - return ClickDetection.class; - } - - @Override - public String getName() { - return "clicks"; - } - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDataUnitExport.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDataUnitExport.java deleted file mode 100644 index 304d37d6..00000000 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDataUnitExport.java +++ /dev/null @@ -1,115 +0,0 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; - -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLInt32; -import com.jmatio.types.MLInt64; -import com.jmatio.types.MLStructure; - -import PamUtils.PamCalendar; -import PamguardMVC.PamDataUnit; - -/** - * Export a detection to MATLAB - * @author Jamie Macaulay - * - */ -public abstract class MLDataUnitExport> { - - /** - * Annotations manager for nadling adding annotations to data units. - */ - private MLAnnotationsManager mlAnnotationsManager; - - public MLDataUnitExport() { - this.mlAnnotationsManager = new MLAnnotationsManager(); - } - - /** - * Create a MATLAB structure which contains all information for a data unit. - * @param dataUnit - the data unit to convert to a MATLAB structure - * @return detection data MATLAB structure ready to be exported to a .mat file or added to a ArrayList. - */ - public MLStructure detectionToStruct(MLStructure mlStruct, T dataUnit, int index){ - - //all the default stuff every data unit has. - MLInt64 millis = new MLInt64(null, new Long[]{dataUnit.getTimeMilliseconds()}, 1); - - //the channel bit map. - MLInt32 channelMap = new MLInt32(null, new Integer[]{dataUnit.getChannelBitmap()}, 1); - - //the sequence map, or 0 if there is no sequence number - MLInt32 sequenceMap = new MLInt32(null, new Integer[]{0}, 1); - if (dataUnit.getSequenceBitmapObject()!=null) { - sequenceMap = new MLInt32(null, new Integer[]{dataUnit.getSequenceBitmapObject()},1); - } - - //UID for the detection. - MLInt64 UID = new MLInt64(null, new Long[]{dataUnit.getUID()}, 1); - - //the start sample. - MLInt64 startSample = new MLInt64(null, new Long[]{dataUnit.getStartSample()}, 1); - - //the duration of the detection in samples. - MLInt64 sampleDuration = new MLInt64(null, new Long[]{dataUnit.getSampleDuration()},1); - - //the frequency limits. - MLDouble freqLimits = new MLDouble(null, dataUnit.getBasicData().getFrequency(), 1); - - //create the structure. - mlStruct.setField("millis", millis, index); - mlStruct.setField("channelMap", channelMap, index); - mlStruct.setField("sequenceMap", sequenceMap, index); - mlStruct.setField("UID", UID, index); - mlStruct.setField("startSample", startSample, index); - mlStruct.setField("sampleDuration", sampleDuration, index); - mlStruct.setField("freqLimits", freqLimits, index); - - //there may be no delay info - if (dataUnit.getBasicData().getTimeDelaysSeconds()!=null && dataUnit.getBasicData().getTimeDelaysSeconds().length>=1){ - MLInt32 numTimeDelays = new MLInt32(null, new Integer[]{dataUnit.getBasicData().getTimeDelaysSeconds().length}, 1); - MLDouble timeDelays = new MLDouble(null, dataUnit.getBasicData().getTimeDelaysSeconds(), 1); - mlStruct.setField("numTimeDelays", numTimeDelays, index); - mlStruct.setField("timeDelays", timeDelays, index); - } - else { - mlStruct.setField("numTimeDelays", new MLInt32(null, new Integer[]{0},1), index); - //mlStruct.setField("timeDelays", new MLEmptyArray(), index); //22-02-2018 - this caused an exception when saving... - mlStruct.setField("timeDelays", new MLInt32(null, new Integer[]{0},1), index); - } - - //MATLAB date number. - double datenumMT = PamCalendar.millistoDateNum(dataUnit.getTimeMilliseconds()); - MLDouble date = new MLDouble(null, new Double[]{datenumMT}, 1); - - mlStruct.setField("date", date, index); - - //add detection specific data - mlStruct= addDetectionSpecificFields(mlStruct, dataUnit, index); - - this.mlAnnotationsManager.addAnnotations(mlStruct, dataUnit); - - return mlStruct; - } - - /** - *Add detection specific fields to a structure. - *@param structure containing all generic info from PamDataUnit - *@param the data unit. - */ - public abstract MLStructure addDetectionSpecificFields(MLStructure mlStruct, T dataUnit, int index); - - /** - * Get the unit class - * @return - */ - public abstract Class getUnitClass(); - - /** - * Get the name of the structure - * @return - */ - public abstract String getName(); - - - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDetectionsManager.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDetectionsManager.java deleted file mode 100644 index 5f117d0f..00000000 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLDetectionsManager.java +++ /dev/null @@ -1,121 +0,0 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; - -import java.util.ArrayList; -import java.util.List; - -import com.jmatio.types.MLArray; -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLStructure; - -import PamguardMVC.PamDataUnit; - - -/** - * Handles the conversion of data units into MATLAB structures. - * @author Jamie Macaulay - * - */ -public class MLDetectionsManager { - - /** - * - * All the possible MLDataUnitExport export classes. - */ - ArrayList mlDataUnitsExport = new ArrayList(); - - - public MLDetectionsManager(){ - mlDataUnitsExport.add(new MLClickExport()); - mlDataUnitsExport.add(new MLWhistleMoanExport()); - mlDataUnitsExport.add(new MLRawExport()); - - } - - /** - * Check whether there are compatible data units to be exported. - * @param dataUnits - the data unit list - * @return true if MATLAB export is possible for the current data units. - */ - public boolean hasCompatibleUnits(List dataUnits) { - //first need to figure out how many data units there are. - for (int j=0; j dataUnits2MAT(ArrayList dataUnits){ - - //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. - - ArrayList list = 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; i=1) { - list.add(mlStructure); - list.add(new MLDouble((mlDataUnitsExport.get(i).getName()+"_sR"), new double[] {sampleRate}, 1)); - } - - - } - - - - //now ready to save. - return list; - - } - - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLRawExport.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLRawExport.java deleted file mode 100644 index 9d65aa98..00000000 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLRawExport.java +++ /dev/null @@ -1,70 +0,0 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; - -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLInt32; -import com.jmatio.types.MLInt64; -import com.jmatio.types.MLStructure; - -import PamUtils.PamUtils; -import PamguardMVC.PamDataUnit; -import PamguardMVC.RawDataHolder; - -/** - * Export data for any data unit which implements raw data holder. - * @author au671271 - * - */ -public class MLRawExport extends MLDataUnitExport{ - - @Override - public MLStructure addDetectionSpecificFields(MLStructure mlStruct, PamDataUnit dataUnit, int index) { - - RawDataHolder rawDataHolder = (RawDataHolder) dataUnit; - - //the waveform - MLDouble wave = new MLDouble(null, rawDataHolder.getWaveData()); - - - //the number of channels - MLInt32 nChan = new MLInt32(null, new Integer[]{PamUtils.getNumChannels(dataUnit.getChannelBitmap())}, 1); - - //the duration - repeat of duration in main data unit. Keeping here so strcut is the same as the struct from binary files - MLInt64 duration = new MLInt64(null, new Long[]{dataUnit.getSampleDuration()}, 1); - - mlStruct.setField("nChan", nChan, index); - mlStruct.setField("duration", duration, index); - mlStruct.setField("wave", wave, index); - - MLDouble angles; - MLDouble angleErrors; - if (dataUnit.getLocalisation()!=null) { - //bearing angles - angles = new MLDouble(null, dataUnit.getLocalisation().getAngles(), 1); - //angle errors - angleErrors = new MLDouble(null, dataUnit.getLocalisation().getAngleErrors(), 1); - } - else { - //bearing angles - angles = new MLDouble(null, new double[] {0}, 1); - //angle errors - angleErrors = new MLDouble(null, new double[] {0}, 1); - } - - mlStruct.setField("angles", angles, index); - mlStruct.setField("angleErrors", angleErrors, index); - - - return mlStruct; - } - - @Override - public Class getUnitClass() { - return RawDataHolder.class; - } - - @Override - public String getName() { - return "raw_data_units"; - } - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java similarity index 74% rename from src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLExportOverlayMenu.java rename to src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java index f185529f..f0702f7f 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/MLExportOverlayMenu.java @@ -1,27 +1,28 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; +package dataPlotsFX.overlaymark.menuOptions; import java.io.File; import java.util.ArrayList; import detectiongrouplocaliser.DetectionGroupSummary; +import export.MLExport.MLDetectionsManager; import javafx.scene.control.Labeled; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; -import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxGlyphs.PamSVGIcon; import pamViewFX.fxNodes.PamButton; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.Struct; import java.io.IOException; import javax.swing.filechooser.FileSystemView; -import com.jmatio.io.MatFileWriter; -import com.jmatio.types.MLArray; - import PamUtils.PamCalendar; import PamView.paneloverlay.overlaymark.OverlayMark; import PamguardMVC.PamDataUnit; -import dataPlotsFX.overlaymark.menuOptions.ExportOverlayMenu; /** * Export to MATLAB .mat files menu. @@ -57,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; @@ -70,9 +71,8 @@ public class MLExportOverlayMenu extends ExportOverlayMenu { private PamButton createButton() { PamButton button = new PamButton(""); - if (svgsprite==null) { - try { - +// if (svgsprite==null) { +// try { // // // load the svg file // InputStream svgFile = @@ -84,20 +84,21 @@ public class MLExportOverlayMenu extends ExportOverlayMenu { // // button.setGraphic(svgImage); - PamSVGIcon svgIconMaker = new PamSVGIcon(); +// PamSVGIcon svgIconMaker = new PamSVGIcon(); +// svgsprite = svgIconMaker.create(getClass().getResource("/Resources/matlab_icon2.svg").toURI().toURL(), Color.WHITE, 1); +//// "-fx-fill: white; -fx-stroke-width: 2;"); +// svgsprite.setFitHeight(standardIconSize+7); +// svgsprite.setFitWidth(standardIconSize+7); + + Text matlabIcon=PamGlyphDude.createPamIcon("file-matlab", standardIconSize+7); - - svgsprite = svgIconMaker.create(getClass().getResource("/Resources/matlab_icon2.svg").toURI().toURL(), Color.WHITE); -// "-fx-fill: white; -fx-stroke-width: 2;"); - svgsprite.setFitHeight(standardIconSize+7); - svgsprite.setFitWidth(standardIconSize+7); - button.setGraphic(svgsprite.getSpriteNode()); + button.setGraphic(matlabIcon); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } +// } catch (Exception e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } return button; } @@ -119,8 +120,8 @@ public class MLExportOverlayMenu extends ExportOverlayMenu { dataUnits.add(fnDataUnit); } - ArrayList mlData=mlDetectionsManager.dataUnits2MAT(dataUnits); - if (mlData==null || mlData.size()==0){ + Struct mlData=mlDetectionsManager.dataUnits2MAT(dataUnits); + if (mlData==null || mlData.getNumRows()==0){ //do nothing System.out.println("MLExportOverlayMenu: no data units were converted to structs"); } @@ -140,8 +141,8 @@ public class MLExportOverlayMenu extends ExportOverlayMenu { long millisStart=foundDataUnits.getFirstTimeMillis(); String currentPath = PamCalendar.formatFileDateTime(millisStart, false); //add data types to the filename - for (int i=0 ;i mlDataUnitsExport = new ArrayList(); - - - 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 - - } - - /** - * Check whether there are compatible data units to be exported. - * @param dataUnits - the data unit list - * @return true if MATLAB export is possible for the current data units. - */ - public boolean hasCompatibleUnits(List dataUnits) { - for (int i=0; i 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(); - - ArrayList list = 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; - - //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. - */ - public ArrayList dataUnitTypes; - } - - - -} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RExportOverlayMenu.java b/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java similarity index 74% rename from src/dataPlotsFX/overlaymark/menuOptions/RExport/RExportOverlayMenu.java rename to src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java index 1711b2c9..fd6f6a0f 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RExportOverlayMenu.java +++ b/src/dataPlotsFX/overlaymark/menuOptions/RExportOverlayMenu.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.RExport; +package dataPlotsFX.overlaymark.menuOptions; import java.io.File; import java.io.FileOutputStream; @@ -14,14 +14,16 @@ import PamUtils.PamCalendar; import PamView.paneloverlay.overlaymark.OverlayMark; import PamguardMVC.PamDataUnit; -import dataPlotsFX.overlaymark.menuOptions.ExportOverlayMenu; -import dataPlotsFX.overlaymark.menuOptions.RExport.RExportManager.RData; import detectiongrouplocaliser.DetectionGroupSummary; +import export.RExport.RExportManager; +import export.RExport.RExportManager.RData; import javafx.scene.control.Labeled; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxGlyphs.PamSVGIcon; import pamViewFX.fxNodes.PamButton; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; @@ -47,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; @@ -65,29 +67,31 @@ public class RExportOverlayMenu extends ExportOverlayMenu { // return button; PamButton button = new PamButton(); + Text matlabIcon=PamGlyphDude.createPamIcon("file-r", standardIconSize+7); + button.setGraphic(matlabIcon); - PamSVGIcon svgsprite; - try { - -// System.out.println("START SVG load R"); - - PamSVGIcon svgIconMaker = new PamSVGIcon(); - - svgsprite = svgIconMaker.create(getClass().getResource("/Resources/r-project.svg").toURI().toURL(), Color.WHITE); - -// svgsprite = PamSVGIcon.create(new File(getClass().getResource("/Resources/r-project.svg").toURI()), -// "-fx-fill: white; -fx-stroke-width: 2;"); - svgsprite.setFitHeight(standardIconSize+7); - svgsprite.setFitWidth(standardIconSize+7); - button.setGraphic(svgsprite.getSpriteNode()); - -// System.out.println("END SVG load R"); - - - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } +// PamSVGIcon svgsprite; +// try { +// +//// System.out.println("START SVG load R"); +// +// PamSVGIcon svgIconMaker = new PamSVGIcon(); +// +// svgsprite = svgIconMaker.create(getClass().getResource("/Resources/r-project.svg").toURI().toURL(), Color.WHITE, 1); +// +//// svgsprite = PamSVGIcon.create(new File(getClass().getResource("/Resources/r-project.svg").toURI()), +//// "-fx-fill: white; -fx-stroke-width: 2;"); +// svgsprite.setFitHeight(standardIconSize+7); +// svgsprite.setFitWidth(standardIconSize+7); +// button.setGraphic(svgsprite.getSpriteNode()); +// +//// System.out.println("END SVG load R"); +// +// +// } catch (Exception e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } return button; } @@ -109,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"); } @@ -130,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(superDets)); } diff --git a/src/dataPlotsFX/overlaymark/popUpMenu/TDMenuPane.java b/src/dataPlotsFX/overlaymark/popUpMenu/TDMenuPane.java index ff2df930..342c04ea 100644 --- a/src/dataPlotsFX/overlaymark/popUpMenu/TDMenuPane.java +++ b/src/dataPlotsFX/overlaymark/popUpMenu/TDMenuPane.java @@ -11,8 +11,8 @@ import dataPlotsFX.layout.TDGraphFX; import dataPlotsFX.layout.TDGraphFX.TDPlotPane; import dataPlotsFX.overlaymark.menuOptions.OverlayMenuItem; import dataPlotsFX.overlaymark.menuOptions.OverlayMenuManager; -import dataPlotsFX.overlaymark.menuOptions.wavExport.WavFileExportManager; import detectiongrouplocaliser.DetectionGroupSummary; +import export.wavExport.WavFileExportManager; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; @@ -149,6 +149,7 @@ public class TDMenuPane extends PamBorderPane { this.setCenter(holder); groupDetectionDisplay = new OverlayGroupDisplay(); + groupDetectionDisplay.addDisplayListener((oldDataUnit, newDataUnit)->{ //listener for changing data units setCurrentDataUnit(newDataUnit); @@ -235,7 +236,6 @@ public class TDMenuPane extends PamBorderPane { */ private void setSummaryText() { - //System.out.println(""); if (currentDataUnit==null || detectionSummary==null) return; String text; @@ -244,11 +244,13 @@ public class TDMenuPane extends PamBorderPane { text=currentDataUnit.getParentDataBlock(). getHoverText(tdGraphFX.getGraphProjector(), currentDataUnit, 0); + if (text==null) return; //do not clear as usually this is because a super unit has been set } else { //selected an area with no data units. text= new String("No data units selected"); } + this.infoTextLabel.setText(PamUtilsFX.htmlToNormal(text)); } @@ -267,7 +269,7 @@ public class TDMenuPane extends PamBorderPane { private Pane createMenuPane(){ PamVBox menuPane = new PamVBox(); - menuPane.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + menuPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); menuPane.setStyle("-fx-background-color: -fx-darkbackground"); //create info and toggle detection buttons that always sit at the top of the display @@ -491,6 +493,7 @@ public class TDMenuPane extends PamBorderPane { * @param pamDataUnit - the current data unit. */ private void setCurrentDataUnit(PamDataUnit pamDataUnit) { + currentDataUnit = pamDataUnit; setSummaryText(); } @@ -528,10 +531,8 @@ public class TDMenuPane extends PamBorderPane { this.groupDetectionDisplay.clearDisplay(); this.infoTextLabel.setText(""); - this.groupDetectionDisplay.setDetectionGroup(detectionSummary!=null ? detectionSummary.getDataList() : null); - //show the detection display. boolean showDetDisplay = toggle.isSelected(); this.toggle.setDisable(false); @@ -545,12 +546,9 @@ public class TDMenuPane extends PamBorderPane { PamRawDataBlock pamRawBlock = findRawSourceBlock(); // System.out.println("Pam raw data block: " + pamRawBlock); - if (WavFileExportManager.haveRawData(pamRawBlock, (long) overlayMarker.getCurrentMark().getLimits()[0], (long) overlayMarker.getCurrentMark().getLimits()[1])) { - //System.out.println("Overaly Marker start X: " + overlayMarker.getCurrentMark().getLimits()[0] + " end: " + overlayMarker.getCurrentMark().getLimits()[1]); //System.out.println("Overlay Marker start Y: " + overlayMarker.getCurrentMark().getLimits()[2] + " end: " + overlayMarker.getCurrentMark().getLimits()[3]); - groupDetectionDisplay.showRawData(pamRawBlock, overlayMarker.getCurrentMark().getLimits(), overlayMarker.getCurrentMark().getMarkChannels()); } else { diff --git a/src/dataPlotsFX/overlaymark/popUpMenu/TDPopUpMenuAdv.java b/src/dataPlotsFX/overlaymark/popUpMenu/TDPopUpMenuAdv.java index f2e452dc..8e1647e4 100644 --- a/src/dataPlotsFX/overlaymark/popUpMenu/TDPopUpMenuAdv.java +++ b/src/dataPlotsFX/overlaymark/popUpMenu/TDPopUpMenuAdv.java @@ -66,7 +66,8 @@ public class TDPopUpMenuAdv implements ExtPopMenu { } if (PamGUIManager.isFX()) { - popOver.show(tdGraphFX.getScene().getWindow(), e.getScreenX(), e.getScreenY()); //FX + //TODO - need to shift by the distance to arrow- not arbitrary 40 pixels + popOver.show(tdGraphFX.getScene().getWindow(), e.getScreenX(), e.getScreenY()-40); //FX } else { popOver.show(tdGraphFX, e.getScreenX(), e.getScreenY()); //Swing diff --git a/src/dataPlotsFX/rawDataPlotFX/RawSoundPlotDataFX.java b/src/dataPlotsFX/rawDataPlotFX/RawSoundPlotDataFX.java index d8a4b609..04260b9b 100644 --- a/src/dataPlotsFX/rawDataPlotFX/RawSoundPlotDataFX.java +++ b/src/dataPlotsFX/rawDataPlotFX/RawSoundPlotDataFX.java @@ -221,10 +221,10 @@ public class RawSoundPlotDataFX { */ public void newRawData(RawDataUnit rawDataUnit, double binsPerPixel) { - // crashes with a Stack Overflow error processing offline wav files. - if (!JamieDev.isEnabled()) { - return; - } +// // crashes with a Stack Overflow error processing offline wav files. +// if (!JamieDev.isEnabled()) { +// return; +// } if (soundStore.binsPerPixel==binsPerPixel || binsPerPixel==-1){ //add data to the sound data store. @@ -382,7 +382,7 @@ public class RawSoundPlotDataFX { if (soundStore.currentRawDataMillis==0){ if (++timeErrors < 10) { - System.err.println("Raw sound data has no associated millisecond time: "+ soundStore.currentRawDataMillis); + System.err.println("RawSoundPlotData: Raw sound data has no associated millisecond time: "+ soundStore.currentRawDataMillis); } return; } @@ -428,7 +428,7 @@ public class RawSoundPlotDataFX { start=(int) Math.round(startPixel); } - //System.out.println(String.format("secondsBack: %.3f arrayPixBack %.1f arrayPixStart: %d start: %d soundStore.storeSize: %d wrapPix %.2f", secondsBack, arrayPixBack, arrayPixStart,start, soundStore.storeSize, wrapPix)); +// System.out.println(String.format("secondsBack: %.3f arrayPixBack %.1f arrayPixStart: %d start: %d soundStore.storeSize: %d wrapPix %.2f", secondsBack, arrayPixBack, arrayPixStart,start, soundStore.storeSize, wrapPix)); int windowSize = (int) ((orientation==Orientation.HORIZONTAL) ? windowRect.getWidth(): windowRect.getHeight()); diff --git a/src/dataPlotsFX/scroller/TDAcousticScroller.java b/src/dataPlotsFX/scroller/TDAcousticScroller.java index 784d3bec..304bd76e 100644 --- a/src/dataPlotsFX/scroller/TDAcousticScroller.java +++ b/src/dataPlotsFX/scroller/TDAcousticScroller.java @@ -144,10 +144,16 @@ public class TDAcousticScroller extends AcousticScrollerFX implements PamSetting createTimeSpinner(); this.settingsPane.setParams(scrollerColourParams); - + updateDataGramColours(scrollerColourParams); } + + private void setSpinnerValue(long millis) { + spinnerCall = true; + spinner.getValueFactory().setValue(millis); + spinnerCall = false; + } boolean spinnerCall = false; @@ -177,9 +183,7 @@ public class TDAcousticScroller extends AcousticScrollerFX implements PamSetting //add a listener so that the spinner changes of the visible amount chnages super.getScrollBarPane().visibleAmountProperty().addListener((obsVal, oldVal, newVal)->{ - spinnerCall = true; - spinner.getValueFactory().setValue(newVal.longValue()); - spinnerCall = false; + setSpinnerValue(newVal.longValue()); }); //add a listener so the visible amount changes of the spinner changes value. @@ -213,6 +217,13 @@ public class TDAcousticScroller extends AcousticScrollerFX implements PamSetting } }); } + + + @Override + public void setVisibleMillis(long visibleAmount) { + super.setVisibleMillis(visibleAmount); + setSpinnerValue(visibleAmount); + } @@ -624,6 +635,7 @@ public class TDAcousticScroller extends AcousticScrollerFX implements PamSetting } + @Override public String getUnitName() { // TODO Auto-generated method stub diff --git a/src/dataPlotsFX/scroller/TDScrollerSettingsPane.java b/src/dataPlotsFX/scroller/TDScrollerSettingsPane.java index bc904afd..81d40375 100644 --- a/src/dataPlotsFX/scroller/TDScrollerSettingsPane.java +++ b/src/dataPlotsFX/scroller/TDScrollerSettingsPane.java @@ -95,7 +95,7 @@ public class TDScrollerSettingsPane extends DynamicSettingsPane 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,15 +342,15 @@ 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)); if (pt1 != null) { -// System.out.println("Draw Whistle: tC " + prevSliceX + " " + pt1.getY()+ " f1: " + f1 + " "+ sliceX + " " + pt2.getY()); - +// System.out.println("Draw Whistle: tC " + prevSliceX + " " + pt1.getY()+ " f1: " + f1 + " "+ sliceX + " " + pt2.getY()); drawWhistleSegment( g, orientation, prevSliceX, pt1.getY(), sliceX, pt2.getY()); } @@ -333,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()); @@ -348,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()); @@ -371,6 +402,7 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { prevSlice = sliceData; prevtC = tC; prevSliceX = sliceX; + } return awtPath; } @@ -413,7 +445,7 @@ public class WhistlePlotInfoFX extends TDDataInfoFX { * @param freq - frequency in pixels. */ private static void drawWhistleSegment(GraphicsContext g, Orientation orientation, double prevtC, double prevFreq, double tC, double freq){ - if (prevtC>(Arrays.asList(RawDataUnit.class)); + } } diff --git a/src/decimator/DecimatorProcessW.java b/src/decimator/DecimatorProcessW.java index 67be9e28..e99260bb 100644 --- a/src/decimator/DecimatorProcessW.java +++ b/src/decimator/DecimatorProcessW.java @@ -1,5 +1,8 @@ package decimator; +import java.util.ArrayList; +import java.util.Arrays; + import Filters.FilterBand; import Filters.FilterParams; import Filters.FilterType; @@ -137,5 +140,10 @@ public class DecimatorProcessW extends PamProcess { public PamRawDataBlock getOutputDataBlock() { return outputDataBlock; } + + @Override + public ArrayList getCompatibleDataUnits(){ + return new ArrayList>(Arrays.asList(RawDataUnit.class)); + } } diff --git a/src/decimator/layoutFX/DecimatorSettingsPane.java b/src/decimator/layoutFX/DecimatorSettingsPane.java new file mode 100644 index 00000000..b9b2c325 --- /dev/null +++ b/src/decimator/layoutFX/DecimatorSettingsPane.java @@ -0,0 +1,469 @@ +package decimator.layoutFX; + + +import org.controlsfx.control.PopOver; + +import Acquisition.layoutFX.OfflineDAQPane; +import Filters.FilterBand; +import Filters.FilterParams; +import Filters.FilterType; +import PamController.PamController; +import PamController.SettingsPane; +import PamDetection.RawDataUnit; +import PamView.dialog.warn.WarnOnce; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamRawDataBlock; +import dataMap.filemaps.OfflineFileParameters; +import decimator.DecimatorControl; +import decimator.DecimatorParams; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.Region; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxGlyphs.PamGlyphDude; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamGridPane; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; +import pamViewFX.fxNodes.utilityPanes.FilterPaneFX; +import pamViewFX.fxNodes.utilityPanes.SourcePaneFX; +import pamViewFX.validator.PamValidator; + +/** + * + * Settings for the decimator. + * + * @author Jamie Macaulay + * @author Dougals Gillespie + * + */ +public class DecimatorSettingsPane extends SettingsPane { + + private DecimatorParams decimatorParams; + + private PamBorderPane mainPane; + + /** + * Reference to the decimator control. + */ + private DecimatorControl decimatorControl; + + /** + * Allows users to slect the input source. + */ + private SourcePaneFX sourcePanel; + + /** + * Shows the sample rate of the source + */ + private Label sourceSampleRate; + + private float inputSampleRate; + + private TextField newSampleRate; + + private PamButton filterButton; + + private PamButton defaultFilterButton; + + private Label filterInfo; + + private FilterPaneFX filterPaneFX; + + private ComboBox interpolator; + + private boolean isViewer; + + private OfflineDAQPane offlineDAQPaneFX; + + private PamValidator validator = new PamValidator(); + + public DecimatorSettingsPane(DecimatorControl aquisitionControl) { + super(null); + + mainPane= new PamBorderPane(); + this.decimatorControl = aquisitionControl; + + mainPane.setCenter(createPane() ); + + } + + private Region createPane() { + + PamVBox holder = new PamVBox(); + holder.setSpacing(5); + + // insets = new Insets(2,2,2,2); + + Label srcLabel = new Label("Decimator settings"); + + PamGuiManagerFX.titleFont2style(srcLabel); + holder.getChildren().add(srcLabel); + + sourcePanel = new SourcePaneFX( RawDataUnit.class, true, true); + sourcePanel.addSelectionListener((obsval, oldVal, newVal)->{ + newDataSource(); + //need to validate the interpolate too as this depends on the input sample rate. + //validator.validate(); + }); + PamGuiManagerFX.titleFont2style(sourcePanel.getChannelLabel()); + + // sourcePanel.addSourcePanelMonitor(new SPMonitor()); + holder.getChildren().addAll(sourcePanel); + + PamGridPane decimatorPane = new PamGridPane(); + decimatorPane.setVgap(5); + decimatorPane.setHgap(5); + + //Decimator Settings + Label label = new Label("Decimator settings"); + PamGuiManagerFX.titleFont2style(label); + holder.getChildren().add(label); + + int gridx = 0; + int gridy = 0; + decimatorPane.add(new Label("Source sample rate "), gridx, gridy); + gridx++; + decimatorPane.add(sourceSampleRate = new Label(" - Hz"), gridx, gridy); + sourceSampleRate.setPadding(new Insets(0,0,0,5)); + gridx = 0; + gridy ++; + + decimatorPane.add(new Label("Output sample rate "), gridx, gridy); + gridx ++; + decimatorPane.add(newSampleRate = new TextField(), gridx, gridy); + gridx ++; + decimatorPane.add(new Label(" Hz"), gridx, gridy); + + validator.createCheck() + .dependsOn("new_sample_rate", newSampleRate.textProperty()) + .withMethod(c -> { + try { + String posVal = c.get("new_sample_rate"); + if (posVal.isEmpty() || Double.valueOf(posVal)==null) { + c.error("The input for output sample rate is invalid"); + } + } + catch (Exception e) { + c.error("The input for output sample rate is invalid"); + } + }) + .decorates(newSampleRate).immediate(); + + newSampleRate.textProperty().addListener((obsVal, oldVal, newval)->{ + //need to validate the interpolate too as this depends on the sample rate. + validator.validate(); + }); + + + gridy ++; + gridx = 0; + // gridwidth = 1; + + filterPaneFX = new FilterPaneFX(); + decimatorPane.add(new Label("Anti-aliasing filter"), gridx, gridy); + + gridx++; + decimatorPane.add(filterButton = new PamButton("Settings"), gridx, gridy); + filterButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chart-bell-curve-cumulative")); + + + filterButton.setTooltip(new Tooltip("Set a custom anti-aliasing filter")); + filterButton.setOnAction((action)->{ + selectFilters(filterButton); + }); + + // filterButton.addActionListener(new FilterButton()); + gridx = 2; + // gridwidth = 2; + + + decimatorPane.add(defaultFilterButton = new PamButton(), gridx, gridy); + defaultFilterButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-cog-refresh",PamGuiManagerFX.iconSize)); + defaultFilterButton.setTooltip(new Tooltip("Set the default anti-aliasing filter")); + defaultFilterButton.setOnAction((action)->{ + restoreDefaultSettings(); + }); + + gridx = 1; + gridy++; + decimatorPane.add(filterInfo = new Label(""), gridx, gridy); + PamGridPane.setColumnSpan(filterInfo, 3); + + gridx = 0; + // gridwidth = 1; + gridy++; + + Label interpLabel = new Label("Interpolation"); + + decimatorPane.add(interpLabel, gridx, gridy); + gridx++; + // gridx += gridwidth; + // gridwidth = 2; + decimatorPane.add(interpolator = new ComboBox(), gridx, gridy); + interpolator.getItems().add("None"); + interpolator.getItems().add("Linear"); + interpolator.getItems().add("Quadratic"); + + validator.createCheck() + .dependsOn("new_sample_rate", interpolator.getSelectionModel().selectedIndexProperty()) + .withMethod(c -> { + //the selected index + int selectedIndex = c.get("new_sample_rate"); + try { + float sampleRate = java.lang.Float.valueOf(newSampleRate.getText()); + String warning = decimatorInterpWarning(selectedIndex, sampleRate); + + if (warning!=null) { + c.warn(warning); + } + + } + catch (Exception e) { + return; + } + }) + .decorates(interpolator).immediate(); + + holder.getChildren().add(decimatorPane); + + + //the viewer mode. + isViewer = PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW; + Region mainPane; + if (isViewer) { + TabPane tabbedPane = new TabPane(); + + offlineDAQPaneFX= new OfflineDAQPane(decimatorControl); + + tabbedPane.getTabs().add(new Tab("Offline Files",offlineDAQPaneFX.getContentNode())); + tabbedPane.getTabs().add(new Tab("Decimator Settings", holder)); + mainPane = tabbedPane; + } + else { + mainPane = holder; + } + + // + // setHelpPoint("sound_processing.decimatorHelp.docs.decimator_decimator"); + // filterButton.setToolTipText("Manual adjustment of filter settings"); + // defaultFilterButton.setToolTipText("Set a default filter (6th order Butterworth low pass at Decimator Nyquist frequency)"); + // interpolator.setToolTipText("If Decimation / upsampling is not by an integer value, you should use interpolation to improve waveform reconstruction"); + + return mainPane; + } + + private void selectFilters(PamButton button) { + + float filtSampleRate = Math.max(inputSampleRate, getOutputSampleRate()); + + PopOver popOver = new PopOver(); + + popOver.setContentNode(filterPaneFX.getContentNode()); + + filterPaneFX.setSampleRate(filtSampleRate); + + if (decimatorParams.filterParams==null) { + restoreDefaultSettings(); + } + + popOver.setOnHidden((e)->{ + + FilterParams newFP = filterPaneFX.getParams(decimatorParams.filterParams); + + if (newFP != null) { + decimatorParams.filterParams = newFP.clone(); + } + + sayFilter(); + + }); + + filterPaneFX.setParams(decimatorParams.filterParams); + //show the filter paramters + popOver.show(button); + } + + + /** + * display filter information + */ + private void sayFilter() { + if (decimatorParams == null || decimatorParams.filterParams == null) { + filterInfo.setText("No filter"); + } + else { + filterInfo.setText(decimatorParams.filterParams.toString()); + } + } + + + private float getOutputSampleRate() { + try { + float fs = Float.valueOf(newSampleRate.getText()); + return fs; + } + catch (NumberFormatException e) { + return inputSampleRate; + } + } + + + public void restoreDefaultSettings() { + /* + * does not set the output sample rate, but does set sensible values for the + * filter. + */ + float newFS = 0; + try { + newFS = java.lang.Float.valueOf(newSampleRate.getText()); + } + catch (NumberFormatException e) { + } + PamDataBlock sourceblock = sourcePanel.getSource(); + if (sourceblock.getSampleRate() > 0) { + newFS = Math.min(newFS, sourceblock.getSampleRate()); + } + if (newFS <= 0) { + PamDialogFX.showWarning("Invalid output samplerate : " + newSampleRate.getText()); + return; + } + decimatorParams.filterParams.lowPassFreq = newFS/2; + decimatorParams.filterParams.filterType = FilterType.BUTTERWORTH; + decimatorParams.filterParams.filterOrder = 6; + decimatorParams.filterParams.filterBand = FilterBand.LOWPASS; + + sayFilter(); + } + + + @Override + public DecimatorParams getParams(DecimatorParams currParams) { + try { + // ArrayList rawBlocks = PamController.getInstance().getRawDataBlocks(); + decimatorParams.rawDataSource = sourcePanel.getSource().getDataName(); + decimatorParams.channelMap = sourcePanel.getChannelList(); + decimatorParams.newSampleRate = java.lang.Float.valueOf(newSampleRate.getText()); + } + catch (Exception Ex) { + PamDialogFX.showWarning("There is an unknown error - get in touch with PMAGuard support. "); + return null; + } + + if (decimatorParams.rawDataSource == null) { + PamDialogFX.showWarning("You must select a raw data source"); + return null; + } + + if (decimatorParams.channelMap == 0) { + PamDialogFX.showWarning("You must select at least one channel for decimation"); + return null; + } + + if (offlineDAQPaneFX != null) { + OfflineFileParameters ofp = offlineDAQPaneFX.getParams(); + if (ofp == null) { + return null; + } + decimatorControl.getOfflineFileServer().setOfflineFileParameters(ofp); + } + + decimatorParams.interpolation = interpolator.getSelectionModel().getSelectedIndex(); + + // boolean isInt = decimatorControl.isIntegerDecimation(sourcePanel.getSource().getSampleRate(), decimatorParams.newSampleRate); + // if (isInt && decimatorParams.interpolation > 0) { + // int ans = WarnOnce.showWarning("Decimator", "With in / out sample rate ratio equal to a whole number, there is no need to interpolate", WarnOnce.OK_CANCEL_OPTION); + // if (ans == WarnOnce.CANCEL_OPTION) { + // return null; + // } + // else { + // decimatorParams.interpolation = 0; + // } + // } + // if (!isInt && decimatorParams.interpolation == 0) { + // int ans = WarnOnce.showWarning("Decimator", "With in / out sample rate ratio NOT equal to a whole number, it is recommended that you use linear or quadratic interpolation", + // WarnOnce.OK_CANCEL_OPTION); + // if (ans == WarnOnce.CANCEL_OPTION) { + // return null; + // } + // else { + //// decimatorParams.interpolation = 0; + // } + // } + + return decimatorParams; + } + + + /** + * Check if the selected interpolator should be used. If not, returning a warning string. + * @param interpSelection - the interpolator selection + * @param sampleRate - the sample rate. + * @return a warning if one is needed. Null if not. + */ + private String decimatorInterpWarning(int interpSelection, float sampleRate) { + boolean isInt = decimatorControl.isIntegerDecimation(sourcePanel.getSource().getSampleRate(), sampleRate); + String warningString = null; + if (isInt && interpSelection > 0) { + warningString = "With in / out sample rate ratio equal to a whole number, there is no need to interpolate"; + + } + if (!isInt && interpSelection == 0) { + warningString = "With in / out sample rate ratio NOT equal to a whole number, it is recommended that you use linear or quadratic interpolation"; + + } + return warningString; + } + + @Override + public void setParams(DecimatorParams input) { + this.decimatorParams = input.clone(); + + sourcePanel.excludeDataBlock(decimatorControl.getDecimatorProcess().getOutputDataBlock(0), true); + sourcePanel.setSourceList(); + PamRawDataBlock currentBlock = PamController.getInstance().getRawDataBlock(decimatorParams.rawDataSource); + sourcePanel.setSource(currentBlock); + sourcePanel.setChannelList(decimatorParams.channelMap); + newSampleRate.setText(String.format("%.1f", decimatorParams.newSampleRate)); + newDataSource(); + if (offlineDAQPaneFX != null) { + offlineDAQPaneFX.setParams(decimatorControl.getOfflineFileServer().getOfflineFileParameters()); + } + interpolator.getSelectionModel().select(decimatorParams.interpolation); + sayFilter(); + validator.validate(); + + } + + @Override + public String getName() { + return "Decimator Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + private void newDataSource() { + PamDataBlock block = sourcePanel.getSource(); + if (block != null) { + sourceSampleRate.setText(String.format("%.1f Hz", + inputSampleRate = block.getSampleRate())); + } + } + +} diff --git a/src/decimator/layoutFX/DecimatorUIFX.java b/src/decimator/layoutFX/DecimatorUIFX.java new file mode 100644 index 00000000..6c83dc17 --- /dev/null +++ b/src/decimator/layoutFX/DecimatorUIFX.java @@ -0,0 +1,57 @@ +package decimator.layoutFX; + + +import PamController.SettingsPane; +import decimator.DecimatorControl; +import decimator.DecimatorParams; +import pamViewFX.PamControlledGUIFX; + +/** + * FX GUI for the SoundAquisition module. + * @author Jamie Macaulay + * + */ +public class DecimatorUIFX extends PamControlledGUIFX { + + /** + * The main settings pane for the aquisition control. + */ + private DecimatorSettingsPane decimatorPane; + + /** + * Reference to the Sound Aquisition control. + */ + private DecimatorControl decimatorControl; + + public DecimatorUIFX(DecimatorControl aquisitionControl) { + this.decimatorControl=aquisitionControl; + } + + @Override + public SettingsPane getSettingsPane(){ + if (decimatorPane==null){ + decimatorPane=new DecimatorSettingsPane(decimatorControl); + } + decimatorPane.setParams(decimatorControl.getDecimatorParams()); + return decimatorPane; + } + + + /** + * This is called whenever a settings pane is closed. If a pamControlledUnit has + * settings pane then this should be used to update settings based on info input + * into settings pane. + */ + @Override + public void updateParams() { + DecimatorParams newParams=decimatorPane.getParams(decimatorControl.getDecimatorParams()); + if (newParams!=null) { + decimatorControl.setDecimatorParams(newParams); + } + //setup the controlled unit. + decimatorControl.setupControlledUnit(); + } + + + +} \ No newline at end of file diff --git a/src/detectionPlotFX/DetectionDisplayControl.java b/src/detectionPlotFX/DetectionDisplayControl.java index d9e78654..9e8fcf7b 100644 --- a/src/detectionPlotFX/DetectionDisplayControl.java +++ b/src/detectionPlotFX/DetectionDisplayControl.java @@ -24,6 +24,7 @@ import userDisplayFX.UserDisplayNodeFX; * @author Jamie Macaulay * */ +@Deprecated public class DetectionDisplayControl extends UserDisplayControlFX { /** @@ -49,7 +50,8 @@ public class DetectionDisplayControl extends UserDisplayControlFX { //create the observer for the parent data block. detectionDataObserver=new DetectionDataObserver(); setMultiParent(false); - + + } /** @@ -83,7 +85,7 @@ public class DetectionDisplayControl extends UserDisplayControlFX { displayToDataModel(this.getUserDisplayProcess().getParentDataBlock()); break; } - this.detectionDisplay.notifyModelChanged(type); + //this.detectionDisplay.notifyModelChanged(type); } /** @@ -119,8 +121,11 @@ public class DetectionDisplayControl extends UserDisplayControlFX { */ public boolean newDataBlockAdded(PamDataBlock pamDataBlock){ + System.out.println("New datablock added: " + pamDataBlock.getDataName()); + //now must find the data provider. //first get the list of dataInfos. + //22/05/206. Need to check we have the correct datablock before doing anything else. The datablock should //be null or of the correct type. If not need to keep the last connection. DDDataProvider newDataProviderFX=DDPlotRegister.getInstance().findDataProvider(pamDataBlock); @@ -147,7 +152,7 @@ public class DetectionDisplayControl extends UserDisplayControlFX { this.detectionDisplay.setDataInfo(newDataProviderFX); //now add the observer to the data block - //System.out.println("DetectionDisplayControl: Adding observer to datablock: "+ pamDataBlock.getDataName()); + //System.out.println("DetectionDisplayControl: Adding observer to data block: "+ pamDataBlock.getDataName()); pamDataBlock.addObserver(this.detectionDataObserver); return true; @@ -156,9 +161,9 @@ public class DetectionDisplayControl extends UserDisplayControlFX { @Override public ArrayList getDisplays(){ if (displays==null){ - detectionDisplay=new DetectionPlotDisplay(this); + detectionDisplay=new DetectionPlotDisplay(); displays=new ArrayList(); - displays.add(detectionDisplay); + //displays.add(detectionDisplay); } return displays; } @@ -181,7 +186,7 @@ public class DetectionDisplayControl extends UserDisplayControlFX { @Override public void addData(PamObservable o, PamDataUnit arg) { - //System.out.println("DetectionDisplay: Incoming data unit: "+arg.getParentDataBlock().getDataName()); + System.out.println("DetectionDisplay: Incoming data unit: "+arg.getParentDataBlock().getDataName()); //send the data unit to the display. //TODO-why? Platform.runLater(()->{ diff --git a/src/detectionPlotFX/DetectionDisplayControl2.java b/src/detectionPlotFX/DetectionDisplayControl2.java new file mode 100644 index 00000000..419f44ea --- /dev/null +++ b/src/detectionPlotFX/DetectionDisplayControl2.java @@ -0,0 +1,166 @@ +package detectionPlotFX; + +import java.util.ArrayList; + +import PamController.PamController; +import PamController.PamControllerInterface; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import PamguardMVC.PamObservable; +import PamguardMVC.PamObserverAdapter; +import pamViewFX.fxNodes.internalNode.PamInternalPane; +import userDisplayFX.UserDisplayControlFX; +import userDisplayFX.UserDisplayNodeFX; + + +/** + * + * Detection plot which can display single or groups of detections. + * + * @author Jamie Macaulay + * + */ +public class DetectionDisplayControl2 extends UserDisplayControlFX { + + + /** + * The display + */ + private DetectionGroupDisplayFX detectionDisplay; + + /** + * A list of displays-> really redundant as there is only ever one display. + */ + private ArrayList displays; + + /** + * The data observer that + */ + private DetectionDataObserver detectionDataObserver; + + + private PamInternalPane internalFrame;; + + + public DetectionDisplayControl2(String unitName) { + super(unitName); + //set which data blocks can connect as parents. + setCompatibleDataUnits(); + //create the observer for the parent data block. + detectionDataObserver=new DetectionDataObserver(); + setMultiParent(false); + } + + + /** + * Set compatible data units in the process for this display. This allows the data model to determine if connections can + * be made to the display. + */ + @SuppressWarnings("unchecked") + private void setCompatibleDataUnits(){ + super.removeCompatibleDataUnits(); + super.addCompatibleUnit(PamDataUnit.class); + //also add data unit + } + + + @Override + public void notifyModelChanged(int type){ + // System.out.println("NOTIFICATION: " + type); + super.notifyModelChanged(type); + switch (type){ + case PamControllerInterface.INITIALIZATION_COMPLETE: + setCompatibleDataUnits(); + break; + case PamControllerInterface.ADD_CONTROLLEDUNIT: + setCompatibleDataUnits(); + break; + case PamControllerInterface.CHANGED_PROCESS_SETTINGS: + //this is were the data block may have been added. Need to add an observer to this data block to say when the thing has + //thing has a new detection. + displayToDataModel(this.getUserDisplayProcess().getParentDataBlock()); + break; + } + if (this.detectionDisplay!=null) this.detectionDisplay.notifyModelChanged(type); + } + + + /** + * Set the display to show detections form the data block set in the data model- + * i.e. show it's parent data block. + */ + protected void displayToDataModel(PamDataBlock parentDataBlock){ + //remove any TDDataInfos which are not present in the data block list + //if the datablock the same do nothing + newDataBlockAdded(parentDataBlock); + } + + + /** + * Called whenever a new data block is added to the display. + * Removes the observer from all other data blocks in the model + * and then the adds the observer to that data block; + * @return true if the parent has successfully been added. + */ + public boolean newDataBlockAdded(PamDataBlock pamDataBlock){ + + //System.out.println("NEW DATA BLOCK DETECTION DISPLAY: " + pamDataBlock); + + //if null then no parent- simply set the DDataInfo to null; + if (pamDataBlock==null) { + this.detectionDisplay.clearDisplay(); + return true; + } + + //now add the observer to the data block + //System.out.println("DetectionDisplayControl: Adding observer to data block: "+ pamDataBlock.getDataName()); + pamDataBlock.addObserver(this.detectionDataObserver); + + return true; + } + + + @Override + public ArrayList getDisplays(){ + if (displays==null){ + detectionDisplay=new DetectionGroupDisplayFX(this); + //set the paramters. + displays=new ArrayList(); + displays.add(detectionDisplay); + //TODO +// detectionDisplay.setEnableScrollBar(false); //make this an option + } + return displays; + } + + + /** + * + * The data observer monitors incoming data from data blocks. + * + * @author Doug Gillespie and Jamie Macaulay + * + */ + private class DetectionDataObserver extends PamObserverAdapter { + + @Override + public long getRequiredDataHistory(PamObservable o, Object arg) { + return 1000; //no data histroy required for this click. + } + + @Override + public void addData(PamObservable o, PamDataUnit arg) { + //System.out.println("DetectionDisplay: INCOMING data unit: "+ arg); + //send the data unit to the display. + detectionDisplay.setDataUnit(arg); + } + + @Override + public String getObserverName() { + return "Detection Display FX"; + } + } + + + +} diff --git a/src/detectionPlotFX/DetectionGroupDisplay.java b/src/detectionPlotFX/DetectionGroupDisplay.java index 38ad7de4..69289edf 100644 --- a/src/detectionPlotFX/DetectionGroupDisplay.java +++ b/src/detectionPlotFX/DetectionGroupDisplay.java @@ -7,6 +7,7 @@ import java.util.List; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.superdet.SuperDetection; +import atlantafx.base.theme.Styles; import detectionPlotFX.data.DDDataInfo; import detectionPlotFX.data.DDDataProvider; import detectionPlotFX.data.DDPlotRegister; @@ -15,27 +16,48 @@ import detectionPlotFX.layout.DetectionPlotDisplay; import javafx.geometry.Insets; 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; /** * - * A display which shows a list of data units with arrows allowing the user to - * navigate through the different data units. + * A detection plot display with convenience functions to set any type of data unit. * * @author Jamie Macaulay * */ +@SuppressWarnings("rawtypes") public class DetectionGroupDisplay extends PamBorderPane { + + /** + * Show the settings within hiding panes within the display. + */ + public static final int DISPLAY_COMPACT = 0; + + /** + * Show settings on top and to the right of the display + */ + public static final int DISPLAY_EXTENDED = 1; /** * Index of the current normal unit with the detection summary. @@ -55,7 +77,7 @@ public class DetectionGroupDisplay extends PamBorderPane { /** * Label for the top holder. */ - private Label label; + private Label dataLabel; /** * Holds the arrow pane and hiding pane. @@ -81,7 +103,7 @@ public class DetectionGroupDisplay extends PamBorderPane { /** * Holds the detection display and controls for viewing standard detections. */ - public PamBorderPane detectionDisplayHolder; + public Pane detectionDisplayHolder; /** * The group detection listeners. @@ -96,12 +118,39 @@ 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. + */ public DetectionGroupDisplay() { //create hash map to map DDDataInfos to datablocks for quick access. dDataInfoHashMap = new HashMap(); - createDetectionDisplay(); + this.layoutType = DISPLAY_EXTENDED; + createDetectionDisplay(DISPLAY_EXTENDED); + this.setCenter(detectionDisplayHolder); + } + + /** + * Constructor for the detection group display. + * @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(layoutType); this.setCenter(detectionDisplayHolder); } @@ -109,8 +158,8 @@ public class DetectionGroupDisplay extends PamBorderPane { * Create the detection display. * @return the detection display */ - private Pane createDetectionDisplay(){ - + private Pane createDetectionDisplay(int layoutType){ + detectionDisplay= new DetectionPlotDisplay(); arrowLeft= new PamButton(); @@ -136,43 +185,128 @@ public class DetectionGroupDisplay extends PamBorderPane { arrowPane.getChildren().addAll(arrowLeft, arrowRight); BorderPane.setAlignment(arrowPane, Pos.CENTER_RIGHT); + + //a label to show information of the data unit + dataLabel = new Label(); + + if (layoutType==DISPLAY_EXTENDED) { + //the display has controls above the axis and a hiding pane that increases the width of the display. + + //the holder for the detection display. + PamBorderPane detectionDisplayHolder= new PamBorderPane(); + + //create the hiding pane to show advanced settings. + hidingPane = new HidingPane(Side.RIGHT, detectionDisplay.getSettingsHolder(), detectionDisplayHolder, layoutType==DISPLAY_COMPACT, 0); + + topHolder=new PamBorderPane(); + topHolder.setRight(arrowPane); + + topHolder.setCenter(dataLabel); + topHolder.setLeft(detectionDisplay.getDataTypePane()); + + //whenever the detection plot selection box e.g. from waveform to wigner then check there is a settings pane. If not + //then get rid of the settings button. + detectionDisplay.getDataTypePane().getDetectionPlotBox().valueProperty().addListener((obsVal, oldVal, newVal)->{ + enableControls(); + }); + + detectionDisplayHolder.setTop(topHolder); + detectionDisplayHolder.setCenter(detectionDisplay); + this.detectionDisplayHolder = detectionDisplayHolder; + + arrowPane.getChildren().add(hidingPane.getShowButton()); + + 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); + + //this makes the hide button appear top right which is nicer for closing the pane. + StackPane.setAlignment(hidingPane.getHideButton(), Pos.TOP_RIGHT); + //hidingPane.removeHideButton(); + hidingPane.styleHideButton(hidingPane.getHideButton(), Side.LEFT); + + //make the background dark for settings pane. + detectionDisplay.getSettingsHolder().setStyle("-fx-background-color: -fx-darkbackground"); + + } + else { + //the display is compact with all controls within an internal hiding pane. + + detectionDisplayHolder = new PamStackPane(); + + PamTabPane settingsPane = new PamTabPane(); + //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); + + 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); + + 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); - topHolder=new PamBorderPane(); - topHolder.setRight(arrowPane); - topHolder.setCenter(label = new Label()); - topHolder.setLeft(detectionDisplay.getDataTypePane()); - - //whenever the detection plot selection box e.g. from waveform to wigner then check there is a settings pane. If not - //then get rid of the settings button. - detectionDisplay.getDataTypePane().getDetectionPlotBox().valueProperty().addListener((obsVal, oldVal, newVal)->{ - enableControls(); - }); + 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); + + //set the hiding pane + Node icon = PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize); + detectionDisplay.getPlotPane().setHidePane(new PamBorderPane(settingsPane), icon, Side.RIGHT); - detectionDisplayHolder= new PamBorderPane(); - detectionDisplayHolder.setTop(topHolder); - detectionDisplayHolder.setCenter(detectionDisplay); + //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(); + + 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)); + } - - //create the hiding pane to show advanced settings. - hidingPane = new HidingPane(Side.RIGHT, detectionDisplay.getSettingsHolder(), detectionDisplayHolder, false, 0); - - detectionDisplay.getSettingsHolder().getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); - detectionDisplay.getSettingsHolder().setStyle("-fx-background-color: -fx-darkbackground"); - hidingPane.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + //set styles +// detectionDisplay.getSettingsHolder().getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); +// detectionDisplay.getSettingsHolder().setStyle("-fx-background-color: -fx-darkbackground"); + hidingPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); - arrowPane.getChildren().add(hidingPane.getShowButton()); - - hidingPane.getShowButton().setGraphic(PamGlyphDude.createPamIcon("mdi2c-cog", - PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); - this.setRight(hidingPane); //bit of a hack but works. - hidingPane.showHidePane(false); - - //this makes the hide button appear top right which is nicer for closing the pane. - StackPane.setAlignment(hidingPane.getHideButton(), Pos.TOP_RIGHT); - //hidingPane.removeHideButton(); - hidingPane.styleHideButton(hidingPane.getHideButton(), Side.LEFT); - + //set default size detectionDisplayHolder.setPrefSize(500, 400); detectionDisplayHolder.setPadding(new Insets(0,10,0,0)); @@ -234,15 +368,15 @@ public class DetectionGroupDisplay extends PamBorderPane { */ private void setLabelText(){ if (detectionGroup.size()>1){ - label.setText(detectionGroup.get(currentUnitIndex).getParentDataBlock().getDataName() + " UID: " + detectionGroup.get(currentUnitIndex).getUID() + + dataLabel.setText(detectionGroup.get(currentUnitIndex).getParentDataBlock().getDataName() + " UID: " + detectionGroup.get(currentUnitIndex).getUID() + ": " + (currentUnitIndex+1) + " of " + detectionGroup.size()); } else if (detectionGroup.size()==1){ - label.setText(detectionGroup.get(currentUnitIndex).getParentDataBlock().getDataName() + " UID: " + detectionGroup.get(currentUnitIndex).getUID()); + dataLabel.setText(detectionGroup.get(currentUnitIndex).getParentDataBlock().getDataName() + " UID: " + detectionGroup.get(currentUnitIndex).getUID()); } else { //selected area with data units. - label.setText("No data units in area"); + dataLabel.setText("No data units in area"); } //add sub detection count if there are sub detections. @@ -250,7 +384,7 @@ public class DetectionGroupDisplay extends PamBorderPane { SuperDetection superDet = (SuperDetection) detectionGroup.get(currentUnitIndex); int subCount = superDet.getSubDetectionsCount(); if (subCount>0) { - label.setText(label.getText()+ " : " + superDet.getSubDetectionsCount() + " sub detection" + (subCount>1 ? "s":"")); + dataLabel.setText(dataLabel.getText()+ " : " + superDet.getSubDetectionsCount() + " sub detection" + (subCount>1 ? "s":"")); } } } @@ -289,7 +423,7 @@ public class DetectionGroupDisplay extends PamBorderPane { public void clearDisplay() { setDataUnit(null); detectionDisplay.clearPane(); - this.label.setText(""); + this.dataLabel.setText(""); } @@ -297,14 +431,15 @@ public class DetectionGroupDisplay extends PamBorderPane { * Sets the current in the display. * @param pamDataUnit - the current data unit to set. * @param detectionDisplay- the detection display plot to set the data unit for. + * @return true of a new data info has been added - usually means a different type of detection to display compared to the last detection. */ - public void setDataUnit(PamDataUnit dataUnit){ + public boolean setDataUnit(PamDataUnit dataUnit){ detectionDisplay.clearPane(); if (dataUnit==null) { detectionDisplay.removeDataInfo(); - return; + return true; } // TDDataInfoFX dataInfo = this.tdGraphFX.findDataInfo(dataUnit); @@ -336,7 +471,7 @@ public class DetectionGroupDisplay extends PamBorderPane { dDataInfoHashMap.put(dataUnit.getParentDataBlock(), dDataInfo); } - if (dDataInfo==null) return; + if (dDataInfo==null) return true; //only change the dDataInfo if it's different,. boolean newDataInfo = false; @@ -364,6 +499,25 @@ public class DetectionGroupDisplay extends PamBorderPane { // clearSingleType(); //TODO....highlight data unit. + + return newDataInfo; + } + + + /** + * Attempts to set the detectionPlot + * @param plotName + * @return + */ + public boolean setDetectionPlot(String plotName) { + + //set the current detection plot based in the name + boolean setOk = currentDataInfo.setCurrentDetectionPlot(plotName); + + //update the detection settings pane so it shows the correct plot names etc. + detectionDisplay.getDataTypePane().notifyDataChange(); + + return setOk; } @@ -374,7 +528,7 @@ public class DetectionGroupDisplay extends PamBorderPane { */ public void triggerListeners(PamDataUnit oldDataUnit, PamDataUnit newDataUnit) { for (GroupDisplayListener aListener : displayListeners) { - aListener.newDataUnitSlected(oldDataUnit, newDataUnit); + aListener.newDataUnitSelected(oldDataUnit, newDataUnit); } } @@ -426,4 +580,35 @@ public class DetectionGroupDisplay extends PamBorderPane { return detectionGroup.get(currentUnitIndex); } + + /** + * 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) { + 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(); + } + + + +// @Override +// public boolean requestNodeSettingsPane() { +// if (dDPlotPane.getHidePane(Side.RIGHT)!=null) dDPlotPane.getHidePane(Side.RIGHT).showHidePane(true); +// if (dDPlotPane.getHidePane(Side.LEFT)!=null) dDPlotPane.getHidePane(Side.LEFT).showHidePane(true); +// return true; +// } + + } \ No newline at end of file diff --git a/src/detectionPlotFX/DetectionGroupDisplayFX.java b/src/detectionPlotFX/DetectionGroupDisplayFX.java new file mode 100644 index 00000000..ea831e73 --- /dev/null +++ b/src/detectionPlotFX/DetectionGroupDisplayFX.java @@ -0,0 +1,249 @@ +package detectionPlotFX; + +import java.io.Serializable; + +import PamController.PamControlledUnitSettings; +import PamController.PamController; +import PamController.PamSettingManager; +import PamController.PamSettings; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import javafx.geometry.Side; +import javafx.scene.layout.Region; +import pamViewFX.fxNodes.internalNode.PamInternalPane; +import userDisplayFX.UserDisplayControlFX; +import userDisplayFX.UserDisplayNodeFX; + +/** + * A group detection display with all the bits added to allow the display to be used in the FX GUI as + * a stand-alone user display. + * + * @author Jamie Macaulay + * + */ +public class DetectionGroupDisplayFX extends DetectionGroupDisplay implements UserDisplayNodeFX, PamSettings{ + + private DetectionPlotParams detectionPlotParams = new DetectionPlotParams(); + + /** + * Reference to the internal frame that migfth hold this graph. + */ + private PamInternalPane internalFrame; + + private DetectionDisplayControl2 displayControl; + + private PamDataUnit currentDetection; + + public DetectionGroupDisplayFX(DetectionDisplayControl2 displayControl){ + super(DetectionGroupDisplay.DISPLAY_COMPACT); + this.displayControl = displayControl; + //register the settings. + PamSettingManager.getInstance().registerSettings(this); + } + + @Override + public String getName() { + return "Detection Dsiplay"; + } + + @Override + public Region getNode() { + // TODO Auto-generated method stub + return this; + } + + @Override + public void openNode() { + // TODO Auto-generated method stub + + } + + @Override + public boolean isStaticDisplay() { + return false; + } + + @Override + public boolean isResizeableDisplay() { + return true; + } + + @Override + public boolean isMinorDisplay() { + return true; + } + + @Override + public void closeNode() {}; + + @Override + public DetectionPlotParams getDisplayParams() { + + return this.detectionPlotParams; + } + + private void prepareDisplayParams() { + if (displayControl.getUserDisplayProcess().getParentDataBlock()!=null) { + detectionPlotParams.dataSource = displayControl.getUserDisplayProcess().getParentDataBlock().getLongDataName(); + } + 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(); + detectionPlotParams.positionY=internalFrame.getInternalRegion().getLayoutY(); + detectionPlotParams.sizeX=internalFrame.getInternalRegion().getWidth(); + detectionPlotParams.sizeY=internalFrame.getInternalRegion().getHeight(); + } + } + + + + @Override + public void setFrameHolder(PamInternalPane internalFrame) { + this.internalFrame=internalFrame; + + } + + @Override + public boolean requestNodeSettingsPane() { + this.showSettingsPane(true); + + return true; + } + + + private void showSettingsPane(boolean b) { + this.detectionDisplay.getHidingPane(Side.RIGHT).showHidePane(b);; + + } + + @Override + public void notifyModelChanged(int changeType) { + + switch (changeType) { + case PamController.INITIALIZATION_COMPLETE: + PamDataBlock dataBlock = PamController.getInstance().getDataBlockByLongName(detectionPlotParams.dataSource); + //set the correct parent data block if on exists + displayControl.getUserDisplayProcess().setParentDataBlock(dataBlock); + displayControl.displayToDataModel(dataBlock); + + break; + } + + } + + /** + * Set the display parameterts. + * @param detectionPlotParams + */ + public void setDisplayParams(DetectionPlotParams detectionPlotParams) { + this.detectionPlotParams = detectionPlotParams; + + } + + @Override + public Serializable getSettingsReference() { + Serializable set = prepareSerialisedSettings(); + return set; + } + + /** + * Prepare settings for saving. + * @return + */ + private Serializable prepareSerialisedSettings() { + if (detectionDisplay==null) return null; + prepareDisplayParams(); + detectionPlotParams = getDisplayParams(); + +// System.out.println("SAVE DETECTION DISPLAY DATA SOURCE: " + detectionPlotParams.dataSource); +// System.out.println("SAVE DETECTION DISPLAY TAB NAME: " + detectionPlotParams.tabName); + + return detectionPlotParams; + } + + + @Override + public long getSettingsVersion() { + return DetectionPlotParams.serialVersionUID; + } + + + @Override + public boolean restoreSettings(PamControlledUnitSettings pamControlledUnitSettings) { + try{ + return restoreSettings((DetectionPlotParams) pamControlledUnitSettings.getSettings()); + } + catch(Exception e){ + e.printStackTrace(); + return false; + } + } + + + private boolean restoreSettings(DetectionPlotParams settings) { + if (settings == null) { + return false; + } + +// System.out.println("LOAD DETECTION DISPLAY DATA SOURCE: " + settings.dataSource); +// System.out.println("LOAD DETECTION DISPLAY DATA SOURCE: " + settings.tabName); + + this.detectionPlotParams = settings.clone(); + + + this.setEnableScrollBar(detectionPlotParams.showScrollBar); + + return true; + } + + @Override + public String getUnitName() { + return displayControl.getUnitName(); + } + + @Override + public String getUnitType() { + return displayControl.getUnitType(); + } + + @Override + public UserDisplayControlFX getUserDisplayControl() { + return displayControl; + } + + @Override + public boolean setDataUnit(PamDataUnit dataUnit){ + + /** + * 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 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(); + //System.out.println("SET CURRENT DETECTION PLOT TO USE IS: " + detectionPlotName); + detectionPlotParams.dataAxisMap.put(currentDetection.getParentDataBlock().getLongDataName(), detectionPlotName); + } + + this.currentDetection = dataUnit; + + //setup the new data unit + boolean newDataInfo = super.setDataUnit(dataUnit); + + if (newDataInfo && dataUnit!=null) { + //if there's a new data info we may want to set the detection back to it's most recent selection + String detectionPlotName = detectionPlotParams.dataAxisMap.get(dataUnit.getParentDataBlock().getLongDataName()); +// System.out.println("THE CURRENT DETECTION PLOT TO USE IS: " + detectionPlotName); + setDetectionPlot(detectionPlotName); + } + + return newDataInfo; + } + +} diff --git a/src/detectionPlotFX/DetectionPlotParams.java b/src/detectionPlotFX/DetectionPlotParams.java index 5085be83..80840b7d 100644 --- a/src/detectionPlotFX/DetectionPlotParams.java +++ b/src/detectionPlotFX/DetectionPlotParams.java @@ -1,5 +1,11 @@ package detectionPlotFX; +import java.util.HashMap; + +import PamModel.parametermanager.ManagedParameters; +import PamModel.parametermanager.PamParameterSet; +import PamModel.parametermanager.PamParameterSet.ParameterSetType; +import dataPlotsFX.TDParametersFX; import userDisplayFX.UserDisplayNodeParams; /** @@ -8,11 +14,49 @@ import userDisplayFX.UserDisplayNodeParams; * @author Jamie Macaulay * */ -public class DetectionPlotParams extends UserDisplayNodeParams { +public class DetectionPlotParams extends UserDisplayNodeParams implements Cloneable, ManagedParameters { + /** * */ - private static final long serialVersionUID = 1L; + static final long serialVersionUID = 3L; + + /** + * 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 + * different types of data units and the dispay types are saved on PAMGuard opne and close. + */ + public HashMap dataAxisMap = new HashMap(); + + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public DetectionPlotParams clone() { + try { + return (DetectionPlotParams) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public PamParameterSet getParameterSet() { + PamParameterSet ps = PamParameterSet.autoGenerate(this, ParameterSetType.DISPLAY); + return ps; + } } diff --git a/src/detectionPlotFX/GeneralDDPlotProvider.java b/src/detectionPlotFX/GeneralDDPlotProvider.java new file mode 100644 index 00000000..75825b57 --- /dev/null +++ b/src/detectionPlotFX/GeneralDDPlotProvider.java @@ -0,0 +1,20 @@ +package detectionPlotFX; + +import PamguardMVC.PamDataBlock; +import detectionPlotFX.data.DDDataInfo; +import detectionPlotFX.data.DDDataProvider; +import detectionPlotFX.layout.DetectionPlotDisplay; + +public class GeneralDDPlotProvider extends DDDataProvider { + + public GeneralDDPlotProvider(PamDataBlock parentDataBlock) { + super(parentDataBlock); + // TODO Auto-generated constructor stub + } + + @Override + public DDDataInfo createDataInfo(DetectionPlotDisplay tdGraph) { + return null; + } + +} diff --git a/src/detectionPlotFX/GroupDisplayListener.java b/src/detectionPlotFX/GroupDisplayListener.java index 2f4f5e55..1f1483c1 100644 --- a/src/detectionPlotFX/GroupDisplayListener.java +++ b/src/detectionPlotFX/GroupDisplayListener.java @@ -16,5 +16,5 @@ public interface GroupDisplayListener { * @param oldDataUnit - the old data unit. * @param newDataUnit - the new selected data unit. */ - public void newDataUnitSlected(PamDataUnit oldDataUnit, PamDataUnit newDataUnit); + public void newDataUnitSelected(PamDataUnit oldDataUnit, PamDataUnit newDataUnit); } diff --git a/src/detectionPlotFX/data/DDDataInfo.java b/src/detectionPlotFX/data/DDDataInfo.java index 5fe4a66f..01c59f79 100644 --- a/src/detectionPlotFX/data/DDDataInfo.java +++ b/src/detectionPlotFX/data/DDDataInfo.java @@ -2,7 +2,6 @@ package detectionPlotFX.data; import java.util.ArrayList; import PamController.PamController; -import PamDetection.PamDetection; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import detectionPlotFX.DDScaleInfo; @@ -12,7 +11,6 @@ import detectionPlotFX.projector.DetectionPlotProjector; import javafx.scene.canvas.GraphicsContext; import javafx.scene.shape.Rectangle; import pamViewFX.fxNodes.PamBorderPane; -import pamViewFX.fxNodes.pamAxis.PamAxisFX; import pamViewFX.fxNodes.pamAxis.PamAxisPane2; /** @@ -23,23 +21,23 @@ import pamViewFX.fxNodes.pamAxis.PamAxisPane2; * */ public abstract class DDDataInfo { - - + + /** * Reference to the detection display plot */ private DetectionPlotDisplay dDPlot; - + /** * Pane whihc holds plot specifc settings. */ private PamBorderPane settingsPane= new PamBorderPane(); - + /** * The data block which the DDDataInfo is associated with */ private PamDataBlock pamDataBlock; - + /** * The scale information. Holds the min and max values for the x and y axis. */ @@ -49,12 +47,12 @@ public abstract class DDDataInfo { * True if in viewer mode. */ private boolean isViewer=false; - + /** * List of detection plots which can display the data unit. */ private ArrayList detectionPlots; - + /* * The index of the current detection plot */ @@ -64,23 +62,23 @@ public abstract class DDDataInfo { * The sample rate */ private float hardSampleRate=-1; - -// /** -// * Create the DDDataInfo -// * @param dDDataProvider - the data provider -// * @param dDPlot - the detection plot -// * @param pamDataBlock - the parent datablock -// */ -// public DDDataInfo(DetectionPlotDisplay dDPlot, PamDataBlock pamDataBlock) { -// super(); -// this.dDPlot=dDPlot; -// this.pamDataBlock = pamDataBlock; -// this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); -// detectionPlots=new ArrayList(); -// } - - + + // /** + // * Create the DDDataInfo + // * @param dDDataProvider - the data provider + // * @param dDPlot - the detection plot + // * @param pamDataBlock - the parent datablock + // */ + // public DDDataInfo(DetectionPlotDisplay dDPlot, PamDataBlock pamDataBlock) { + // super(); + // this.dDPlot=dDPlot; + // this.pamDataBlock = pamDataBlock; + // this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); + // detectionPlots=new ArrayList(); + // } + + /** * Create the DDDataInfo * @param dDDataProvider - the dataplot provider @@ -94,7 +92,7 @@ public abstract class DDDataInfo { this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); detectionPlots=new ArrayList(); } - + /** * Create the DDdata info wothout reference to a provider * @param dDPlot - - the data plot @@ -107,20 +105,20 @@ public abstract class DDDataInfo { this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); detectionPlots=new ArrayList(); } - -// /** -// * Create the DDdata info wothout reference to a provider -// * @param dDPlot - - the data plot -// * @param pamDataBlock - the parent datablopck -// */ -// public DDDataInfo(DetectionPlotDisplay dDPlot, float sR) { -// super(); -// this.dDPlot=dDPlot; -// this.hardSampleRate=sR; -// this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); -// detectionPlots=new ArrayList(); -// } - + + // /** + // * Create the DDdata info wothout reference to a provider + // * @param dDPlot - - the data plot + // * @param pamDataBlock - the parent datablopck + // */ + // public DDDataInfo(DetectionPlotDisplay dDPlot, float sR) { + // super(); + // this.dDPlot=dDPlot; + // this.hardSampleRate=sR; + // this.isViewer = (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW); + // detectionPlots=new ArrayList(); + // } + /** * Get the data block for this information * @return the datablock. This can be null. @@ -128,8 +126,8 @@ public abstract class DDDataInfo { public PamDataBlock getDataBlock() { return pamDataBlock; } - - + + /** * Get the sample rate. This is acquired form the datablock if one exists. Otherwise a hard * wired sample rate is used. @@ -139,9 +137,9 @@ public abstract class DDDataInfo { if (pamDataBlock!=null) return pamDataBlock.getSampleRate(); else return hardSampleRate; } - - - + + + /** * Set the sample rate. This is only used if the parent data block is null. * @return the sample rate. @@ -149,7 +147,7 @@ public abstract class DDDataInfo { public void setHardSampleRate(float hardSampleRate) { this.hardSampleRate = hardSampleRate; } - + /** * Add a type of data unit to the list. * @param unitType String name of the data unit. @@ -157,7 +155,7 @@ public abstract class DDDataInfo { public void addDetectionPlot(DetectionPlot detectionPlot) { detectionPlots.add(detectionPlot); } - + /** * Get a detection plot * @param the index of the detection plot to get. @@ -165,14 +163,14 @@ public abstract class DDDataInfo { public DetectionPlot getDetectionPlot(int i) { return detectionPlots.get(i); } - + /** * The number of possible detection plots for the data unit. */ public int getDetectionPlotCount(){ return detectionPlots.size(); } - + /** * Get the scale information. This holds the min and max values for the x and y axis along with other * axis related parameters. @@ -188,42 +186,57 @@ public abstract class DDDataInfo { public DetectionPlotDisplay getDetectionPlotDisplay() { return dDPlot; } - + /* * Sets the current detection plot to be shown. * @param index of the plot in the plot list. */ public void setCurrentDetectionPlot(int index) { - this.currentDetectionPlotIndex=index; - if (index>=0 && index=0 && index { if (currentDetectionPlotIndex<0 || currentDetectionPlotIndex>=detectionPlots.size()) return null; return detectionPlots.get(currentDetectionPlotIndex); } - - + + /** * Get the settings pane for this DDDataInfo. This holds plot specific settings. * @return @@ -243,7 +256,7 @@ public abstract class DDDataInfo { public PamBorderPane getSettingsPane(){ return settingsPane; } - + /** * Paint data into the graphics window. @@ -255,17 +268,17 @@ public abstract class DDDataInfo { * @param a flag with extra information - e.g. if this is a scroll bar paint or not. */ public void drawData(GraphicsContext g, Rectangle windowRect, DetectionPlotProjector projector, T pamDataUnit, int flag){ - + if (getCurrentDetectionPlot()==null) return; - + g.clearRect(0, 0, windowRect.getWidth(), windowRect.getHeight()); - + getCurrentDetectionPlot().setupAxis((PamDataUnit) pamDataUnit, this.getHardSampleRate(), projector); - + getCurrentDetectionPlot().paintPlot((PamDataUnit) pamDataUnit, g, windowRect, projector, flag); - + } - + /** * Paint data into the graphics window. @@ -276,8 +289,8 @@ public abstract class DDDataInfo { * @param newDataUnit - the data unit to plot. */ public void drawData(GraphicsContext g, Rectangle windowRect, DetectionPlotProjector projector, T pamDataUnit){ - - drawData( g, windowRect, projector, pamDataUnit, DetectionPlot.STANDARD_DRAW); + + drawData( g, windowRect, projector, pamDataUnit, DetectionPlot.STANDARD_DRAW); } @@ -286,23 +299,23 @@ public abstract class DDDataInfo { getCurrentDetectionPlot().setupAxis((PamDataUnit) pamDataUnit, this.getHardSampleRate(), projector); } -// /** -// * Gets a value for a specific data unit which should be in the -// * same units as the scale information. This will then be -// * converted into a plot position by the TDGraph. -// * @param pamDataUnit -// * @return data value or null if this data point should not be plotted. -// */ -// abstract public Double[] getYValue(PamDataUnit pamDataUnit); -// -// /** -// * Gets a value for a specific data unit which should be in the -// * same units as the scale information. This will then be -// * converted into a plot position by the TDGraph. -// * @param pamDataUnit -// * @return data value or null if this data point should not be plotted. -// */ -// abstract public Double[] getXValue(PamDataUnit pamDataUnit); - + // /** + // * Gets a value for a specific data unit which should be in the + // * same units as the scale information. This will then be + // * converted into a plot position by the TDGraph. + // * @param pamDataUnit + // * @return data value or null if this data point should not be plotted. + // */ + // abstract public Double[] getYValue(PamDataUnit pamDataUnit); + // + // /** + // * Gets a value for a specific data unit which should be in the + // * same units as the scale information. This will then be + // * converted into a plot position by the TDGraph. + // * @param pamDataUnit + // * @return data value or null if this data point should not be plotted. + // */ + // abstract public Double[] getXValue(PamDataUnit pamDataUnit); + } diff --git a/src/detectionPlotFX/layout/DDDataPane2.java b/src/detectionPlotFX/layout/DDDataPane2.java index b754f1cb..f73e289c 100644 --- a/src/detectionPlotFX/layout/DDDataPane2.java +++ b/src/detectionPlotFX/layout/DDDataPane2.java @@ -87,7 +87,6 @@ public class DDDataPane2 extends PamBorderPane { dataPlots.getItems().add(ddDataInfo.getDetectionPlot(i).getName()); - if (ddDataInfo.getCurrentDetectionPlot()==ddDataInfo.getDetectionPlot(i)) { index=i; } @@ -108,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 865eecb9..16120cee 100644 --- a/src/detectionPlotFX/layout/DetectionPlotDisplay.java +++ b/src/detectionPlotFX/layout/DetectionPlotDisplay.java @@ -37,7 +37,7 @@ import userDisplayFX.UserDisplayNodeParams; * @author Jamie Macaulay * */ -public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNodeFX { +public class DetectionPlotDisplay extends PamBorderPane { private static final double PREF_SETTINGS_WIDTH = 250; @@ -73,8 +73,7 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo /** * Pane which sits on the right hand side and */ - private PamBorderPane settingsHolder; - + private PamBorderPane settingsHolder; /** @@ -82,11 +81,7 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo */ private boolean enableSettingsButton = true; - /** - * Convenience reference to the settings css style - */ - String cssSettingsResource=PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS(); - + /** * The pane which allows users to settings specific to the type of display */ @@ -257,56 +252,23 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo return isViewer; } - - @Override - public String getName() { - return "Detection Display"; - } - - @Override - public Region getNode() { - return this; - } - - @Override - public void openNode() { - // TODO Auto-generated method stub - - } - - @Override - public boolean isStaticDisplay() { - return false; - } - - @Override - public boolean isResizeableDisplay() { - return true; - } - - @Override - public void closeNode() { - // TODO Auto-generated method stub - - } - - @Override - public void notifyModelChanged(int changeType) { - switch (changeType){ - case PamControllerInterface.INITIALIZATION_COMPLETE: - - break; - case PamControllerInterface.ADD_CONTROLLEDUNIT: - this.dataSettingsPane.notifyDataChange(); - break; - case PamControllerInterface.CHANGED_PROCESS_SETTINGS: - //this is were the data block may have been added. Need to add an observer to this data block to say when the thing has - //thing has a new detection. - this.dataSettingsPane.notifyDataChange(); - break; - } - - } +// @Override +// public void notifyModelChanged(int changeType) { +// switch (changeType){ +// case PamControllerInterface.INITIALIZATION_COMPLETE: +// +// break; +// case PamControllerInterface.ADD_CONTROLLEDUNIT: +// this.dataSettingsPane.notifyDataChange(); +// break; +// case PamControllerInterface.CHANGED_PROCESS_SETTINGS: +// //this is were the data block may have been added. Need to add an observer to this data block to say when the thing has +// //thing has a new detection. +// this.dataSettingsPane.notifyDataChange(); +// break; +// } +// +// } /** * Set the DataInfo for the display. @@ -393,16 +355,17 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo * 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()); @@ -422,6 +385,9 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo } 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); } } @@ -447,19 +413,25 @@ public class DetectionPlotDisplay extends PamBorderPane implements UserDisplayNo 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 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/RawDataOrder.java b/src/detectionPlotFX/plots/RawDataOrder.java index 5627e9ca..718bffed 100644 --- a/src/detectionPlotFX/plots/RawDataOrder.java +++ b/src/detectionPlotFX/plots/RawDataOrder.java @@ -186,7 +186,6 @@ public abstract class RawDataOrder { */ public boolean resetForLoad(long dataStart, long dataEnd, float sampleRate, int channel) { - // System.out.println("RESET FOR LOAD: "); count=0; this.dataStart=dataStart; this.dataEnd=dataEnd; @@ -194,6 +193,8 @@ public abstract class RawDataOrder { this.fftSampleRate=sampleRate; int dataFrame=(int) (sampleRate*((dataEnd-dataStart)/1000.)); + +// System.out.println("RESET FOR LOAD: " + dataFrame); if (dataFrame>=maxMb){ System.err.println("The raw data is way too big"); @@ -213,12 +214,13 @@ public abstract class RawDataOrder { } private int currentIndex=0; + /*** * Called whenever new raw data is acquired * @param dataUnit */ private void newRawData(RawDataUnit dataUnit) { - // System.out.println("New raw data " + count); +// System.out.println("New raw data " + count); // try{ if (PamUtils.hasChannel(dataUnit.getChannelBitmap(), channel)){ 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/plots/SpectrumPlot.java b/src/detectionPlotFX/plots/SpectrumPlot.java index bd7e8966..752130d7 100644 --- a/src/detectionPlotFX/plots/SpectrumPlot.java +++ b/src/detectionPlotFX/plots/SpectrumPlot.java @@ -1,6 +1,7 @@ package detectionPlotFX.plots; import Layout.PamAxis; +import PamUtils.Coordinate3d; import PamguardMVC.PamDataUnit; import detectionPlotFX.layout.DetectionPlot; import detectionPlotFX.layout.DetectionPlotDisplay; @@ -131,7 +132,7 @@ public abstract class SpectrumPlot implements Detection if (data ==null) return; - this.sR=sR; + this.sR=sR; int[] minmax = getAxisMinMaxSamples(plotProjector); @@ -155,21 +156,25 @@ public abstract class SpectrumPlot implements Detection /** * Get the minimum and maximum samples currently shown in the plot projector - * @param plotProjector - the pot projector. + * @param plotProjector - the plot projector. * @return the minimum and maximum samples */ private int[] getAxisMinMaxSamples(DetectionPlotProjector plotProjector) { int[] minmax = new int[2]; // System.out.println("Plot projector: " + plotProjector.getAxis(Side.TOP).getMinVal() + " " + -// plotProjector.getAxis(Side.TOP).getMaxVal()); - - minmax[0] = (int) ((plotProjector.getAxis(Side.TOP).getMinVal()/1000.)*sR); //this is in milliseconds - minmax[1] = (int) ((plotProjector.getAxis(Side.TOP).getMaxVal()/1000.)*sR); +// plotProjector.getAxis(Side.TOP).getMaxVal() + " sR: " + getSampleRate()); + minmax[0] = (int) ((plotProjector.getAxis(Side.TOP).getMinVal()/1000.)*getSampleRate()); //this is in milliseconds + minmax[1] = (int) ((plotProjector.getAxis(Side.TOP).getMaxVal()/1000.)*getSampleRate()); + return minmax; } + private double getSampleRate() { + return sR; + } + @Override public void paintPlot(D data, GraphicsContext gc, Rectangle rectangle, DetectionPlotProjector projector, int flag) { // this.lastDataUnit=data; @@ -293,13 +298,13 @@ public abstract class SpectrumPlot implements Detection */ private void drawSpectrum(GraphicsContext g2, Rectangle clipRect, double[][] clickLineData, DetectionPlotProjector projector){ - double xScale, yScale; + double yScale; double x0, y0, x1, y1; Rectangle r = clipRect; double scale = 1./(maxVal*1.1); - xScale = r.getWidth() / (clickLineData[0].length - 1); +// xScale = r.getWidth() / (clickLineData[0].length - 1); yScale = r.getHeight() / (maxVal * 1.1); double[] scaledDataX; @@ -315,12 +320,15 @@ public abstract class SpectrumPlot implements Detection scaledDataX = new double[clickLineData[iChan].length+2]; scaledDataY = new double[clickLineData[iChan].length+2]; scaledDataY[0]= r.getHeight() ; + Coordinate3d coord; for (int i = 1; i < clickLineData[iChan].length+1; i++) { - x1 = (i * xScale); - // y1 = r.height - (int) (yScale * clickLineData[iChan][i]); - //System.out.println("Hello Projector: " +projector); - y1 = projector.getCoord3d(0,clickLineData[iChan][i-1]*scale,0).y; +// x1 = (i * xScale); +// System.out.println("Freq: " + ((i-1)*1./clickLineData[iChan].length+1)*(getSampleRate()/2./1000.) + " " + (getSampleRate()/2./1000.) + " " + ((i-1)*1./clickLineData[iChan].length)); + + coord = projector.getCoord3d(((i-1)*1./clickLineData[iChan].length)*(getSampleRate()/2./1000.) ,clickLineData[iChan][i-1]*scale,0); // g2.strokeLine(x0, y0, x1, y1); + x1 = coord.x; + y1=coord.y; scaledDataX[i]=x1; scaledDataY[i]=y1; @@ -400,7 +408,7 @@ public abstract class SpectrumPlot implements Detection } lastCepChoice = spectrumPlotParams.plotCepstrum; //drawLogSpectrum(g,clipRect,clickLineData, eventLineData); - drawLogSpectrum(g,clipRect,clickLineData); + drawLogSpectrum(g,clipRect,clickLineData, projector); } } @@ -412,11 +420,11 @@ public abstract class SpectrumPlot implements Detection * @param eventLineData * @param clickLineData */ - public void drawLogSpectrum(GraphicsContext g2, Rectangle clipRect, double[][] clickLineData){ + public void drawLogSpectrum(GraphicsContext g2, Rectangle clipRect, double[][] clickLineData, DetectionPlotProjector projector){ Rectangle r = clipRect; double xScale, yScale, scaleLim; - int x0, y0, x1, y1; + double x0, y0, x1, y1; Color channelColour; // if (eventLineData!=null && isViewer==true && clickSpectrumParams.showEventInfo==true){ @@ -457,12 +465,16 @@ public abstract class SpectrumPlot implements Detection scaleLim = Math.abs(spectrumPlotParams.logRange); yScale = r.getHeight() / Math.abs(scaleLim); + Coordinate3d coord; for (int iChan = 0; iChan < logSpectrum.length; iChan++) { g2.setStroke(PamColorsFX.getInstance().getChannelColor(iChan)); x0 = 0; y0 = (int) (yScale * (maxLogVal-logSpectrum[iChan][0])); for (int i = 1; i < logSpectrum[iChan].length; i++) { x1 = (int) (i * xScale); + + coord = projector.getCoord3d(((i-1)*1./clickLineData[iChan].length)*(getSampleRate()/2./1000.) ,0,0); + x1 = coord.x; y1 = (int) (yScale * (maxLogVal-logSpectrum[iChan][i])); g2.strokeLine(x0, y0, x1, y1); x0 = x1; diff --git a/src/detectionPlotFX/plots/WaveformPlot.java b/src/detectionPlotFX/plots/WaveformPlot.java index 3b790802..5d0abe90 100644 --- a/src/detectionPlotFX/plots/WaveformPlot.java +++ b/src/detectionPlotFX/plots/WaveformPlot.java @@ -146,6 +146,7 @@ public abstract class WaveformPlot implements DetectionPl public void paintPlot(D pamDetection, GraphicsContext gc, Rectangle rectangle, DetectionPlotProjector plotProjector, int flag){ //if (currentDetection == pamDetection) return; currentDetection = pamDetection; + if (flag == DetectionPlot.SCROLLPANE_DRAW) { //drawing a datagram in the scroll pane. //System.out.println("WaveformPlot: Print scroll datagram: " + pamDetection+ " " + rectangle); @@ -166,6 +167,8 @@ public abstract class WaveformPlot implements DetectionPl private void paintScrollDataGram(D pamDetection, GraphicsContext gc, Rectangle clipRect, DetectionPlotProjector projector) { currentWaveform=getWaveform(pamDetection); + if (currentWaveform==null) return; + paintWaveform(currentWaveform, currentDetection.getSequenceBitmap(), gc, clipRect, 0, currentWaveform[0].length, log2Amplitude, null, true, false); @@ -180,6 +183,9 @@ public abstract class WaveformPlot implements DetectionPl */ private void forcePaintPlot(D pamDetection, GraphicsContext gc, Rectangle rectangle, DetectionPlotProjector projector){ currentWaveform=getWaveform(pamDetection); + + if (currentWaveform==null) return; + waveformPlotParams.invert=false; setYScale(); paintWaveform(currentWaveform, gc, rectangle, projector, null); diff --git a/src/detectionPlotFX/projector/DetectionPlotProjector.java b/src/detectionPlotFX/projector/DetectionPlotProjector.java index 05ac2ab2..960f61d1 100644 --- a/src/detectionPlotFX/projector/DetectionPlotProjector.java +++ b/src/detectionPlotFX/projector/DetectionPlotProjector.java @@ -21,13 +21,13 @@ public class DetectionPlotProjector extends GeneralProjector { /** - * The minimum limit of the scroll bar. + * The minimum limit of the scroll bar. In millis */ public double minScrollLimit = 0; /** - * The maximum limit of the scroll bar. i.e. the maximum value that can be scrolled to (e.g. the length of a waveform) + * The maximum limit of the scroll bar. i.e. the maximum value that can be scrolled to (e.g. the length of a waveform) In millis */ public double maxScrollLimit = 1; @@ -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/rawDDPlot/RawSpectrumPlot.java b/src/detectionPlotFX/rawDDPlot/RawSpectrumPlot.java index a2d6a7e0..ef98e221 100644 --- a/src/detectionPlotFX/rawDDPlot/RawSpectrumPlot.java +++ b/src/detectionPlotFX/rawDDPlot/RawSpectrumPlot.java @@ -54,30 +54,7 @@ public class RawSpectrumPlot extends SpectrumPlot { plotProjector.setEnableScrollBar(true); } - -// @Override -// public double[][] getPowerSpectrum(PamDataUnit data) { -// return ((RawDataHolder) data).getDataTransforms().getPowerSpectrum(((RawDataHolder) data).getWaveData()[0].length); -// } -// @Override -// public double[][] getCepstrum(PamDataUnit data) { -// return ((RawDataHolder) data).getDataTransforms().getCepstrum(((RawDataHolder) data).getWaveData()[0].length); -// //return data.getRawDataTransforms().getCepstrum(channel, cepLength) -// } - - - -// -// public ClickSpectrumPlot(DetectionPlotDisplay displayPlot) { -// super(displayPlot); -// } -// -// -// @Override -// public String getName() { -// return "Spectrum"; -// } -// + /** * New click added to the display. * @param newClick @@ -85,10 +62,13 @@ public class RawSpectrumPlot extends SpectrumPlot { */ private void newClick(PamDataUnit newClick, int bin1, int bin2, boolean forceRecalc){ - //System.out.println("Hello: RawSpectrumPlot"); - + //System.out.println("Hello: RawSpectrumPlot"); + RawDataHolder rawDataHolder = (RawDataHolder) newClick; + if (rawDataHolder.getDataTransforms()==null) return; + + int nChan = PamUtils.getNumChannels(newClick.getChannelBitmap()); //if we don;t need to recalc the spectrum then don't! @@ -153,7 +133,6 @@ public class RawSpectrumPlot extends SpectrumPlot { // @Override public double[][] getPowerSpectrum(PamDataUnit newClick, int min, int max) { - //System.out.println("Get power spectrum: min: " + min + " max: " + max); newClick(newClick,min, max, true); return spectrum; } @@ -167,13 +146,15 @@ public class RawSpectrumPlot extends SpectrumPlot { @Override public void paintPlot(PamDataUnit data, GraphicsContext gc, Rectangle rectangle, DetectionPlotProjector projector, int flag) { -// this.lastDataUnit=data; - //System.out.println("Paint plot projector: "+ projector); + + if (((RawDataHolder) data).getWaveData()==null) { + gc.clearRect(0, 0, rectangle.getWidth(), rectangle.getHeight()); + return; + } + if (flag== DetectionPlot.SCROLLPANE_DRAW) { - double[][] waveformTemp = ((RawDataHolder) data).getWaveData(); -// System.out.println("Spectrum plot: " + " " + projector.getMinScrollLimit() + " " -// + projector.getMaxScrollLimit() + " " + projector.getAxis(Side.TOP).getMaxVal() + " wvfrm: " + WaveformPlot.getYScale(waveformTemp)); + double[][] waveformTemp = ((RawDataHolder) data).getWaveData(); WaveformPlot.paintWaveform(waveformTemp, data.getSequenceBitmap(), gc, rectangle, 0, waveformTemp[0].length, WaveformPlot.getYScale(waveformTemp), null, true, false); diff --git a/src/detectionPlotFX/rawDDPlot/RawWaveformPlot.java b/src/detectionPlotFX/rawDDPlot/RawWaveformPlot.java index 3318d8ac..4deef0f3 100644 --- a/src/detectionPlotFX/rawDDPlot/RawWaveformPlot.java +++ b/src/detectionPlotFX/rawDDPlot/RawWaveformPlot.java @@ -32,7 +32,6 @@ public class RawWaveformPlot extends WaveformPlot{ return rawDetection.getDataTransforms().getFilteredWaveData(super.getWaveformPlotParams().waveformFilterParams.clone()); } else { -// System.out.println("Get normal waveform. "); return rawDetection.getWaveData(); } } 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..ebfca3d6 100644 --- a/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java +++ b/src/detectionPlotFX/whistleDDPlot/WhistleSettingsPane.java @@ -1,21 +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 +17,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 +38,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/detectiongrouplocaliser/DetectionGroupSummary.java b/src/detectiongrouplocaliser/DetectionGroupSummary.java index ff86d16a..db461e89 100644 --- a/src/detectiongrouplocaliser/DetectionGroupSummary.java +++ b/src/detectiongrouplocaliser/DetectionGroupSummary.java @@ -39,7 +39,7 @@ public class DetectionGroupSummary { private OverlayMark overlayMark; /** - * Thye currently selected or focused data unit; + * The currently selected or focused data unit; */ private int focusedUnitIndex=0; diff --git a/src/difar/trackedGroups/TrackedGroupProcess.java b/src/difar/trackedGroups/TrackedGroupProcess.java index 766d82c8..73349ae2 100644 --- a/src/difar/trackedGroups/TrackedGroupProcess.java +++ b/src/difar/trackedGroups/TrackedGroupProcess.java @@ -1,32 +1,22 @@ package difar.trackedGroups; import java.util.ArrayList; -import java.util.ListIterator; - -import generalDatabase.DBControl; import generalDatabase.DBControlUnit; import generalDatabase.PamConnection; -import generalDatabase.SQLLogging; -import difar.DIFARCrossingInfo; -import difar.DIFARTargetMotionInformation; import difar.DifarControl; import difar.DifarDataUnit; import pamScrollSystem.PamScroller; -import pamScrollSystem.ViewLoadObserver; import targetMotionModule.TargetMotionResult; import targetMotionModule.algorithms.Simplex2D; import Array.ArrayManager; import Array.StreamerDataUnit; import PamController.PamController; import PamUtils.LatLong; -import PamUtils.PamCalendar; -import PamUtils.PamUtils; import PamView.symbol.StandardSymbolManager; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.PamObservable; import PamguardMVC.PamProcess; -import autecPhones.AutecGraphics; /** * Subscribe to DifarDataBlocks and assign new bearings to groups of whales diff --git a/src/effortmonitor/EffortDataBlock.java b/src/effortmonitor/EffortDataBlock.java index 8933aa28..6b7fac1c 100644 --- a/src/effortmonitor/EffortDataBlock.java +++ b/src/effortmonitor/EffortDataBlock.java @@ -51,7 +51,7 @@ public class EffortDataBlock extends PamDataBlock { long origEnd = offlineDataLoadInfo.getEndMillis(); long dataStart = Math.min(offlineDataLoadInfo.getStartMillis(), dmc.getFirstTime() - 360000000L); long dataEnd = Math.max(offlineDataLoadInfo.getEndMillis(), dmc.getLastTime() + 360000000L); - System.out.printf("Load effort data from %s to %s\n", PamCalendar.formatDBDateTime(dataStart), PamCalendar.formatDBDateTime(dataEnd)); +// System.out.printf("Load effort data from %s to %s\n", PamCalendar.formatDBDateTime(dataStart), PamCalendar.formatDBDateTime(dataEnd)); offlineDataLoadInfo.setStartMillis(dataStart); offlineDataLoadInfo.setEndMillis(dataEnd); } diff --git a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVDataUnitExport.java b/src/export/CSVExport/CSVDataUnitExport.java similarity index 93% rename from src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVDataUnitExport.java rename to src/export/CSVExport/CSVDataUnitExport.java index 827a367c..3b247e98 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/CSVExport/CSVDataUnitExport.java +++ b/src/export/CSVExport/CSVDataUnitExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.CSVExport; +package export.CSVExport; import org.apache.commons.csv.CSVPrinter; diff --git a/src/export/CSVExport/CSVExportManager.java b/src/export/CSVExport/CSVExportManager.java new file mode 100644 index 00000000..bf011e30 --- /dev/null +++ b/src/export/CSVExport/CSVExportManager.java @@ -0,0 +1,44 @@ +package export.CSVExport; + +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"; + } + + @Override + public void close() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/export/ExportOptions.java b/src/export/ExportOptions.java new file mode 100644 index 00000000..0ce6a1f8 --- /dev/null +++ b/src/export/ExportOptions.java @@ -0,0 +1,113 @@ +package export; + +import java.io.Serializable; +import javax.swing.JFrame; + +import PamController.PamControlledUnitSettings; +import PamController.PamSettingManager; +import PamController.PamSettings; +import PamController.StorageParameters; +import export.layoutFX.ExportParams; +import export.swing.ExportProcessDialog; + +/** + * Manages opening settings dialog and saving settings for both FX and Swing GUI's + * + * @author Jamie Macaulay + * + */ +public class ExportOptions implements PamSettings { + + private static ExportOptions singleInstance; + + /** + * Parameters for the exporter. + */ + private ExportParams storageParameters = new ExportParams(); + + /** + * Swing dialog for exporting data. + */ + private ExportProcessDialog exportProcessDialog; + + /** + * Reference to the export manager. This handles exporting data units. + */ + private PamExporterManager exportManager = new PamExporterManager(); + + private ExportOptions() { + PamSettingManager.getInstance().registerSettings(this); + } + + public static ExportOptions getInstance() { + if (singleInstance == null) { + singleInstance = new ExportOptions(); + } + return singleInstance; + } + + + /** + * Show the swing dialog. + * @param parentFrame - the parent frame. + * @return true if settings are OK on close + */ + public boolean showDialog(JFrame parentFrame) { + + if (exportProcessDialog==null) { + exportProcessDialog= new ExportProcessDialog(exportManager); + } + this.exportProcessDialog.showOfflineDialog(parentFrame, this.storageParameters); + +// ExportParams newParams = StorageOptionsDialog.showDialog(parentFrame, storageParameters); +// if (newParams != null) { +// storageParameters = newParams.clone(); +// return true; +// } +// else { +// return false; +// } + + return true; + } + + @Override + public Serializable getSettingsReference() { + return storageParameters; + } + + @Override + public long getSettingsVersion() { + return StorageParameters.serialVersionUID; + } + + @Override + public String getUnitName() { + return "PAMGUARD Export Options"; + } + + @Override + public String getUnitType() { + return "PAMGUARD Export Options"; + } + + @Override + public boolean restoreSettings( + PamControlledUnitSettings pamControlledUnitSettings) { + storageParameters = ((ExportParams) pamControlledUnitSettings.getSettings()).clone(); + return true; + } + + + public void setStorageParameters(ExportParams storageParameters) { + this.storageParameters = storageParameters; + } + + /** + * Get storage parameters settings. + * @return the storage paramters settings + */ + public ExportParams getStorageParameters() { + return storageParameters; + } +} \ No newline at end of file diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLAnnotationsManager.java b/src/export/MLExport/MLAnnotationsManager.java similarity index 72% rename from src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLAnnotationsManager.java rename to src/export/MLExport/MLAnnotationsManager.java index 4ca4d900..4b42d476 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLAnnotationsManager.java +++ b/src/export/MLExport/MLAnnotationsManager.java @@ -1,11 +1,8 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; - - - -import com.jmatio.types.MLStructure; +package export.MLExport; import PamguardMVC.PamDataUnit; import annotation.DataAnnotationType; +import us.hebi.matlab.mat.types.Struct; /** * Adds annotations to data units. @@ -16,16 +13,16 @@ import annotation.DataAnnotationType; public class MLAnnotationsManager { /** - * Add annotations to a matlab structure. + * Add annotations to a MATLAB structure. * @param mlStruct + * @param index * @param dataUnit */ - public void addAnnotations(MLStructure mlStruct, PamDataUnit dataUnit) { + public void addAnnotations(Struct mlStruct, int index, PamDataUnit dataUnit) { for (int i=0; i getUnitClass() { + return ClickDetection.class; + } + + @Override + public String getName() { + return "clicks"; + } + +} diff --git a/src/export/MLExport/MLDataUnitExport.java b/src/export/MLExport/MLDataUnitExport.java new file mode 100644 index 00000000..8c67bbaa --- /dev/null +++ b/src/export/MLExport/MLDataUnitExport.java @@ -0,0 +1,118 @@ +package export.MLExport; + +import org.jamdev.jdl4pam.utils.DLMatFile; + +import PamUtils.PamCalendar; +import PamguardMVC.PamDataUnit; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; + +/** + * Export a detection to MATLAB + * @author Jamie Macaulay + * + */ +public abstract class MLDataUnitExport> { + + /** + * Annotations manager for nadling adding annotations to data units. + */ + private MLAnnotationsManager mlAnnotationsManager; + + public MLDataUnitExport() { + this.mlAnnotationsManager = new MLAnnotationsManager(); + } + + /** + * Create a MATLAB structure which contains all information for a data unit. + * @param dataUnit - the data unit to convert to a MATLAB structure + * @return detection data MATLAB structure ready to be exported to a .mat file or added to a ArrayList. + */ + public Struct detectionToStruct(Struct mlStruct, T dataUnit, int index){ + + Mat5.newScalar(dataUnit.getTimeMilliseconds()); + + //all the default stuff every data unit has. + Matrix millis = Mat5.newScalar(dataUnit.getTimeMilliseconds()); + + //the channel bit map. + Matrix channelMap = Mat5.newScalar(dataUnit.getChannelBitmap()); + + //the sequence map, or 0 if there is no sequence number + Matrix sequenceMap = Mat5.newScalar(0); + if (dataUnit.getSequenceBitmapObject()!=null) { + sequenceMap = Mat5.newScalar(dataUnit.getSequenceBitmapObject()); + } + + //UID for the detection. + Matrix UID = Mat5.newScalar(dataUnit.getUID()); + + //the start sample. + Matrix startSample = Mat5.newScalar(dataUnit.getStartSample()); + + //the duration of the detection in samples. + Matrix sampleDuration = Mat5.newScalar(dataUnit.getSampleDuration()); + + Matrix freqLimits = DLMatFile.array2Matrix(dataUnit.getBasicData().getFrequency()); + + //the frequency limits. + + //create the structure. + mlStruct.set("millis", index, millis); + mlStruct.set("channelMap", index, channelMap); + mlStruct.set("sequenceMap",index, sequenceMap); + mlStruct.set("UID",index, UID); + mlStruct.set("startSample", index,startSample); + mlStruct.set("sampleDuration",index, sampleDuration); + mlStruct.set("freqLimits", index,freqLimits); + + //there may be no delay info + if (dataUnit.getBasicData().getTimeDelaysSeconds()!=null && dataUnit.getBasicData().getTimeDelaysSeconds().length>=1){ + Matrix numTimeDelays = Mat5.newScalar(dataUnit.getBasicData().getTimeDelaysSeconds().length); + Matrix timeDelays = DLMatFile.array2Matrix(dataUnit.getBasicData().getTimeDelaysSeconds()); + mlStruct.set("numTimeDelays", index, numTimeDelays); + mlStruct.set("timeDelays", index,timeDelays); + } + else { + mlStruct.set("numTimeDelays", index, Mat5.newScalar(0)); + //mlStruct.setField("timeDelays", new MLEmptyArray(), index); //22-02-2018 - this caused an exception when saving... + mlStruct.set("timeDelays", index, Mat5.newScalar(0)); + } + + //MATLAB date number. + double datenumMT = PamCalendar.millistoDateNum(dataUnit.getTimeMilliseconds()); + Matrix date = Mat5.newScalar(datenumMT); + + mlStruct.set("date", index, date); + + //add detection specific data + mlStruct= addDetectionSpecificFields(mlStruct, index, dataUnit); + + this.mlAnnotationsManager.addAnnotations(mlStruct, index, dataUnit); + + return mlStruct; + } + + /** + *Add detection specific fields to a structure. + *@param structure containing all generic info from PamDataUnit + *@param the data unit. + */ + public abstract Struct addDetectionSpecificFields(Struct mlStruct, int index, T dataUnit); + + /** + * Get the unit class + * @return + */ + public abstract Class getUnitClass(); + + /** + * Get the name of the structure + * @return + */ + public abstract String getName(); + + + +} diff --git a/src/export/MLExport/MLDetectionsManager.java b/src/export/MLExport/MLDetectionsManager.java new file mode 100644 index 00000000..fe05cd39 --- /dev/null +++ b/src/export/MLExport/MLDetectionsManager.java @@ -0,0 +1,275 @@ +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.PamArrayUtils; +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; +import us.hebi.matlab.mat.types.Struct; +import us.hebi.matlab.mat.util.Casts; + + +/** + * Handles the conversion of data units into MATLAB structures. + * @author Jamie Macaulay + * + */ +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(); + + private Sink sink; + + private File currentFile; + + + public MLDetectionsManager(){ + mlDataUnitsExport.add(new MLClickExport()); + 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 = PamArrayUtils.getMinTimeMillis(dataUnits); + + //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; + + //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 + e.printStackTrace(); + return false; + } + + } + + /** + * Check whether there are compatible data units to be exported. + * @param dataUnits - the data unit list + * @return true if MATLAB export is possible for the current data units. + */ + public boolean hasCompatibleUnits(List dataUnits) { + //first need to figure out how many data units there are. + for (int j=0; j dataUnits){ + + //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 + //(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; i0) { + list.set(mlDataUnitsExport.get(i).getName(),mlStructure); + list.set(mlDataUnitsExport.get(i).getName()+"_sR", Mat5.newScalar(sampleRate)); + } + } + + //now ready to save. + return list; + + } + + @Override + public String getFileExtension() { + return extension; + } + + @Override + public String getIconString() { + return "file-matlab"; + } + + @Override + 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()); + + mlStruct.set("triggerMap", 0, triggerMap); + mlStruct.set("triggerMap", 1, triggerMap); + mlStruct.set("triggerMap", 2, triggerMap); + + matFile.addArray("test_struct", mlStruct); + + //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(); + + } 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/MLExport/MLRawExport.java b/src/export/MLExport/MLRawExport.java new file mode 100644 index 00000000..9271ec9f --- /dev/null +++ b/src/export/MLExport/MLRawExport.java @@ -0,0 +1,70 @@ +package export.MLExport; + +import org.jamdev.jdl4pam.utils.DLMatFile; + +import PamUtils.PamUtils; +import PamguardMVC.PamDataUnit; +import PamguardMVC.RawDataHolder; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; + +/** + * Export data for any data unit which implements raw data holder. + * @author Jamie Macaulay + * + */ +public class MLRawExport extends MLDataUnitExport{ + + @Override + public Struct addDetectionSpecificFields(Struct mlStruct, int index, PamDataUnit dataUnit) { + + RawDataHolder rawDataHolder = (RawDataHolder) dataUnit; + + //the waveform + Matrix wave = DLMatFile.array2Matrix(rawDataHolder.getWaveData()); + + + //the number of channels + Matrix nChan = Mat5.newScalar(PamUtils.getNumChannels(dataUnit.getChannelBitmap())); + + //the duration - repeat of duration in main data unit. Keeping here so strcut is the same as the struct from binary files + Matrix duration = Mat5.newScalar(dataUnit.getSampleDuration()); + + mlStruct.set("nChan", index, nChan); + mlStruct.set("duration",index, duration); + mlStruct.set("wave", index, wave); + + Matrix angles; + Matrix angleErrors; + if (dataUnit.getLocalisation()!=null) { + //bearing angles + angles = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngles() == null ? new double[] {0.} : dataUnit.getLocalisation().getAngles()); + //angle errors + angleErrors = DLMatFile.array2Matrix(dataUnit.getLocalisation().getAngleErrors() == null ? new double[] {0.} : dataUnit.getLocalisation().getAngleErrors()); + } + else { + //bearing angles + angles = Mat5.newScalar(0.); + //angle errors + angleErrors = Mat5.newScalar(0.); + } + + mlStruct.set("angles", index, angles); + mlStruct.set("angleErrors", index, angleErrors); + + + return mlStruct; + } + + @Override + public Class getUnitClass() { + return RawDataHolder.class; + } + + @Override + public String getName() { + return "raw_data_units"; + } + +} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLWhistleMoanExport.java b/src/export/MLExport/MLWhistleMoanExport.java similarity index 57% rename from src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLWhistleMoanExport.java rename to src/export/MLExport/MLWhistleMoanExport.java index 60c5e025..124628ca 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/MLExport/MLWhistleMoanExport.java +++ b/src/export/MLExport/MLWhistleMoanExport.java @@ -1,45 +1,45 @@ -package dataPlotsFX.overlaymark.menuOptions.MLExport; +package export.MLExport; -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLInt32; -import com.jmatio.types.MLInt64; -import com.jmatio.types.MLStructure; +import org.jamdev.jdl4pam.utils.DLMatFile; import PamUtils.PamArrayUtils; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; import whistlesAndMoans.ConnectedRegionDataUnit; import whistlesAndMoans.SliceData; public class MLWhistleMoanExport extends MLDataUnitExport { @Override - public MLStructure addDetectionSpecificFields(MLStructure mlStruct, ConnectedRegionDataUnit dataUnit, int index) { + public Struct addDetectionSpecificFields(Struct mlStruct, int index, ConnectedRegionDataUnit dataUnit) { //date //MLInt64 date = new MLInt64(null, new Long[]{dataUnit.getTimeMilliseconds()}, 1); //nSlices int - MLInt32 nSlices = new MLInt32(null, new Integer[]{dataUnit.getConnectedRegion().getNumSlices()}, 1); + Matrix nSlices = Mat5.newScalar(dataUnit.getConnectedRegion().getNumSlices()); //list of structures: sliceNumber int, nPeaks int, peakData - MLStructure sliceDataStruct = createSliceStruct(dataUnit); + Struct sliceDataStruct = createSliceStruct(dataUnit); int[][] contourData = calcPeakContourWidths( dataUnit); //contour int[] - MLInt32 contour = new MLInt32(null, new int[][]{contourData[0]}); + Matrix contour = DLMatFile.array2Matrix(new int[][]{contourData[0]}); //contour width double[] - MLInt32 contourWidth = new MLInt32(null, new int[][]{contourData[1]}); + Matrix contourWidth = DLMatFile.array2Matrix(new int[][]{contourData[1]}); //mean width - MLDouble meanWidth = new MLDouble(null, new Double[]{PamArrayUtils.mean(contourData[0])}, 1); + Matrix meanWidth = DLMatFile.array2Matrix(new double[]{PamArrayUtils.mean(contourData[0])}); - mlStruct.setField("nSlices", nSlices, index); - mlStruct.setField("sliceData", sliceDataStruct, index); - mlStruct.setField("contour", contour, index); - mlStruct.setField("contourWidth", contourWidth, index); - mlStruct.setField("meanWidth", meanWidth, index); + mlStruct.set("nSlices", index, nSlices); + mlStruct.set("sliceData", index, sliceDataStruct); + mlStruct.set("contour", index, contour); + mlStruct.set("contourWidth", index, contourWidth); + mlStruct.set("meanWidth", index, meanWidth); return mlStruct; } @@ -78,14 +78,16 @@ public class MLWhistleMoanExport extends MLDataUnitExport dataUnits, boolean append); + + /** + * Get the extension for the output file type + * @return the extension for the file type e.g. "mat" + */ + public String getFileExtension(); + + /** + * Get the ikonli icon string for the exporter. + * @return the ikon string. + */ + public String getIconString(); + + /** + * Get the name of the exporter + * @return + */ + public String getName(); + + /** + * Clsoe the exporter + */ + public void close(); + + +} diff --git a/src/export/PamExporterManager.java b/src/export/PamExporterManager.java new file mode 100644 index 00000000..61631f1a --- /dev/null +++ b/src/export/PamExporterManager.java @@ -0,0 +1,180 @@ +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; +import export.wavExport.WavFileExportManager; + +/** + * Exports data to external files. Manages the file sizes and creates data buffers for + * offline processing. + */ +public class PamExporterManager { + + /** + * The number of data units to save before saving. This prevents too much being stored in memory + */ + private static int BUFFER_SIZE = 10000; + + /** + * Keep the file size to around 1GB per file + */ + private static int MAX_FILE_SIZE_MB = 1024; + + + /** + * A buffer of data units so we are not opening a file everytime a new data is passed to the export. + */ + private ArrayList dataUnitBuffer = new ArrayList(); + + /** + * A list of the avilable exporters. + */ + private ArrayList pamExporters; + + /** + * The current file. + */ + private File currentFile; + + /** + * 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(); + + //add the MATLAB export + 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, boolean force) { + boolean exportOK = true; + + if (dataUnit==null) { + if (force) { + 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 file is null or too large create another a file for saving. + 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 + newFileName + + "." + pamExporters.get(exportParams.exportChoice).getFileExtension()); + + currentFile = new File(fileName); + } + + dataUnitBuffer.add(dataUnit); + + 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(); + } + + return exportOK; + + } + + public void close() { + pamExporters.get(exportParams.exportChoice).close(); + + } + + /** + * Check whether the current file is greater than the maximum allowed file size. + * @param currentFile2 - the current file + * @return true of greater than or equal to the maximum file size. + */ + private boolean isFileSizeMax(File currentFile2) { + return getFileSizeMegaBytes(currentFile2) >= MAX_FILE_SIZE_MB; + } + + /** + * Get the file size in MegaBytes + * @param file + * @return + */ + 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; + } + + return false; + } + + /** + * Get the number of data exporters. e.g. MAT files, CSV, Wav etc. + * @return the number of data exporters + */ + public int getNumExporters() { + return pamExporters.size(); + } + + /** + * Get a specific exporter. + * @param i - the index of the exporter. + * @return the exporter. + */ + public PamDataUnitExporter getExporter(int i) { + return pamExporters.get(i); + + } + + public void setCurrentFile(File file) { + this.currentFile=file; + + } + + public ExportParams getExportParams() { + return exportParams; + } + + public void setExportParams(ExportParams currentParams) { + exportParams=currentParams; + + } + + + + +} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RClickExport.java b/src/export/RExport/RClickExport.java similarity index 89% rename from src/dataPlotsFX/overlaymark/menuOptions/RExport/RClickExport.java rename to src/export/RExport/RClickExport.java index 866c4edd..8730c118 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RClickExport.java +++ b/src/export/RExport/RClickExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.RExport; +package export.RExport; import org.renjin.sexp.DoubleArrayVector; import org.renjin.sexp.ListVector.NamedBuilder; @@ -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/dataPlotsFX/overlaymark/menuOptions/RExport/RDataUnitExport.java b/src/export/RExport/RDataUnitExport.java similarity index 97% rename from src/dataPlotsFX/overlaymark/menuOptions/RExport/RDataUnitExport.java rename to src/export/RExport/RDataUnitExport.java index ecf6df5f..b57d8167 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RDataUnitExport.java +++ b/src/export/RExport/RDataUnitExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.RExport; +package export.RExport; import org.renjin.sexp.DoubleArrayVector; import org.renjin.sexp.ListVector; diff --git a/src/export/RExport/RExportManager.java b/src/export/RExport/RExportManager.java new file mode 100644 index 00000000..a6cf1add --- /dev/null +++ b/src/export/RExport/RExportManager.java @@ -0,0 +1,263 @@ +package export.RExport; + +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.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; + +/** + * Handles exporting pam data units into an rdata. + * @author Jamie Macaulay + * + */ +@SuppressWarnings("rawtypes") +public class RExportManager implements PamDataUnitExporter { + + /** + * + * All the possible RDataUnit export classes. + */ + ArrayList rDataExport = new ArrayList(); + + private File currentFileName ; + + + private Builder allData; + + + public RExportManager(){ + /***Add more options here to export data units****/ + 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) { + + /** + * 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. + */ + // 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 { + + FileOutputStream fos = new FileOutputStream(currentFileName); + GZIPOutputStream zos = new GZIPOutputStream(fos); + + RDataWriter writer = new RDataWriter(context, zos); + + writer.save(allData.build()); + writer.close(); + zos.close(); + } + catch (IOException e1) { + e1.printStackTrace(); + } + + } + + /** + * Check whether there are compatible data units to be exported. + * @param dataUnits - the data unit list + * @return true if r export is possible for the current data units. + */ + public boolean hasCompatibleUnits(List dataUnits) { + //first need to figure out how many data units there are. + for (int j=0; j 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. + + 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; i0) { + + 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()); + } + + } + + RData rData = new RData(); + rData.rData=allData; + rData.dataUnitTypes=dataUnitTypes; + + //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 which were saved. + */ + public ArrayList dataUnitTypes; + } + + + + @Override + public String getFileExtension() { + return "RData"; + } + + @Override + public String getIconString() { + return "file-r"; + } + + @Override + public String getName() { + return "R data"; + } + + + @Override + public void close() { + if (allData!=null) { + writeRFile(); + } + } + + + +} diff --git a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RRawExport.java b/src/export/RExport/RRawExport.java similarity index 84% rename from src/dataPlotsFX/overlaymark/menuOptions/RExport/RRawExport.java rename to src/export/RExport/RRawExport.java index 1689b381..24f5abaa 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RRawExport.java +++ b/src/export/RExport/RRawExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.RExport; +package export.RExport; import org.renjin.sexp.DoubleArrayVector; import org.renjin.sexp.Vector; @@ -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 @@ -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/dataPlotsFX/overlaymark/menuOptions/RExport/RWhistleExport.java b/src/export/RExport/RWhistleExport.java similarity index 96% rename from src/dataPlotsFX/overlaymark/menuOptions/RExport/RWhistleExport.java rename to src/export/RExport/RWhistleExport.java index fd643a59..d378af8d 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/RExport/RWhistleExport.java +++ b/src/export/RExport/RWhistleExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.RExport; +package export.RExport; import org.renjin.sexp.ListVector.NamedBuilder; diff --git a/src/export/layoutFX/ExportParams.java b/src/export/layoutFX/ExportParams.java new file mode 100644 index 00000000..a4c5dbdc --- /dev/null +++ b/src/export/layoutFX/ExportParams.java @@ -0,0 +1,44 @@ +package export.layoutFX; + +import java.io.Serializable; + + +/** + * Parameter for the exporter. + * @author Jamie Macaulay + * + */ +public class ExportParams implements Serializable, Cloneable { + + /** + * + */ + private static final long serialVersionUID = 2L; + + /** + * The index of the export choice. + */ + public int exportChoice = 0; + + /** + * The folder to save to. + */ + public String folder = System.getProperty("user.home"); + + /** + * The maximum file size in Megabytes + */ + public Double maximumFileSize = 1000.0; + + @Override + public ExportParams clone() { + try { + ExportParams newP = (ExportParams) super.clone(); + return newP; + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/export/layoutFX/ExporterPane.java b/src/export/layoutFX/ExporterPane.java new file mode 100644 index 00000000..5c923a61 --- /dev/null +++ b/src/export/layoutFX/ExporterPane.java @@ -0,0 +1,53 @@ +package export.layoutFX; + +import PamController.SettingsPane; +import javafx.scene.Node; +import javafx.scene.control.Label; +import pamViewFX.fxNodes.PamBorderPane; + +/** + * Create a pane to export data. + * @author Jamie Macaulay + * + */ +public class ExporterPane extends SettingsPane{ + + PamBorderPane mainPane; + + public ExporterPane(Object ownerWindow) { + super(ownerWindow); + mainPane = new PamBorderPane(); + + mainPane.setTop(new Label("Hello")); + mainPane.setCenter(new Label("Hello")); + } + + @Override + public ExportParams getParams(ExportParams currParams) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setParams(ExportParams input) { + // TODO Auto-generated method stub + + } + + @Override + public String getName() { + return "Export Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/export/swing/ExportProcessDialog.java b/src/export/swing/ExportProcessDialog.java new file mode 100644 index 00000000..fd62ec6e --- /dev/null +++ b/src/export/swing/ExportProcessDialog.java @@ -0,0 +1,378 @@ +package export.swing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Window; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JSpinner; +import javax.swing.JSpinner.DefaultEditor; +import javax.swing.JTextField; +import javax.swing.JToggleButton; +import javax.swing.SpinnerListModel; +import javax.swing.SwingConstants; +import javax.swing.border.TitledBorder; + +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.fileicons.FileIcons; +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; +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. + * + * @author Jamie Macaulay + * + */ +public class ExportProcessDialog { + + + /** + * The offline task group + */ + private OfflineTaskGroup dlOfflineGroup; + + + private ExportOLDialog mtOfflineDialog; + + /** + * Reference to the export manager. + */ + private PamExporterManager exportManager; + + /** + * The current paramters. + */ + private ExportParams currentParams; + + public ExportProcessDialog(PamExporterManager exportManager) { + //create the offline task group. + this.exportManager=exportManager; + dlOfflineGroup = new ExportTaskGroup("Export data"); + } + + + public void createExportGroup() { + + //clear current tasks. + dlOfflineGroup.clearTasks(); + + //go through every data block we have and check if we can export the data units... + ArrayList dataBlocks= PamController.getInstance().getDataBlocks(); + + for (int i=0; i { +// System.out.println(actionEvent.getActionCommand() + " Selected"); + //TODO set the buttons to be disabled or enabled. + enableTasks(getExportSelection()); + }; + + exportButtons = new JToggleButton[exportManager.getNumExporters()]; + for (int i = 0; i < exportManager.getNumExporters(); i++) { + JToggleButton b = new JToggleButton(); + b.setToolTipText("Export to " + exportManager.getExporter(i).getName() + " file (" + exportManager.getExporter(i).getFileExtension() + ")"); + + FontIcon icon = FontIcon.of(getIconFromString(exportManager.getExporter(i).getIconString())); + icon.setIconSize(25); + icon.setIconColor(Color.DARK_GRAY); + + b.setIcon(icon); + + b.addActionListener(listener); + + exportButtons[i]=b; + buttonGroup.add(b); + buttonPanel.add(b); + } + + + PamPanel p = new PamPanel(new GridBagLayout()); + GridBagConstraints c = new PamGridBagContraints(); + c.gridwidth = 3; + c.gridx = 0; + c.gridy = 0; + + addComponent(p, exportTo = new JTextField(), c); + exportTo.setMinimumSize(new Dimension(180, 25)); + exportTo.setPreferredSize(new Dimension(180, 25)); + + c.gridx +=3; + c.gridwidth = 1; + PamButton button = new PamButton("Browse..."); + + fc = new PamFileChooser(); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + button.addActionListener((action)->{ + int returnVal = fc.showSaveDialog(this); + if(returnVal == JFileChooser.APPROVE_OPTION) { + File yourFolder = fc.getSelectedFile(); + exportTo.setText(yourFolder.getAbsolutePath()); + exportTo.setToolTipText(yourFolder.getAbsolutePath()); + } + }); + + addComponent(p, button, c); + + 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(); + } + + + public void setParams(ExportParams params) { + if (params ==null) currentParams = new ExportParams(); + currentParams = params.clone(); + + buttonGroup.clearSelection(); + exportButtons[params.exportChoice].setSelected(true); + + exportTo.setText(currentParams.folder); + + spinner.setValue(currentParams.maximumFileSize); + } + + + + } + + + class ExportTaskGroup extends OfflineTaskGroup{ + + public ExportTaskGroup(String settingsName) { + super(null, settingsName); + // TODO Auto-generated constructor stub + + } + + @Override + public String getUnitType() { + return "Export Data"; + } + } + + + + + + + + + +} \ No newline at end of file diff --git a/src/export/swing/ExportTask.java b/src/export/swing/ExportTask.java new file mode 100644 index 00000000..665d9ded --- /dev/null +++ b/src/export/swing/ExportTask.java @@ -0,0 +1,113 @@ +package export.swing; + +import PamController.PamController; +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import PamguardMVC.dataSelector.DataSelectDialog; +import PamguardMVC.dataSelector.DataSelector; +import dataMap.OfflineDataMapPoint; +import export.PamExporterManager; +import offlineProcessing.OfflineTask; + +/** + * Export data to a file type. + * + * @author Jamie Macaulay + * + */ +public class ExportTask extends OfflineTask>{ + + /** + * Reference to the data exporter which manages exporting of data. + */ + private PamExporterManager exporter; + + /** + * The data selector for the data block + */ + 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); + + + } + + @Override + public String getName() { + return this.getDataBlock().getDataName(); + } + + @Override + public boolean processDataUnit(PamDataUnit dataUnit) { + 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() { + System.out.println("EXPORTER: loaded data complete"); + + //force the exporter so save any renaming data units in the buffer + exporter.exportDataUnit(null, true); + exporter.close(); + exporter.setCurrentFile(null); + + } + /** + * task has settings which can be called + * @return true or false + */ + public boolean hasSettings() { + return dataSelector!=null; + } + + /** + * Call any task specific settings + * @return true if settings may have changed. + */ + public boolean callSettings() { + + dataSelector.getDialogPanel().setParams(); + + DataSelectDialog dataSelectDialog = new DataSelectDialog(PamController.getMainFrame(), + this.getDataBlock(), dataSelector, null); + return dataSelectDialog.showDialog(); + + } + + /** + * 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/dataPlotsFX/overlaymark/menuOptions/wavExport/RawHolderWavExport.java b/src/export/wavExport/RawHolderWavExport.java similarity index 90% rename from src/dataPlotsFX/overlaymark/menuOptions/wavExport/RawHolderWavExport.java rename to src/export/wavExport/RawHolderWavExport.java index 77e3ccf0..181aee3a 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/RawHolderWavExport.java +++ b/src/export/wavExport/RawHolderWavExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.wavExport; +package export.wavExport; import PamguardMVC.PamDataUnit; import PamguardMVC.RawDataHolder; diff --git a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavDataUnitExport.java b/src/export/wavExport/WavDataUnitExport.java similarity index 95% rename from src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavDataUnitExport.java rename to src/export/wavExport/WavDataUnitExport.java index 7d03c647..a1f161b8 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavDataUnitExport.java +++ b/src/export/wavExport/WavDataUnitExport.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.wavExport; +package export.wavExport; import PamView.paneloverlay.overlaymark.OverlayMark; import PamguardMVC.PamDataUnit; diff --git a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavFileExportManager.java b/src/export/wavExport/WavFileExportManager.java similarity index 91% rename from src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavFileExportManager.java rename to src/export/wavExport/WavFileExportManager.java index 52636f3d..a765a0c7 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavFileExportManager.java +++ b/src/export/wavExport/WavFileExportManager.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.wavExport; +package export.wavExport; import java.io.File; import java.util.ArrayList; @@ -22,6 +22,7 @@ import PamguardMVC.PamRawDataBlock; import PamguardMVC.dataOffline.OfflineDataLoading; import dataMap.OfflineDataMapPoint; import detectiongrouplocaliser.DetectionGroupSummary; +import export.PamDataUnitExporter; import wavFiles.Wav16AudioFormat; import wavFiles.WavFileWriter; @@ -30,7 +31,7 @@ import wavFiles.WavFileWriter; * @author Jamie Macaulay * */ -public class WavFileExportManager { +public class WavFileExportManager implements PamDataUnitExporter { /** * Successful writing of .wav file. @@ -70,7 +71,7 @@ public class WavFileExportManager { defaultPath=FileSystemView.getFileSystemView().getDefaultDirectory().getPath(); - defaultPath=defaultPath + "/Pamguard Manual Export"; + defaultPath=defaultPath + File.separator + "Pamguard Manual Export"; currentFolder=defaultPath; } @@ -78,7 +79,7 @@ public class WavFileExportManager { /** - * Get a list of ecporters that can export wav files from a data unit. + * Get a list of exporters that can export wav files from a data unit. * @return the wav file exporters. */ public ArrayList getWavDataUnitExporters() { @@ -145,7 +146,7 @@ public class WavFileExportManager { //add correct file type. currentPath = currentPath + ".wav"; - currentPath = currentFolder+"/"+currentPath; + currentPath = currentFolder + File.separator + currentPath; return currentPath; @@ -196,7 +197,7 @@ public class WavFileExportManager { if (foundDataUnits!=null) { //check whether the wav file has all data raw data units. int n = getNWavDataUnits(foundDataUnits); - hasAllWavClips = n == foundDataUnits.getNumDataUnits(); + hasAllWavClips = (n == foundDataUnits.getNumDataUnits() && n!=0); //make sure to do a zero check here or raw wav data won't save System.out.println("N raw data units: " + n + " N found data units: " + foundDataUnits.getNumDataUnits()); } @@ -233,7 +234,6 @@ public class WavFileExportManager { rawDataBlock.orderOfflineData(new RawObserver(wavWrite, rawDataBlock.getChannelMap()), new RawLoadObserver(wavWrite), start, end, 1, OfflineDataLoading.OFFLINE_DATA_INTERRUPT); - return 0; } @@ -291,7 +291,7 @@ public class WavFileExportManager { * @param dataUnit */ private void newRawData(RawDataUnit dataUnit) { - // System.out.println(" Time millis: "+ dataUnit.getTimeMilliseconds()+ " channel: " + PamUtils.getSingleChannel(dataUnit.getChannelBitmap()) ); +// System.out.println("Write wav data: Time millis: "+ dataUnit.getTimeMilliseconds()+ " channel: " + PamUtils.getSingleChannel(dataUnit.getChannelBitmap()) ); if (currentTimeMillis!=dataUnit.getTimeMilliseconds()) { currentTimeMillis=dataUnit.getTimeMilliseconds(); if (wavArray!=null) { @@ -353,8 +353,6 @@ public class WavFileExportManager { */ private int saveDataUnitWav(DetectionGroupSummary foundDataUnits) { //TODO - need to pad the detections...with zeros. - - //System.out.println("Save data unit wav: " + foundDataUnits.getNumDataUnits()); int n=0; @@ -420,6 +418,53 @@ public class WavFileExportManager { + @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 "wav"; + } + + + + @Override + public String getIconString() { + // TODO Auto-generated method stub + return "mdi2f-file-music"; + } + + + + @Override + public String getName() { + return "raw sound"; + } + + + + @Override + public void close() { + // TODO Auto-generated method stub + + } + + + // hello(){ diff --git a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavSaveCallback.java b/src/export/wavExport/WavSaveCallback.java similarity index 79% rename from src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavSaveCallback.java rename to src/export/wavExport/WavSaveCallback.java index 4b374c4a..f2a0ed2f 100644 --- a/src/dataPlotsFX/overlaymark/menuOptions/wavExport/WavSaveCallback.java +++ b/src/export/wavExport/WavSaveCallback.java @@ -1,4 +1,4 @@ -package dataPlotsFX.overlaymark.menuOptions.wavExport; +package export.wavExport; public interface WavSaveCallback { diff --git a/src/fftManager/PamFFTProcess.java b/src/fftManager/PamFFTProcess.java index 1d8cbb56..64803968 100644 --- a/src/fftManager/PamFFTProcess.java +++ b/src/fftManager/PamFFTProcess.java @@ -124,7 +124,7 @@ public class PamFFTProcess extends PamProcess { public synchronized void setupFFT() { - System.out.println("In call to setupFFT in " + getProcessName()); +// System.out.println("In call to setupFFT in " + getProcessName()); // need to find the existing source data block and remove from observing it. // then find the new one and subscribe to that instead. channelCounts = new int[PamConstants.MAX_CHANNELS]; diff --git a/src/generalDatabase/DBControlUnit.java b/src/generalDatabase/DBControlUnit.java index 0b4239b8..a48971da 100644 --- a/src/generalDatabase/DBControlUnit.java +++ b/src/generalDatabase/DBControlUnit.java @@ -18,6 +18,7 @@ import generalDatabase.backup.DatabaseBackupStream; import pamScrollSystem.ViewLoadObserver; import pamViewFX.pamTask.PamTaskUpdate; import PamController.AWTScheduler; +import PamController.DataIntegrityChecker; import PamController.DataOutputStore; import PamController.OfflineDataStore; import PamController.PamConfiguration; @@ -524,5 +525,10 @@ public class DBControlUnit extends DBControl implements DataOutputStore { } return state; } + @Override + public DataIntegrityChecker getInegrityChecker() { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/generalDatabase/LogSettings.java b/src/generalDatabase/LogSettings.java index 23b713be..c7072e30 100644 --- a/src/generalDatabase/LogSettings.java +++ b/src/generalDatabase/LogSettings.java @@ -109,9 +109,9 @@ public class LogSettings extends DbSpecial { * data that will fit into the database. */ try { - if (pamSettings.getUnitName().equals("User TDDisplay")) { - System.out.println("Saving user display data"); - } +// if (pamSettings.getUnitName().equals("User TDDisplay")) { +// System.out.println("Saving user display data"); +// } } catch (NullPointerException e) { e.printStackTrace(); diff --git a/src/generalDatabase/PamSubtableData.java b/src/generalDatabase/PamSubtableData.java index 35a8dc27..a05a6c1e 100644 --- a/src/generalDatabase/PamSubtableData.java +++ b/src/generalDatabase/PamSubtableData.java @@ -81,6 +81,11 @@ public class PamSubtableData { * subdetection table, and not the index from the Click_Detector_Clicks table) */ private long dbIndex; + + /** + * Click number. only exists for clicks table, but needed for corrupt database. + */ + private Integer clickNumber; /** * @@ -158,5 +163,18 @@ public class PamSubtableData { this.childUTC = childUTC; } + /** + * @return the clickNumber + */ + public Integer getClickNumber() { + return clickNumber; + } + + /** + * @param clickNumber the clickNumber to set + */ + public void setClickNumber(Integer clickNumber) { + this.clickNumber = clickNumber; + } } diff --git a/src/generalDatabase/SQLLogging.java b/src/generalDatabase/SQLLogging.java index 905ee28f..087a039f 100644 --- a/src/generalDatabase/SQLLogging.java +++ b/src/generalDatabase/SQLLogging.java @@ -2077,8 +2077,9 @@ public abstract class SQLLogging { ArrayList tableList = new ArrayList(); int n = 0; try { + PamSubtableDefinition subtableTableDef = (PamSubtableDefinition) getTableDefinition(); + PamTableItem clickNoItem = subtableTableDef.findTableItem("ClickNo"); while (subtableResults.next()) { - PamSubtableDefinition subtableTableDef = (PamSubtableDefinition) getTableDefinition(); PamTableItem tableItem; // transferDataFromResult(con.getSqlTypes(), subtableResults); for (int i = 0; i < subtableTableDef.getTableItemCount(); i++) { @@ -2102,6 +2103,9 @@ public abstract class SQLLogging { subtableData.setLongName(subtableTableDef.getLongName().getStringValue()); subtableData.setBinaryFilename(subtableTableDef.getBinaryfile().getStringValue()); subtableData.setDbIndex(subtableTableDef.getIndexItem().getIntegerValue()); + if (clickNoItem != null) { + subtableData.setClickNumber(clickNoItem.getIntegerValue()); + } try { subtableData.setChildUID(subtableTableDef.getUidItem().getLongObject()); } diff --git a/src/generalDatabase/lookupTables/LookUpTables.java b/src/generalDatabase/lookupTables/LookUpTables.java index bd095eba..abea6653 100644 --- a/src/generalDatabase/lookupTables/LookUpTables.java +++ b/src/generalDatabase/lookupTables/LookUpTables.java @@ -141,9 +141,11 @@ public class LookUpTables { // search for repeats. for (int i = 0; i < n-1; i++) { String code = list.get(i).getCode().trim(); + String topic1 = list.get(i).getTopic().trim(); for (int j = i+1; j < n; j++) { String code2 = list.get(j).getCode().trim(); - if (code.equals(code2)) { + String topic2 = list.get(j).getTopic().trim(); + if (code.equals(code2) && topic1.equals(topic2)) { isRepeat[j] = true; nRepeat++; } diff --git a/src/group3dlocaliser/Group3DLocaliserControl.java b/src/group3dlocaliser/Group3DLocaliserControl.java index 1e2baa04..c2fe8c8a 100644 --- a/src/group3dlocaliser/Group3DLocaliserControl.java +++ b/src/group3dlocaliser/Group3DLocaliserControl.java @@ -18,6 +18,8 @@ import group3dlocaliser.algorithm.LocaliserAlgorithmParams; import group3dlocaliser.algorithm.crossedbearing.CrossedBearingGroupLocaliser; import group3dlocaliser.algorithm.gridsearch.TOADGridSearch; import group3dlocaliser.algorithm.hyperbolic.HyperbolicLocaliser; +import group3dlocaliser.algorithm.toadmcmc.ToadMCMCLocaliser; +import group3dlocaliser.algorithm.toadmimplex.ToadMimplexLocaliser; import group3dlocaliser.algorithm.toadsimplex.ToadSimplexLocaliser; import group3dlocaliser.dialog.GroupLocSettingPaneFX; import group3dlocaliser.offline.Group3DOfflineTask; @@ -42,10 +44,13 @@ public class Group3DLocaliserControl extends PamControlledUnit implements PamSet public Group3DLocaliserControl(String unitName) { super(unitType, unitName); algorithms3D = new ArrayList<>(); - algorithms3D.add(new CrossedBearingGroupLocaliser()); + algorithms3D.add(new CrossedBearingGroupLocaliser(this)); algorithms3D.add(new HyperbolicLocaliser(this)); algorithms3D.add(new ToadSimplexLocaliser(this, 2)); algorithms3D.add(new ToadSimplexLocaliser(this, 3)); + algorithms3D.add(new ToadMCMCLocaliser(this)); + algorithms3D.add(new ToadMimplexLocaliser(this)); + // algorithms3D.add(new TOADGridSearch(this)); group3dProcess = new Group3DProcess(this); diff --git a/src/group3dlocaliser/Group3DProcess.java b/src/group3dlocaliser/Group3DProcess.java index 7822e346..6629b05f 100644 --- a/src/group3dlocaliser/Group3DProcess.java +++ b/src/group3dlocaliser/Group3DProcess.java @@ -53,7 +53,7 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor public Group3DProcess(Group3DLocaliserControl group3DControl) { super(group3DControl, null); this.group3DControl = group3DControl; - localiserAlgorithm3D = new CrossedBearingGroupLocaliser(); + localiserAlgorithm3D = group3DControl.getAlgorithmProviders().get(0); detectionGrouper = new DetectionGrouper(this); group3dDataBlock = new Group3DDataBlock(group3DControl.getUnitName() + " Localisations", this, 0, group3DControl); @@ -62,7 +62,7 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor group3dDataBlock.setPamSymbolManager(new Group3DSymbolManager(group3DControl, group3dDataBlock, Group3DOverlayDraw.defaultSymbol.getSymbolData())); group3dLogging = new Group3DLogging(group3DControl, group3dDataBlock); - group3dDataBlock.SetLogging(group3dLogging); + group3dDataBlock.SetLogging(group3dLogging); } /* @@ -118,6 +118,9 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor @Override public void pamStop() { + + ///need this here to close the group + this.detectionGrouper.closeMotherGroup(); String prf = String.format("%s %s ", group3DControl.getUnitName(), localiserAlgorithm3D.getName()); System.out.println(cpuMonitor.getSummary(prf)); } @@ -180,7 +183,10 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor } @Override - public void newGroupedDataSet(DetectionGroupedSet detectionGroupedSet) { + public void newGroupedDataSet(DetectionGroupedSet detectionGroupedSet1) { + + DetectionGroupedSet detectionGroupedSet = this.localiserAlgorithm3D.preFilterLoc(detectionGroupedSet1); + int nGroups = detectionGroupedSet.getNumGroups(); AbstractLocalisation abstractLocalisation; GroupLocalisation groupLocalisation; @@ -223,14 +229,17 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor logViewerData(newDataUnit); } } -// System.out.println("Ran localisation " + localiserAlgorithm3D.getName() + " got: " + abstractLocalisation); + + System.out.println("Ran localisation " + i + " " + localiserAlgorithm3D.getName() + " got: " + abstractLocalisation.getLatLong(0) + " " + abstractLocalisation.getHeight(0) + " Error: " + abstractLocalisation.getLocError(0)); if (abstractLocalisation instanceof GroupLocalisation) { groupLocalisation = (GroupLocalisation) abstractLocalisation; + cpuMonitor.stop(); if (groupLocalisation != null) { groupLocalisation.sortLocResults(); GroupLocResult locResult = groupLocalisation.getGroupLocaResult(0); + if (locResult == null) { continue; } @@ -257,10 +266,15 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor // bestSet = i; // } } - } else { + } else if (bestLocalisation==null) { + //note it is important here to make sure bestLoclaisation is null. If we have an array which has a linear + //compenent than a set of time delays of only the linear system may return a linear loclaisation in which case, without a + // null check, this will always override a group loclaisation. bestLocalisation = abstractLocalisation; bestSet = i; } + + } if (bestLocalisation != null && logAll == false) { // best make and output a data unit ! @@ -272,6 +286,8 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor // } Group3DDataUnit newDataUnit = group3dDataUnits[bestSet]; newDataUnit.setLocalisation(bestLocalisation); + + System.out.println("Set click localisation: " + bestSet + " " + bestLocalisation.getRange(0) + " " + bestLocalisation.getLatLong(0)); group3dDataBlock.addPamData(newDataUnit); if (group3DControl.isViewer()) { // call explicityly since it won't happen in normal mode. @@ -288,7 +304,7 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor * @param group2 * @param result1 * @param result2 - * @return + * @return true if group 1 is better than group 2. */ private boolean isBetter(List group1, List group2, GroupLocResult result1, GroupLocResult result2) { @@ -298,6 +314,17 @@ public class Group3DProcess extends PamProcess implements DetectionGroupMonitor double chi2 = result2.getChi2() / nch2; Double errMag1 = null, errMag2 = null; LocaliserError err = result1.getLocError(); + + //if one has location information and the other does not, choose the one with the + System.out.println("LAT LONG TEST: " + result1.getLatLong() + " " + result2.getLatLong()); + if (result1.getLatLong()==null && result2.getLatLong()!=null) { + return false; + } + + if (result2.getLatLong() == null && result1.getLatLong() != null) { + return true; + } + if (err != null) { errMag1 = err.getErrorMagnitude(); if (errMag1.isInfinite()) { diff --git a/src/group3dlocaliser/ToadManagedSettingsPane.java b/src/group3dlocaliser/ToadManagedSettingsPane.java new file mode 100644 index 00000000..53f39b14 --- /dev/null +++ b/src/group3dlocaliser/ToadManagedSettingsPane.java @@ -0,0 +1,23 @@ +package group3dlocaliser; + +import javafx.scene.control.TabPane; +import pamViewFX.fxNodes.pamDialogFX.ManagedSettingsPane; + +public abstract class ToadManagedSettingsPane extends ManagedSettingsPane{ + + /** + * Get the main tab pane. + * @return the main tab pane. + */ + public abstract TabPane getTabPane(); + + /** + * Set whether the pane should show errors. Because there are multiple algorithms that + * will use this pane we do not want to warn for every algorithm only the user selected one. + * @param warn = true to warn errors e.g. show an dialog - false and there are no error warnings. + */ + public abstract void setErrorWarn(boolean warn); + + + +} diff --git a/src/group3dlocaliser/algorithm/LocaliserAlgorithm3D.java b/src/group3dlocaliser/algorithm/LocaliserAlgorithm3D.java index e7a055b3..42e1afe9 100644 --- a/src/group3dlocaliser/algorithm/LocaliserAlgorithm3D.java +++ b/src/group3dlocaliser/algorithm/LocaliserAlgorithm3D.java @@ -1,14 +1,18 @@ package group3dlocaliser.algorithm; import java.awt.Window; +import java.io.Serializable; import Array.ArrayManager; import Localiser.LocaliserModel; +import Localiser.LocaliserPane; import PamController.SettingsPane; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.superdet.SuperDetection; import generalDatabase.SQLLoggingAddon; +import group3dlocaliser.ToadManagedSettingsPane; +import group3dlocaliser.grouper.DetectionGroupedSet; import pamViewFX.fxNodes.pamDialogFX.ManagedSettingsPane; abstract public class LocaliserAlgorithm3D implements LocaliserModel { @@ -31,19 +35,51 @@ abstract public class LocaliserAlgorithm3D implements LocaliserModelThis may be Swing or FX for compatibility with old code. - * @param parent parent frame / window, etc. - * @param currentParams current algorithm params. These may be null - * the first time this gets called, so prepare for this and create new as - * required - * @return null if the dialog cancelled, a new params object otherwise. + * Option to pre-filter the localisation results. This can be useful when using algorithms that + * internally handle detection match uncertainty. + * @param - the initial set of detection matches to filter. */ - public LocaliserAlgorithmParams showAlgorithmDialog(Window parent, LocaliserAlgorithmParams currentParams) { - return null; + public DetectionGroupedSet preFilterLoc(DetectionGroupedSet preGroups) { + return preGroups; } + + /** + * The loclaiser params. The + *

+ * + * Note: the localiser params for each model are stored in + * @param localiserParams + */ + public void setLocaliserParams(LocaliserAlgorithmParams localiserParams){ + } + + +// /** +// * Configure parameters for this algorithm. Show a dialog with +// * anything you want ... +// *
This may be Swing or FX for compatibility with old code. +// * @param parent parent frame / window, etc. +// * @param currentParams current algorithm params. These may be null +// * the first time this gets called, so prepare for this and create new as +// * required +// * @return null if the dialog cancelled, a new params object otherwise. +// */ +// public LocaliserAlgorithmParams showAlgorithmDialog(Window parent, LocaliserAlgorithmParams currentParams) { +// return null; +// } + + + /** + * Get an algorithm specific settings pane. This holds settings which are specific to the + * localisation algorithm. + */ + @Override + public LocaliserPane getAlgorithmSettingsPane(){ + return null; + } + + /** * Get a source settings pane. This will be inserted into the dialog beneath where the * detection source and algorithm have been selected. @@ -52,7 +88,7 @@ abstract public class LocaliserAlgorithm3D implements LocaliserModel getSourceSettingsPane(Window parent, PamDataBlock detectionSource) { + public ToadManagedSettingsPane getSourceSettingsPane(Window parent, PamDataBlock detectionSource) { return null; } diff --git a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingGroupLocaliser.java b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingGroupLocaliser.java index 2874c9b3..81b13b1a 100644 --- a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingGroupLocaliser.java +++ b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingGroupLocaliser.java @@ -1,12 +1,11 @@ package group3dlocaliser.algorithm.crossedbearing; import java.awt.Window; -import java.util.List; +import java.io.Serializable; import Array.ArrayManager; import Localiser.LocaliserPane; import Localiser.detectionGroupLocaliser.DetectionGroupOptions; -import Localiser.detectionGroupLocaliser.GroupLocalisation; import PamDetection.AbstractLocalisation; import PamDetection.LocContents; import PamDetection.LocalisationInfo; @@ -17,11 +16,21 @@ import annotation.localise.targetmotion.TMAnnotation; import annotation.localise.targetmotion.TMAnnotationOptions; import annotation.localise.targetmotion.TMAnnotationType; import generalDatabase.SQLLoggingAddon; -import group3dlocaliser.GroupLocDataUnit; +import group3dlocaliser.Group3DLocaliserControl; import group3dlocaliser.algorithm.LocaliserAlgorithm3D; import group3dlocaliser.algorithm.LocaliserAlgorithmParams; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxGlyphs.PamGlyphDude; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamHBox; -public class CrossedBearingGroupLocaliser extends LocaliserAlgorithm3D{ +public class CrossedBearingGroupLocaliser extends LocaliserAlgorithm3D { private double sampleRate; @@ -29,7 +38,12 @@ public class CrossedBearingGroupLocaliser extends LocaliserAlgorithm3D{ private LocContents locContents = new LocContents(LocContents.HAS_BEARING); - public CrossedBearingGroupLocaliser() { + private CrossedBearingPane crossedBearingPane; + + private Group3DLocaliserControl group3dLocaliserControl; + + public CrossedBearingGroupLocaliser(Group3DLocaliserControl group3dLocaliserControl) { + this.group3dLocaliserControl = group3dLocaliserControl; tmAnnotationType = new TMAnnotationType(); TMAnnotationOptions tmAnnotationOptions = new TMAnnotationOptions("CrossedBearingGroupLocaliser"); tmAnnotationOptions.getLocalisationParams().setIsSelected(0, false); @@ -124,9 +138,11 @@ public class CrossedBearingGroupLocaliser extends LocaliserAlgorithm3D{ * @see Localiser.LocaliserModel#getSettingsPane() */ @Override - public LocaliserPane getSettingsPane() { - // TODO Auto-generated method stub - return null; + public LocaliserPane getAlgorithmSettingsPane() { + if (crossedBearingPane == null) { + crossedBearingPane = new CrossedBearingPane(); + } + return crossedBearingPane; } /* (non-Javadoc) @@ -145,28 +161,121 @@ public class CrossedBearingGroupLocaliser extends LocaliserAlgorithm3D{ // TODO Auto-generated method stub } - - /* (non-Javadoc) - * @see group3dlocaliser.algorithm.LocaliserAlgorithmProvider#showAlgorithmDialog(java.awt.Window, group3dlocaliser.algorithm.LocaliserAlgorithmParams) + + //TODO - make this full FX + /** + * Settings pane for the crossed bearing loclaiser. Note that this just shows a settings button which open a swing dialog.. + * @author Jamie Macaulay + * */ - @Override - public LocaliserAlgorithmParams showAlgorithmDialog(Window parent, LocaliserAlgorithmParams currentParams) { + public class CrossedBearingPane extends LocaliserPane { + + private PamBorderPane mainPane; - if (currentParams != null && currentParams.getAlgorithmParameters() instanceof TMAnnotationOptions) { - TMAnnotationOptions p = (TMAnnotationOptions) currentParams.getAlgorithmParameters(); - this.setTmAnnotationOptions(p); + private LocaliserAlgorithmParams algorithmPaams; + + public CrossedBearingPane() { + PamHBox hBox = new PamHBox(); + hBox.setSpacing(5); + hBox.setAlignment(Pos.CENTER); + hBox.setPadding(new Insets(5,5,5,5)); + + PamButton algoOptsButton = new PamButton("",PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); + algoOptsButton.setOnAction((action)->{ + moreAlgorithmOptions() ; + }); + algoOptsButton.setTooltip(new Tooltip("More Algorithm Options ...")); + + hBox.getChildren().addAll(new Label(getName() + " settings"), algoOptsButton); + + + mainPane = new PamBorderPane(); + mainPane.setCenter(hBox); } -// AnnotationSettingsPanel settingsPanel = cbLocaliser.getTmAnnotationType().getSettingsPanel(); - boolean asd = AnnotationSettingsDialog.showDialog(parent, this.getTmAnnotationType()); - if (asd) { - return new LocaliserAlgorithmParams(this.getTmAnnotationOptions()); + /** + * Handle algorithm options ... + */ + protected void moreAlgorithmOptions() { + String algoName = getName(); + if (algoName == null) { + return; + } + LocaliserAlgorithm3D localiserAlgorithm = group3dLocaliserControl.findAlgorithm(algoName); + if (localiserAlgorithm == null) { + return; + } + if (localiserAlgorithm.hasParams() == false) { + return; + } + + LocaliserAlgorithmParams algorithmPaams = group3dLocaliserControl.getLocaliserAlgorithmParams(localiserAlgorithm); + + + this.algorithmPaams = showAlgorithmDialog(getAWTWindow(), algorithmPaams); + + +// if (algorithmPaams != null) { +// group3dLocaliserControl.setAlgorithmParams(localiserAlgorithm, algorithmPaams); +// } } - else { - return null; + + + /* (non-Javadoc) + * @see group3dlocaliser.algorithm.LocaliserAlgorithmProvider#showAlgorithmDialog(java.awt.Window, group3dlocaliser.algorithm.LocaliserAlgorithmParams) + */ + public LocaliserAlgorithmParams showAlgorithmDialog(Window parent, LocaliserAlgorithmParams currentParams) { + + if (currentParams != null && currentParams.getAlgorithmParameters() instanceof TMAnnotationOptions) { + TMAnnotationOptions p = (TMAnnotationOptions) currentParams.getAlgorithmParameters(); + setTmAnnotationOptions(p); + } + +// AnnotationSettingsPanel settingsPanel = cbLocaliser.getTmAnnotationType().getSettingsPanel(); + boolean asd = AnnotationSettingsDialog.showDialog(parent, getTmAnnotationType()); + + + if (asd) { + return new LocaliserAlgorithmParams(getTmAnnotationOptions()); + } + else { + return null; + } } + + @Override + public Serializable getParams(Serializable currParams) { + if (algorithmPaams!=null) { + return this.algorithmPaams ; + } + return currParams; + } + + @Override + public void setParams(Serializable input) { + this.algorithmPaams = (LocaliserAlgorithmParams) input; + } + + @Override + public String getName() { + return "Crossed Bearing Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + } + + @Override public boolean canLocalise(PamDataBlock pamDataBlock) { if (pamDataBlock == null) { diff --git a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingProvider.java b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingProvider.java index 7d863701..b6e20ab6 100644 --- a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingProvider.java +++ b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingProvider.java @@ -19,7 +19,7 @@ public class CrossedBearingProvider extends LocaliserAlgorithmProvider { */ private synchronized CrossedBearingGroupLocaliser getInstance() { if (crossedBearingLocaliser == null) { - crossedBearingLocaliser = new CrossedBearingGroupLocaliser(); + crossedBearingLocaliser = new CrossedBearingGroupLocaliser(null); } return crossedBearingLocaliser; } diff --git a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingSQLAddon.java b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingSQLAddon.java index dbcb6ff2..15a0b458 100644 --- a/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingSQLAddon.java +++ b/src/group3dlocaliser/algorithm/crossedbearing/CrossedBearingSQLAddon.java @@ -5,7 +5,7 @@ import targetMotionOld.TargetMotionSQLLogging; public class CrossedBearingSQLAddon extends TargetMotionSQLLogging { - private targetMotionModule.TargetMotionSQLLogging tml; +// private targetMotionModule.TargetMotionSQLLogging tml; public CrossedBearingSQLAddon(int nResults) { super(nResults, ""); // hideColumns(getBeamLatitude()); diff --git a/src/group3dlocaliser/algorithm/gridsearch/TOADGridSearch.java b/src/group3dlocaliser/algorithm/gridsearch/TOADGridSearch.java index 3c5130b1..166cb4d5 100644 --- a/src/group3dlocaliser/algorithm/gridsearch/TOADGridSearch.java +++ b/src/group3dlocaliser/algorithm/gridsearch/TOADGridSearch.java @@ -1,5 +1,7 @@ package group3dlocaliser.algorithm.gridsearch; +import java.io.Serializable; + import org.apache.commons.math.MathException; import org.apache.commons.math.distribution.ChiSquaredDistributionImpl; @@ -15,6 +17,7 @@ import PamguardMVC.PamDataUnit; import generalDatabase.SQLLoggingAddon; import group3dlocaliser.Group3DLocaliserControl; import group3dlocaliser.algorithm.Chi2Data; +import group3dlocaliser.algorithm.LocaliserAlgorithmParams; import group3dlocaliser.algorithm.toadbase.TOADBaseAlgorithm; import group3dlocaliser.algorithm.toadbase.TOADInformation; import group3dlocaliser.grids.Grid3D; @@ -50,11 +53,6 @@ public class TOADGridSearch extends TOADBaseAlgorithm { return null; } - @Override - public LocaliserPane getSettingsPane() { - // TODO Auto-generated method stub - return null; - } @Override public boolean hasParams() { diff --git a/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicGroupLocalisation.java b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicGroupLocalisation.java new file mode 100644 index 00000000..6df47138 --- /dev/null +++ b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicGroupLocalisation.java @@ -0,0 +1,33 @@ +package group3dlocaliser.algorithm.hyperbolic; + +import Localiser.detectionGroupLocaliser.GroupLocResult; +import Localiser.detectionGroupLocaliser.GroupLocalisation; +import PamguardMVC.PamDataUnit; +import pamMaths.PamVector; + +/** + * Keeps a copy of the raw position vector for hyperbolic localisation. + * @author Jamie Macaulay + * + */ +public class HyperbolicGroupLocalisation extends GroupLocalisation { + + /** + * The raw localisation result. + */ + double[] posVec; + + + public HyperbolicGroupLocalisation(PamDataUnit pamDataUnit, GroupLocResult targetMotionResult) { + super(pamDataUnit, targetMotionResult); + // TODO Auto-generated constructor stub + } + + public double[] getPosVec() { + return posVec; + } + + public void setPosVec(double[] posVec) { + this.posVec = posVec; + } +} diff --git a/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicLocaliser.java b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicLocaliser.java index dc6e5eb4..f9483bad 100644 --- a/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicLocaliser.java +++ b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicLocaliser.java @@ -1,6 +1,8 @@ package group3dlocaliser.algorithm.hyperbolic; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Random; import org.apache.commons.math3.distribution.ChiSquaredDistribution; @@ -9,13 +11,14 @@ import Array.SnapshotGeometry; import GPS.GpsData; import Jama.Matrix; import Localiser.LocaliserPane; +import Localiser.algorithms.locErrors.SimpleError; import Localiser.detectionGroupLocaliser.GroupLocResult; import Localiser.detectionGroupLocaliser.GroupLocalisation; import PamDetection.AbstractLocalisation; import PamDetection.LocContents; import PamUtils.LatLong; +import PamUtils.PamArrayUtils; import PamguardMVC.PamDataUnit; -import PamguardMVC.debug.Debug; import generalDatabase.SQLLoggingAddon; import group3dlocaliser.Group3DLocaliserControl; import group3dlocaliser.algorithm.Chi2Data; @@ -25,15 +28,24 @@ import group3dlocaliser.localisation.LinearLocalisation; import pamMaths.PamVector; /** - * Hyperbolic localisation using methods described in - * Gillette, M. D., and Silverman, H. F. (2008). A linear closed-form algorithm for source localization from time-differences of arrival, IEEE Signal Processing Letters, 15, 14.

+ * Hyperbolic localisation using methods described in Gillette, M. D., and + * Silverman, H. F. (2008). �A linear closed-form algorithm for source + * localization from time-differences of arrival,� IEEE Signal Processing + * Letters, 15, 1�4. + *

+ * + * Also worth reading Spiesberger, J. L. (2001). �Hyperbolic location errors due + * to insufficient numbers of receivers,� The Journal of the Acoustical Society + * of America, 109, 3076�3079. which gives a clearer explanation of why at least + * 4 recievers are needed for 2D localisation and 5 for 3D localisation. + *

+ * Worth noting that the equations derived in Gillette 2008 are functionally + * identical to those in Spiesberger 2001 and an earlier work by Speisberger and + * Fristrup:
+ * Spiesberger, J. L., and Fristrup, K. M. (1990). �Passive localization of + * calling animals and sensing of their acoustic environment using acoustic + * tomography,� The american naturalist, 135, 107�153. * - * Also worth reading Spiesberger, J. L. (2001). Hyperbolic location errors due to insufficient numbers of receivers, The Journal of the Acoustical Society of America, 109, 30763079. - * which gives a clearer explanation of why at least 4 recievers are needed for 2D localisation and 5 for 3D localisation.

- * Worth noting that the equations derived in Gillette 2008 are functionally identical to those in Spiesberger 2001 and an earlier work by Speisberger and Fristrup:
- * Spiesberger, J. L., and Fristrup, K. M. (1990). Passive localization of calling animals and sensing of their acoustic environment using acoustic tomography, - * The american naturalist, 135, 107153. - * * @author Doug Gillespie * */ @@ -41,27 +53,53 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { private double[] lastPosVector; private LocContents locContents = new LocContents(0); + + private HyperbolicParams params = new HyperbolicParams(); + + private HyperbolicSettingsPane hyperbolicLocaliserPane; + public HyperbolicLocaliser(Group3DLocaliserControl group3dLocaliser) { super(group3dLocaliser); - // TODO Auto-generated constructor stub } @Override public AbstractLocalisation processTOADs(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation) { + + //System.out.println("Process TOADS: Hyperbolic: " + toadInformation.getChannelMap()); + + /** + * This module is a little odd in that it stores paramters for each algorithm in it's own has table without acc + */ + params = (HyperbolicParams) group3dLocaliser.getLocaliserAlgorithmParams(this).getAlgorithmParameters(); + + int shape = geometry.getShape(); + GroupLocalisation loclaisation = null; switch (shape) { case ArrayManager.ARRAY_TYPE_LINE: + //System.out.println("HyperbolicLocaliser: Line"); + return processTOADs2D(groupDataUnit, geometry, toadInformation); case ArrayManager.ARRAY_TYPE_PLANE: - return processTOADsPlane(groupDataUnit, geometry, toadInformation); + //System.out.println("HyperbolicLocaliser: Plane"); + + loclaisation = processTOADsPlane(groupDataUnit, geometry, toadInformation); + calcErrors(loclaisation.getGroupLocaResult(0), groupDataUnit, geometry, toadInformation, params); + return loclaisation; + case ArrayManager.ARRAY_TYPE_VOLUME: - return processTOADs3D(groupDataUnit, geometry, toadInformation); + //System.out.println("HyperbolicLocaliser: Volume"); + + loclaisation = processTOADs3D(groupDataUnit, geometry, toadInformation); + calcErrors(loclaisation.getGroupLocaResult(0), groupDataUnit, geometry, toadInformation, params); + return loclaisation; } - return null; + System.out.println("HyperbolicLocaliser: Shape is null?"); + return loclaisation; //will be null if no array geometry found. } - private GroupLocalisation processTOADsPlane(PamDataUnit groupDataUnit, SnapshotGeometry geometry, + private HyperbolicGroupLocalisation processTOADsPlane(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation) { PamVector[] arrayAxes = geometry.getArrayAxes(); if (arrayAxes.length < 2) { @@ -322,8 +360,9 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { GroupLocResult glr = new GroupLocResult(pos, 0, 0); glr.setPerpendicularDistance(0.); // glr.setModel(model); - GroupLocalisation groupLocalisation = new GroupLocalisation(groupDataUnit, glr); - + HyperbolicGroupLocalisation groupLocalisation = new HyperbolicGroupLocalisation(groupDataUnit, glr); + groupLocalisation.setPosVec(posVec); + return groupLocalisation; } @@ -581,7 +620,8 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { return new Chi2Data(chiTot, nGood-2); } - private GroupLocalisation processTOADs3D(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation) { + private HyperbolicGroupLocalisation processTOADs3D(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation) { + // follow formulation in http://ieeexplore.ieee.org/document/4418389/#full-text-section // for (int i = 0; i < delays.length; i++) { @@ -703,6 +743,7 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { // System.out.println(e.getMessage()); // Matrix m = leftMatrix.transpose(); // m = m.inverse(); + System.out.println("HyperbolicLoclaiser: localisation failed"); return null; } // get a chi2 value for this location. @@ -720,24 +761,25 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { // System.out.printf("Res %d = %3.5f, ", i, answer.get(i, 0)); // } // System.out.printf(", Chi2 = %3.1f, p=%3.1f, ndf = %d\n", chiData.getChi2(), cumProb, chiData.getNdf()); + - // error estimation - try { - /* - * Error estimation based on - * https://www.math.umd.edu/~petersd/460/linsysterrn.pdf - * - */ - double cond1 = leftMatrix.norm1()*leftInverse.norm1(); - double errorSscale = (rightPlusErrorMatrix.minus(rightMatrix)).norm1(); - errorSscale *= cond1; - errorSscale /= rightMatrix.norm1(); -// System.out.printf("Error scale = %3.3f\n", errorSscale); - - } - catch (Exception e) { - System.out.println("Error in HyperbolicLocaliser.processTOADs3D: " + e.getMessage()); - } +// // error estimation +// try { +// /* +// * Error estimation based on +// * https://www.math.umd.edu/~petersd/460/linsysterrn.pdf +// * +// */ +// double cond1 = leftMatrix.norm1()*leftInverse.norm1(); +// double errorSscale = (rightPlusErrorMatrix.minus(rightMatrix)).norm1(); +// errorSscale *= cond1; +// errorSscale /= rightMatrix.norm1(); +// //System.out.printf("Error scale = %3.3f\n", errorSscale); +// +// } +// catch (Exception e) { +// System.out.println("Error in HyperbolicLocaliser.processTOADs3D: " + e.getMessage()); +// } LatLong pos = geometry.getReferenceGPS().addDistanceMeters(centre.getCoordinate(0)+answer.get(0,0), centre.getCoordinate(1)+answer.get(1, 0), @@ -745,11 +787,102 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { // TargetMotionResult tmResult = new TargetMotionResult(geometry.getTimeMilliseconds(), null, pos, 0, 0); GroupLocResult glr = new GroupLocResult(pos, 0, 0); glr.setPerpendicularDistance(0.); + glr.setModel(this); + int nToads = countUsableTOADS(toadInformation); + int nDF = nToads-3; + glr.setnDegreesFreedom(nDF); + glr.setDim(3); + glr.setBeamLatLong(geometry.getReferenceGPS()); + glr.setBeamTime(groupDataUnit.getTimeMilliseconds()); + glr.setAic(chiData.getChi2()-6); // glr.setModel(model); - GroupLocalisation groupLocalisation = new GroupLocalisation(groupDataUnit, glr); + HyperbolicGroupLocalisation groupLocalisation = new HyperbolicGroupLocalisation(groupDataUnit, glr); + groupLocalisation.setPosVec(posVec); + + //System.out.println("HyperbolicLoclaiser: Lat long: " + groupLocalisation.getLatLong(0) + " " + groupLocalisation.hasLocContent(LocContents.HAS_LATLONG) ); + return groupLocalisation; } + + /** + * Calculate and add the errors in source position. This is achieved by sampling a random number from the time delay error distributions, localising and looking at the distribution + * in position of localisation results. + * @param loclaisation - the localisation result. + * @param timeDelays - object containing time delay data. + * @param hydrophonePos - the hydrophone positions. + * @param params - hyperbolic params object with settings for error calculations. + * @return the loclaisation object iwth errors added. + */ + public GroupLocResult calcErrors(GroupLocResult loclaisation, PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation, HyperbolicParams params){ + double[] errors = calcErrors( groupDataUnit, geometry, toadInformation , params); + loclaisation.setError(new SimpleError(errors[0], errors[1], errors[2],geometry.getArrayAngles()[0])); + return loclaisation; + } + + + + /** + * Calculate the errors in source position. This is achieved by sampling a random number from the time delay error distributions, localising and looking at the distribution + * in position of localisation results. + * @param timeDelays - object containing time delay data. + * @param hydrophonePos - the hydrophone positions. + * @param params - hyperbolic params object with settings for error calculations. + * @return the errors in meters + */ + public double[] calcErrors(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation , HyperbolicParams params){ + + Random r = new Random(); + double[][] sourcePositions=new double[params.bootStrapN][3]; + + TOADInformation errToadInformation; + HyperbolicGroupLocalisation errLoc; + for (int i=0; i timeDelaysJitter=new ArrayList(); + double error; + + errToadInformation = (TOADInformation) toadInformation.clone(); + + //generate the new time delay errors. + for (int j=0; j getSettingsPane() { - return null; + public LocaliserPane getAlgorithmSettingsPane() { + if (hyperbolicLocaliserPane == null) { + hyperbolicLocaliserPane= new HyperbolicSettingsPane(); + } + return hyperbolicLocaliserPane; } /* (non-Javadoc) @@ -799,8 +935,7 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { */ @Override public boolean hasParams() { - // TODO Auto-generated method stub - return false; + return true; } /* (non-Javadoc) @@ -811,5 +946,8 @@ public class HyperbolicLocaliser extends TOADBaseAlgorithm { // TODO Auto-generated method stub } + + + } diff --git a/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicParams.java b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicParams.java new file mode 100644 index 00000000..b257dce9 --- /dev/null +++ b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicParams.java @@ -0,0 +1,47 @@ +package group3dlocaliser.algorithm.hyperbolic; + +import java.io.Serializable; + +import PamModel.parametermanager.ManagedParameters; +import PamModel.parametermanager.PamParameterSet; + +/** + * Holds settings for hyperbolic loclaiser. + * @author Jamie Macaulay + * + */ +public class HyperbolicParams implements Serializable, Cloneable, ManagedParameters { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Calculates errors from a random distribution of time delay errors + */ + public boolean calcErrors=false; + + /** + * Number of iterations to calculate error. More=more computational time per localisation; + */ + public int bootStrapN=100; + + @Override + public HyperbolicParams clone() { + try { + + return (HyperbolicParams) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public PamParameterSet getParameterSet() { + PamParameterSet ps = PamParameterSet.autoGenerate(this); + return ps; + } + +} diff --git a/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicSettingsPane.java b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicSettingsPane.java new file mode 100644 index 00000000..f0a95fe0 --- /dev/null +++ b/src/group3dlocaliser/algorithm/hyperbolic/HyperbolicSettingsPane.java @@ -0,0 +1,88 @@ +package group3dlocaliser.algorithm.hyperbolic; + +import Localiser.LocaliserPane; +import PamController.SettingsPane; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamSpinner; +import pamViewFX.fxNodes.utilityPanes.PamToggleSwitch; + +/** + * Hyperbolic settings pane. + * @author Jamie Macaulay + * + */ +public class HyperbolicSettingsPane extends LocaliserPane { + + PamBorderPane mainPane; + + /** + * The toggle switch. + */ + private PamToggleSwitch hyperbolicToggleSwitch; + + private PamSpinner numIterations; + + public HyperbolicSettingsPane() { + mainPane = new PamBorderPane(); + mainPane.setPadding(new Insets(5,5,5,5)); + + hyperbolicToggleSwitch = new PamToggleSwitch("Calculate errors"); + hyperbolicToggleSwitch.selectedProperty().addListener((obsVal, oldVal, newVal)->{ + numIterations.setDisable(!hyperbolicToggleSwitch.isSelected()); + }); + + numIterations = new PamSpinner(0,1000,100,1); + numIterations.setDisable(!hyperbolicToggleSwitch.isSelected()); + numIterations.setEditable(true); + + PamHBox hBox = new PamHBox( ); + hBox.setAlignment(Pos.CENTER_LEFT); + hBox.setSpacing(5); + hBox.getChildren().addAll(hyperbolicToggleSwitch, numIterations, new Label("Iterations")); + + mainPane.setTop(hBox); + + } + + + + @Override + public HyperbolicParams getParams(HyperbolicParams currParams) { + if (currParams ==null) currParams = new HyperbolicParams(); + currParams.bootStrapN = numIterations.getValue(); + currParams.calcErrors = hyperbolicToggleSwitch.isSelected(); + + return currParams; + } + + @Override + public void setParams(HyperbolicParams input) { + if (input==null) input = new HyperbolicParams(); + //System.out.println("Hyperbolic set Params: " + input.bootStrapN); + numIterations.getValueFactory().setValue(input.bootStrapN); + hyperbolicToggleSwitch.setSelected(input.calcErrors); + } + + @Override + public String getName() { + return "Hyperbolic parameters"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/group3dlocaliser/algorithm/toadbase/TOADBaseAlgorithm.java b/src/group3dlocaliser/algorithm/toadbase/TOADBaseAlgorithm.java index 0e98ada5..3bf13104 100644 --- a/src/group3dlocaliser/algorithm/toadbase/TOADBaseAlgorithm.java +++ b/src/group3dlocaliser/algorithm/toadbase/TOADBaseAlgorithm.java @@ -3,7 +3,6 @@ package group3dlocaliser.algorithm.toadbase; import java.awt.Window; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import Acquisition.AcquisitionControl; @@ -18,11 +17,8 @@ import Localiser.detectionGroupLocaliser.DetectionGroupOptions; import PamController.PamControlledUnitSettings; import PamController.PamSettingManager; import PamController.PamSettings; -import PamController.SettingsNameProvider; -import PamController.SettingsPane; import PamDetection.AbstractLocalisation; import PamUtils.PamUtils; -import PamUtils.complex.ComplexArray; import PamguardMVC.FFTDataHolder; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; @@ -32,7 +28,7 @@ import PamguardMVC.toad.GenericTOADCalculator; import PamguardMVC.toad.TOADCalculator; import fftManager.FFTDataUnit; import group3dlocaliser.Group3DLocaliserControl; -import group3dlocaliser.Group3DParams; +import group3dlocaliser.ToadManagedSettingsPane; import group3dlocaliser.algorithm.Chi2Data; import group3dlocaliser.algorithm.FitTestValue; import group3dlocaliser.algorithm.LocaliserAlgorithm3D; @@ -40,7 +36,21 @@ import group3dlocaliser.algorithm.LogLikelihoodData; import pamMaths.PamVector; import pamViewFX.fxNodes.pamDialogFX.ManagedSettingsPane; + abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { + + public enum MultiVariateType { + /** + * Chi2 fucntion + */ + CHI2, + + /** + * Log liklilihood function + */ + LOG_LIKILIHOOD; + } + private double sampleRate; @@ -54,8 +64,10 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { private TOADBaseParams toadBaseParams = new TOADBaseParams(); - private Group3DLocaliserControl group3dLocaliser; - + protected Group3DLocaliserControl group3dLocaliser; + + private TOADSettingsPaneWithChannels tspwc; + private static double halflog2pi = Math.log(2.*Math.PI)/2.; public TOADBaseAlgorithm(Group3DLocaliserControl group3dLocaliser) { @@ -90,6 +102,7 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { } double sampleRate = 0; for (PamDataUnit aDataUnit:subDetections) { + //System.out.println("Channels: " + aDataUnit.getChannelBitmap()); allChannels |= aDataUnit.getChannelBitmap(); sampleRate = aDataUnit.getParentDataBlock().getSampleRate(); } @@ -110,14 +123,21 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { SnapshotGeometry geometry = ArrayManager.getArrayManager().getSnapshotGeometry(hydrophoneMap, groupDataUnit.getTimeMilliseconds()); + + + System.out.println("HYDROPHONE 0 POSITION: "+ groupDataUnit.getTimeMilliseconds() + " " + geometry.getGeometry()[0]); + // if (groupDataUnit.getSubDetection(0).getUID() == 9035003222L) { -// if (superDetection.getSubDetection(0).getUID() == 9035004477L) { -// System.out.println("Found it"); -// } + // if (superDetection.getSubDetection(0).getUID() == 9035004477L) { + // System.out.println("Found it"); + // } TOADInformation toadInformation = toadCalculator.getTOADInformation(superDetection.getSubDetections(), sampleRate, allChannels, geometry); boolean toadOK = checkTOADInformation(toadInformation); + +// System.out.println("ToadOK: " + toadOK + " toadInformation: " + toadInformation + " allChannels: " + allChannels + " geometry: " + geometry); + if (!toadOK) { return null; } @@ -152,7 +172,7 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { public int countUsableTOADS(TOADInformation toadInformation) { return countUsableTOADS(toadInformation, toadBaseParams.getMinCorrelation()); } - + /** * Count the number of TOAD values which have a correlation coefficient or 'score' * >= the minimum. @@ -258,21 +278,24 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { return null; } + /** - * Calculate a Log Likelihood value for the given geometry and set of delays. - * @param geometry - * @param delays - * @return + * Calculates chi2 or log likelihood for a position. + * @param geometry - the geometry of the receivers. + * @param toadInformation - the received time delay information. + * @param position - the position to test. + * @param flag - which value to calculate. + * @return the chi2 or log likilihood value. */ - public LogLikelihoodData calcLogLikelihood(SnapshotGeometry geometry, TOADInformation toadInformation, double[] position) { + private FitTestValue multivariateFunction(SnapshotGeometry geometry, TOADInformation toadInformation, double[] position, MultiVariateType flag) { /** * There is an awful lot of repeated code in this and the Chi2 function. Would be good * to sort out some time ! */ -// if (1>0) { -// Chi2Data chiRes = calcChi2(geometry, toadInformation, position); -// LogLikelihoodData lld = new LogLikelihoodData(-chiRes.getChi2(), chiRes.getDegreesOfFreedom()); -// } + // if (1>0) { + // Chi2Data chiRes = calcChi2(geometry, toadInformation, position); + // LogLikelihoodData lld = new LogLikelihoodData(-chiRes.getChi2(), chiRes.getDegreesOfFreedom()); + // } if (position == null || position.length == 0) { return null; @@ -343,18 +366,140 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { * delay error, but more if you're end on. */ errSq += Math.pow(exp*cError/c, 2.); - /* - * Do the full calc -ln(2pi)/2 - .5ln(sig) - .5(diff^2/errSq). - * So note the 0.5 instead of the which is to take the sqrt(errSq) ! - */ - llVal -= (halflog2pi + 0.5*Math.log(errSq) + Math.pow(val-exp, 2)/errSq/2); -// llVal -= Math.pow(val-exp, 2)/errSq/2.; - nGood++; + + switch (flag) { + + case LOG_LIKILIHOOD: + /* + * Do the full calc -ln(2pi)/2 - .5ln(sig) - .5(diff^2/errSq). + * So note the 0.5 instead of the which is to take the sqrt(errSq) ! + */ + llVal -= (halflog2pi + 0.5*Math.log(errSq) + Math.pow(val-exp, 2)/errSq/2); + // llVal -= Math.pow(val-exp, 2)/errSq/2.; + nGood++; + break; + + case CHI2: + llVal += Math.pow(val-exp, 2)/errSq; + nGood++; + break; + + } } } - return new LogLikelihoodData(llVal, nGood-nDim); - } + + switch (flag) { + case LOG_LIKILIHOOD: + return new LogLikelihoodData(llVal, nGood-nDim); + case CHI2: + return new Chi2Data(llVal, nGood-nDim); + } + //something has gone pretty wrong + return null; + + } + + + /** + * Calculate a Log Likelihood value for the given geometry and set of delays. + * @param geometry + * @param delays + * @return + */ + public LogLikelihoodData calcLogLikelihood(SnapshotGeometry geometry, TOADInformation toadInformation, double[] position) { + + return (LogLikelihoodData) multivariateFunction( geometry, toadInformation, position, MultiVariateType.LOG_LIKILIHOOD); +// /** +// * There is an awful lot of repeated code in this and the Chi2 function. Would be good +// * to sort out some time ! +// */ +// // if (1>0) { +// // Chi2Data chiRes = calcChi2(geometry, toadInformation, position); +// // LogLikelihoodData lld = new LogLikelihoodData(-chiRes.getChi2(), chiRes.getDegreesOfFreedom()); +// // } +// +// if (position == null || position.length == 0) { +// return null; +// } +// double[][] delays = toadInformation.getToadSeconds(); +// double[][] delayErrors = toadInformation.getToadErrorsSeconds(); +// +// double minCorrelationValue = toadBaseParams.getMinCorrelation(); +// +// int nDim = position.length; +// int[] hydrophones = toadInformation.getHydrophoneList(); +// int[] channels = toadInformation.getChannelList(); +// int nChan = channels.length; +// double[] expectedDelays = new double[nChan]; +// double c = geometry.getCurrentArray().getSpeedOfSound(); +// double cError = geometry.getCurrentArray().getSpeedOfSoundError(); +// PamVector centre = geometry.getGeometricCentre(); +// double[] channelErrors = new double[nChan]; +// double[] streamerErrors = new double[nChan]; +// int[] streamerId = new int[nChan]; +// double[][] correlationScores = toadInformation.getToadScores(); +// PamVector positionVec = new PamVector(position).add(centre); +// // calculate the absolute distance (then time) to each hydrophone used in the delay matrix. +// for (int i = 0; i < nChan; i++) { +// double r = 0; +// /* +// * use the hydrophone LUT from TOAD information to make sure we get the right phone +// * since channel groups may not be in a sensible order. +// */ +// PamVector hydrophoneGeom = geometry.getGeometry()[hydrophones[i]]; +// PamVector rv = positionVec.sub(hydrophoneGeom); +// expectedDelays[i] = rv.norm(nDim) / c; +// /** +// * Now work out the expected range error along the unit vector position-hydrophone+centre +// */ +// PamVector rvu = rv.getUnitVector(); +// PamVector hErr = geometry.getHydrophoneErrors()[hydrophones[i]]; //NB. this is not really a vector ! +// channelErrors[i] = Math.pow(rvu.sumComponentsSquared(hErr)/c, 2); // square and convert to time now +// PamVector sErr = geometry.getStreamerErrors()[hydrophones[i]]; // this isn't a vector either +// streamerId[i] = geometry.getCurrentArray().getStreamerForPhone(hydrophones[i]); +// if (sErr != null) { +// streamerErrors[i] = Math.pow(rvu.sumComponentsSquared(sErr)/c, 2); +// } +// } +// double llVal = 0.; +// int nGood = 0; +// for (int i = 0; i < nChan; i++) { +// for (int j = i+1; j < nChan; j++) { +// double val = (delays[i][j]); +// if (Double.isNaN(val)) { +// continue; +// } +// if (correlationScores == null || correlationScores[i][j] < minCorrelationValue) { +// continue; +// } +// double exp = expectedDelays[j]-expectedDelays[i]; +// // now work out the squared error... +// double errSq = Math.pow(delayErrors[i][j], 2)+channelErrors[i]+channelErrors[j]; +// +// // add in the streamer errors if they are in different streamers. +// if (streamerId[i] != streamerId[j]) { +// errSq += streamerErrors[i]+streamerErrors[j]; +// } +// /* +// * Finally there is an error due to uncertainty in c, which is +// * obviously proportional to the expected delay. i.e. if you're +// * perpendicular to a hydrophone pair, then there will be zero +// * delay error, but more if you're end on. +// */ +// errSq += Math.pow(exp*cError/c, 2.); +// /* +// * Do the full calc -ln(2pi)/2 - .5ln(sig) - .5(diff^2/errSq). +// * So note the 0.5 instead of the which is to take the sqrt(errSq) ! +// */ +// llVal -= (halflog2pi + 0.5*Math.log(errSq) + Math.pow(val-exp, 2)/errSq/2); +// // llVal -= Math.pow(val-exp, 2)/errSq/2.; +// nGood++; +// } +// } +// return new LogLikelihoodData(llVal, nGood-nDim); + } + /** * Calculate a chi2 value for the given geometry and set of delays. * @param geometry array geometry. @@ -363,93 +508,104 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { * @return */ public Chi2Data calcChi2(SnapshotGeometry geometry, TOADInformation toadInformation, double[] position) { - if (position == null || position.length == 0) { - return null; - } - double[][] delays = toadInformation.getToadSeconds(); - double[][] delayErrors = toadInformation.getToadErrorsSeconds(); - - double minCorrelationValue = toadBaseParams.getMinCorrelation(); - - int nDim = position.length; - int[] hydrophones = toadInformation.getHydrophoneList(); - int[] channels = toadInformation.getChannelList(); - int nChan = channels.length; - double[] expectedDelays = new double[nChan]; - double c = geometry.getCurrentArray().getSpeedOfSound(); - double cError = geometry.getCurrentArray().getSpeedOfSoundError(); - PamVector centre = geometry.getGeometricCentre(); - double[] channelErrors = new double[nChan]; - double[] streamerErrors = new double[nChan]; - int[] streamerId = new int[nChan]; - double[][] correlationScores = toadInformation.getToadScores(); - PamVector positionVec = new PamVector(position).add(centre); - // calculate the absolute distance (then time) to each hydrophone used in the delay matrix. - for (int i = 0; i < nChan; i++) { - double r = 0; - /* - * use the hydrophone LUT from TOAD information to make sure we get the right phone - * since channel groups may not be in a sensible order. - */ - PamVector hydrophoneGeom = geometry.getGeometry()[hydrophones[i]]; - PamVector rv = positionVec.sub(hydrophoneGeom); - expectedDelays[i] = rv.norm() / c; - /** - * Now work out the expected range error along the unit vector position-hydrophone+centre - * - * These calculations were incorrect since they look at dot of the - * error against the direction to the source, which is wrong ! - * e.g. if error was (1,1,1) it appears as a vector along that bearing, if this - * were dotted with an angle perpendicular to this, then it would give a very - * different answer. - * The correct way to calculate the inter pair error is to add each component of - * the error along the vector joining the two hydrophones. This error should be stored - * as a pair error and then dot producted with the unit vector towards the source. - * Need to rewrite and produce a matrix of channel pair errors! - */ - PamVector rvu = rv.getUnitVector(); - PamVector hErr = geometry.getHydrophoneErrors()[hydrophones[i]]; - // if (hErr != null) { - channelErrors[i] = Math.pow(rvu.sumComponentsSquared(hErr)/c, 2); // square and convert to time now - // } - PamVector sErr = geometry.getStreamerErrors()[hydrophones[i]]; - streamerId[i] = geometry.getCurrentArray().getStreamerForPhone(hydrophones[i]); - if (sErr != null) { - streamerErrors[i] = Math.pow(rvu.sumComponentsSquared(sErr)/c, 2); - } - } - double chiVal = 0.; - int nGood = 0; - for (int i = 0; i < nChan; i++) { - for (int j = i+1; j < nChan; j++) { - double val = (delays[i][j]); - if (Double.isNaN(val)) { - continue; - } - if (correlationScores == null || correlationScores[i][j] < minCorrelationValue) { - continue; - } - double exp = expectedDelays[j]-expectedDelays[i]; - // now work out the squared error... - double errSq = Math.pow(delayErrors[i][j], 2)+channelErrors[i]+channelErrors[j]; - - // add in the streamer errors if they are in different streamers. - if (streamerId[i] != streamerId[j]) { + + return (Chi2Data) multivariateFunction( geometry, toadInformation, position, MultiVariateType.CHI2); +// +// if (position == null || position.length == 0) { +// return null; +// } +// double[][] delays = toadInformation.getToadSeconds(); +// double[][] delayErrors = toadInformation.getToadErrorsSeconds(); +// +// double minCorrelationValue = toadBaseParams.getMinCorrelation(); +// +// int nDim = position.length; +// int[] hydrophones = toadInformation.getHydrophoneList(); +// int[] channels = toadInformation.getChannelList(); +// int nChan = channels.length; +// double[] expectedDelays = new double[nChan]; +// double c = geometry.getCurrentArray().getSpeedOfSound(); +// double cError = geometry.getCurrentArray().getSpeedOfSoundError(); +// PamVector centre = geometry.getGeometricCentre(); +// double[] channelErrors = new double[nChan]; +// double[] streamerErrors = new double[nChan]; +// int[] streamerId = new int[nChan]; +// double[][] correlationScores = toadInformation.getToadScores(); +// PamVector positionVec = new PamVector(position).add(centre); +// // calculate the absolute distance (then time) to each hydrophone used in the delay matrix. +// for (int i = 0; i < nChan; i++) { +// double r = 0; +// /* +// * use the hydrophone LUT from TOAD information to make sure we get the right phone +// * since channel groups may not be in a sensible order. +// */ +// PamVector hydrophoneGeom = geometry.getGeometry()[hydrophones[i]]; +// PamVector rv = positionVec.sub(hydrophoneGeom); +// +// +// expectedDelays[i] = rv.norm() / c; +// /** +// * Now work out the expected range error along the unit vector position-hydrophone+centre +// * +// * These calculations were incorrect since they look at dot of the +// * error against the direction to the source, which is wrong ! +// * e.g. if error was (1,1,1) it appears as a vector along that bearing, if this +// * were dotted with an angle perpendicular to this, then it would give a very +// * different answer. +// * The correct way to calculate the inter pair error is to add each component of +// * the error along the vector joining the two hydrophones. This error should be stored +// * as a pair error and then dot producted with the unit vector towards the source. +// * Need to rewrite and produce a matrix of channel pair errors! +// */ +// PamVector rvu = rv.getUnitVector(); +// PamVector hErr = geometry.getHydrophoneErrors()[hydrophones[i]]; +// // if (hErr != null) { +// channelErrors[i] = Math.pow(rvu.sumComponentsSquared(hErr)/c, 2); // square and convert to time now +// // } +// PamVector sErr = geometry.getStreamerErrors()[hydrophones[i]]; +// streamerId[i] = geometry.getCurrentArray().getStreamerForPhone(hydrophones[i]); +// if (sErr != null) { +// streamerErrors[i] = Math.pow(rvu.sumComponentsSquared(sErr)/c, 2); +// } +// } +// double chiVal = 0.; +// int nGood = 0; +// for (int i = 0; i < nChan; i++) { +// for (int j = i+1; j < nChan; j++) { +// double val = (delays[i][j]); +// if (Double.isNaN(val)) { +// continue; +// } +// if (correlationScores == null || correlationScores[i][j] < minCorrelationValue) { +// continue; +// } +// double exp = expectedDelays[j]-expectedDelays[i]; +// // now work out the squared error... +// double errSq = Math.pow(delayErrors[i][j], 2)+channelErrors[i]+channelErrors[j]; +// +// // add in the streamer errors if they are in different streamers. +// if (streamerId[i] != streamerId[j]) { +// // errSq += streamerErrors[i]+streamerErrors[j]; +// // continue; simulate unsynchronised streamers. +// } +// +// // add in the streamer errors if they are in different streamers. +// if (streamerId[i] != streamerId[j]) { // errSq += streamerErrors[i]+streamerErrors[j]; - // continue; simulate unsynchronised streamers. - } - /* - * Finally there is an error due to uncertainty in c, which is - * obviously proportional to the expected delay. i.e. if you're - * perpendicular to a hydrophone pair, then there will be zero - * delay error, but more if you're end on. - */ - errSq += Math.pow(exp*cError/c, 2.); - chiVal += Math.pow(val-exp, 2)/errSq; - nGood++; - } - } - return new Chi2Data(chiVal, nGood-nDim); +// } +// +// /* +// * Finally there is an error due to uncertainty in c, which is +// * obviously proportional to the expected delay. i.e. if you're +// * perpendicular to a hydrophone pair, then there will be zero +// * delay error, but more if you're end on. +// */ +// errSq += Math.pow(exp*cError/c, 2.); +// chiVal += Math.pow(val-exp, 2)/errSq; +// nGood++; +// } +// } +// return new Chi2Data(chiVal, nGood-nDim); } /** * Estimate an elliptical error with axes aligned with largest error coordinate based on the curvature of the Chi2 / likelihood surface. @@ -459,22 +615,22 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { * @return Elliptical error */ public EllipticalError estimateEllipticalError(SnapshotGeometry geometry, TOADInformation toadInformation, double[] position) { -// Chi2Data centralChi2 = calcChi2(geometry, toadInformation, position); + // Chi2Data centralChi2 = calcChi2(geometry, toadInformation, position); /** * Will take the principle axis as a direct line to the detection from the central point of the array * geometry. */ -// PamVector arrayCentre = geometry.getGeometricCentre(); + // PamVector arrayCentre = geometry.getGeometricCentre(); // position is already relative to the geometric centre of the array. PamVector posVec = new PamVector(position); double[] errors = new double[6]; PamVector[] errVecs = new PamVector[3]; // get a unit vector along the direction from the array centre to the calculated coordinate. errVecs[0] = posVec.getUnitVector(); -// if (posVec.getCoordinate(0) > 6 && posVec.getCoordinate(0) < 8 && Math.abs(posVec.getCoordinate(1)) < 5) { -// System.out.println(posVec); -// } - + // if (posVec.getCoordinate(0) > 6 && posVec.getCoordinate(0) < 8 && Math.abs(posVec.getCoordinate(1)) < 5) { + // System.out.println(posVec); + // } + /* * to get secondary axis we can rotate by 90 degrees in any direction apart from * the principle direction of the vector. @@ -490,7 +646,7 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { errVecs[1] = (errVecs[0].vecProd(PamVector.zAxis)).getUnitVector(); errVecs[2] = errVecs[1].vecProd(errVecs[0]); } - + /* * Error along each principle axis. */ @@ -501,11 +657,11 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { } // now need to convert the error vectors to heading, pitch and roll. double[] angles = PamVector.getHeadingPitchRoll(errVecs); - + EllipticalError elError = new EllipticalError(angles, errors); return elError; } - + /** * Estimate the error in Cartesian coordinates based on the curvature of the Chi2 / likelihood surface. * @param geometry Array geometry @@ -544,7 +700,7 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { return simpleError; } - + /** * Get the error along the principle direction * @param geometry current array geometry @@ -588,9 +744,9 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { */ double nDF = Math.max(centralChi2.getDegreesOfFreedom(), 1); double chiCent = centralChi2.getTestScore(); -// if (chiCent == 0) { -// chiCent = 1; -// } + // if (chiCent == 0) { + // chiCent = 1; + // } double step; double[] sig = new double[2]; double[] shiftedChi = new double[2]; @@ -632,23 +788,24 @@ abstract public class TOADBaseAlgorithm extends LocaliserAlgorithm3D { * @see group3dlocaliser.algorithm.LocaliserAlgorithm3D#getSourceSettingsPane(java.awt.Window) */ @Override - public ManagedSettingsPane getSourceSettingsPane(Window parent, PamDataBlock detectionSource) { + public ToadManagedSettingsPane getSourceSettingsPane(Window parent, PamDataBlock detectionSource) { // return new TOADSourcePane(parent); // return toadCalculator.getSettingsPane(); - if (toadCalculator != null) { + + //System.out.println("Get source settings pane: " +toadCalculator + " " + tspwc); + + if (toadCalculator != null && tspwc == null) { /* * this gets the algorithm specific settings pane. We want to tab * this with a channel panel. */ - ManagedSettingsPane toadPane = toadCalculator.getSettingsPane(parent, detectionSource); - TOADSettingsPaneWithChannels tspwc = new TOADSettingsPaneWithChannels<>(parent, this, toadPane); + ManagedSettingsPane toadPane = toadCalculator.getSettingsPane(parent, detectionSource); + tspwc = new TOADSettingsPaneWithChannels(parent, this, toadPane); tspwc.getChannelPanel().setMultiColumn(true); tspwc.getChannelPanel().setAvailableChannels(detectionSource.getChannelMap()); return tspwc; } - else { - return null; - } + return tspwc; } @Override diff --git a/src/group3dlocaliser/algorithm/toadbase/TOADInformation.java b/src/group3dlocaliser/algorithm/toadbase/TOADInformation.java index 6fcb59e0..4ba34d95 100644 --- a/src/group3dlocaliser/algorithm/toadbase/TOADInformation.java +++ b/src/group3dlocaliser/algorithm/toadbase/TOADInformation.java @@ -1,11 +1,13 @@ package group3dlocaliser.algorithm.toadbase; +import group3dlocaliser.algorithm.hyperbolic.HyperbolicParams; + /** * Class holding information about TOAD delays, channels, etc. * @author dg50 * */ -public class TOADInformation { +public class TOADInformation implements Cloneable { private double[][] toadSeconds; @@ -154,5 +156,17 @@ public class TOADInformation { public void setToadScores(double[][] toadScores) { this.toadScores = toadScores; } + + + @Override + public TOADInformation clone() { + try { + + return (TOADInformation) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } } diff --git a/src/group3dlocaliser/algorithm/toadbase/TOADOptionsPane.java b/src/group3dlocaliser/algorithm/toadbase/TOADOptionsPane.java index 8ce684d9..283d0e4e 100644 --- a/src/group3dlocaliser/algorithm/toadbase/TOADOptionsPane.java +++ b/src/group3dlocaliser/algorithm/toadbase/TOADOptionsPane.java @@ -13,7 +13,14 @@ import javafx.scene.layout.GridPane; import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamLabel; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; +import pamViewFX.validator.PamValidator; +/** + * The TOAD options pane. + * + * @author Jamie Macaulay + * + */ public class TOADOptionsPane extends SettingsPane{ private BorderPane mainPane; @@ -21,22 +28,89 @@ public class TOADOptionsPane extends SettingsPane{ private TextField minCorrelation; private TextField minTimeDelays, minGroups; + + private PamValidator validator; public TOADOptionsPane(Object ownerWindow) { super(ownerWindow); + + validator = new PamValidator(); + mainPane = new BorderPane(); GridPane gridPane = new PamGridPane(); int x = 0, y = 0; gridPane.add(new PamLabel("Min correlation ", Pos.CENTER_RIGHT), x++, y); - gridPane.add(minCorrelation = new TextField("1234"), x, y); + gridPane.add(minCorrelation = new TextField("0"), x, y); + + + //create check to show at least some check boxes need to be selected. + validator.createCheck() + .dependsOn(("min_corr"), minCorrelation.textProperty()) + .withMethod(c -> { + + try { + double newCorr = Double.valueOf(minCorrelation.getText()); + if (newCorr>=0.9) { + c.warn("It's very unblikely you will get a correlation this high"); + } + if (newCorr>=1.) { + c.error("Correlation values cannot be higher than 1"); + } + + } + catch (Exception e) { + c.error("Input cannot be read as a number"); + } + }) + .decorates(minCorrelation) + .immediate(); + + x = 0; y++; gridPane.add(new PamLabel("Min TOAD measurements ", Pos.CENTER_RIGHT), x++, y); - gridPane.add(minTimeDelays = new TextField("1234"), x, y); + gridPane.add(minTimeDelays = new TextField("3"), x, y); + + validator.createCheck() + .dependsOn(("min_delays"), minTimeDelays.textProperty()) + .withMethod(c -> { + + try { + int minDelays = Integer.valueOf(minTimeDelays.getText()); + + //TODO would be great to add something which catches whether max delays have been reached. + + } + catch (Exception e) { + c.error("Min TOAD measurements cannot be read as a number"); + } + }) + .decorates(minTimeDelays) + .immediate(); + x = 0; y++; gridPane.add(new PamLabel("Min groups ", Pos.CENTER_RIGHT), x++, y); - gridPane.add(minGroups = new TextField("1234"), x, y); + gridPane.add(minGroups = new TextField("0"), x, y); + + + validator.createCheck() + .dependsOn(("min_groups"), minGroups.textProperty()) + .withMethod(c -> { + + try { + int minGroupsVal = Integer.valueOf(minGroups.getText()); + + //TODO would be great to add something which catches whether max delays have been reached. + + } + catch (Exception e) { + c.error("Minb groups input cannot be read as a number"); + } + }) + .decorates(minGroups) + .immediate(); + minCorrelation.setTooltip(new Tooltip("Minimum cross correlation coefficient for each hydrophone pair")); minTimeDelays.setTooltip(new Tooltip("Minimum number of time delays with acceptable cross correlation")); minGroups.setTooltip(new Tooltip("Minimum number of channel groups included with acceptable cross correlations")); diff --git a/src/group3dlocaliser/algorithm/toadbase/TOADSettingsPaneWithChannels.java b/src/group3dlocaliser/algorithm/toadbase/TOADSettingsPaneWithChannels.java index a08dbad0..d3ef1a02 100644 --- a/src/group3dlocaliser/algorithm/toadbase/TOADSettingsPaneWithChannels.java +++ b/src/group3dlocaliser/algorithm/toadbase/TOADSettingsPaneWithChannels.java @@ -2,26 +2,40 @@ package group3dlocaliser.algorithm.toadbase; import PamController.SettingsPane; import PamUtils.PamUtils; +import group3dlocaliser.ToadManagedSettingsPane; import javafx.scene.Node; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TabPane.TabClosingPolicy; import javafx.scene.layout.BorderPane; +import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamTitledBorderPane; import pamViewFX.fxNodes.pamDialogFX.ManagedSettingsPane; import pamViewFX.fxNodes.pamDialogFX.SwingFXDialogWarning; import pamViewFX.fxNodes.utilityPanes.ChannelPanelFX; -public class TOADSettingsPaneWithChannels extends ManagedSettingsPane { +public class TOADSettingsPaneWithChannels extends ToadManagedSettingsPane { private ManagedSettingsPane toadSettingsPane; private ChSettingsPane tabbedPane; private TabPane tabPane; + + /** + * Get the tab pane for the settings. + * @return the tab pane for the different settings. + */ + @Override + public TabPane getTabPane() { + return tabPane; + } + private ChannelPanelFX channelPanel; private TOADBaseAlgorithm toadBaseAlgorithm; private Object parent; private TOADOptionsPane toadOptionsPane; + private boolean warnError = true; + public TOADSettingsPaneWithChannels(Object parent, TOADBaseAlgorithm toadBaseAlgorithm, ManagedSettingsPane toadSettingsPane) { this.parent = parent; @@ -42,7 +56,11 @@ public class TOADSettingsPaneWithChannels extends ManagedSettingsPane { int chMap = channelPanel.getChannelMap(); toadBaseAlgorithm.getToadBaseParams().setChannelBitmap(chMap); int nSelChannels = PamUtils.getNumChannels(chMap); - if (nSelChannels < 3) { + if (nSelChannels < 3 && warnError) { + /** + * TODO - This is a nasty way of doing this. There should be a validator in this pane which is checked before getParams is called ] + * but that would require a lot of restructuring. + */ return SwingFXDialogWarning.showWarning(parent, "Channel selection", "Not enough channels selected"); } TOADBaseParams ans = toadOptionsPane.getParams(toadBaseAlgorithm.getToadBaseParams()); @@ -51,6 +69,7 @@ public class TOADSettingsPaneWithChannels extends ManagedSettingsPane { @Override public T findParams() { + //System.out.println("CHANNELS: Get channel map: " + toadBaseAlgorithm.getToadBaseParams().getChannelBitmap() + " " + PamUtils.getNumChannels(toadBaseAlgorithm.getToadBaseParams().getChannelBitmap())); channelPanel.setChannelMap(toadBaseAlgorithm.getToadBaseParams().getChannelBitmap()); toadOptionsPane.setParams(toadBaseAlgorithm.getToadBaseParams()); return null; @@ -70,8 +89,17 @@ public class TOADSettingsPaneWithChannels extends ManagedSettingsPane { BorderPane boderPane = new BorderPane(); boderPane.setCenter(new PamTitledBorderPane("Timing options", toadOptionsPane.getContentNode())); boderPane.setBottom(toadSettingsPane.getSettingsPane().getContentNode()); - tabPane.getTabs().add(new Tab("Timing", boderPane)); - tabPane.getTabs().add(new Tab("Channels", new PamTitledBorderPane("Channel Selection", channelPanel.getContentNode()))); + + Tab timingTab = new Tab("Timing", boderPane); + timingTab.setGraphic(PamGlyphDude.createPamIcon("mdi2w-waveform")); + + tabPane.getTabs().add(timingTab ); + + + Tab channelsTab = new Tab("Channels", new PamTitledBorderPane("Channel Selection", channelPanel.getContentNode())); + channelsTab.setGraphic(PamGlyphDude.createPamIcon("mdi2f-format-list-numbered-rtl")); + + tabPane.getTabs().add(channelsTab); } @Override @@ -116,4 +144,10 @@ public class TOADSettingsPaneWithChannels extends ManagedSettingsPane { public ChannelPanelFX getChannelPanel() { return channelPanel; } + + @Override + public void setErrorWarn(boolean warn) { + this.warnError = warn; + + } } diff --git a/src/group3dlocaliser/algorithm/toadmcmc/MCMCEllipticalError.java b/src/group3dlocaliser/algorithm/toadmcmc/MCMCEllipticalError.java new file mode 100644 index 00000000..4b0210a8 --- /dev/null +++ b/src/group3dlocaliser/algorithm/toadmcmc/MCMCEllipticalError.java @@ -0,0 +1,58 @@ +package group3dlocaliser.algorithm.toadmcmc; + +import Localiser.algorithms.locErrors.EllipseLocErrorDraw; +import Localiser.algorithms.locErrors.EllipticalError; +import Localiser.algorithms.locErrors.LocErrorGraphics; +import PamUtils.PamArrayUtils; + +/** + * Elliptical error for MCMC. + * + * @author Jamie Macaulay + * + */ +public class MCMCEllipticalError extends EllipticalError { + + /** + * Class for drawing the error. + */ + private EllipseLocErrorDraw ellipseLocErrorDraw = new MCMCErrorDraw(this); + + + private float[][] points; + + + private double[] meanLoc; + + + public double[] getMeanLoc() { + return meanLoc; + } + + + + public MCMCEllipticalError(double[][] points, double[] meanLoc) { + super(points); + this.points = PamArrayUtils.double2Float(points); + this.meanLoc = meanLoc; + } + + + + public float[][] getPoints() { + return points; + } + + + + public void setPoints(float[][] points) { + this.points = points; + } + + + + @Override + public LocErrorGraphics getErrorDraw() { + return ellipseLocErrorDraw; + } +} diff --git a/src/group3dlocaliser/algorithm/toadmcmc/MCMCErrorDraw.java b/src/group3dlocaliser/algorithm/toadmcmc/MCMCErrorDraw.java new file mode 100644 index 00000000..70ff4f78 --- /dev/null +++ b/src/group3dlocaliser/algorithm/toadmcmc/MCMCErrorDraw.java @@ -0,0 +1,110 @@ +package group3dlocaliser.algorithm.toadmcmc; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Point2D; + +import Localiser.algorithms.locErrors.EllipseLocErrorDraw; +import Localiser.algorithms.locErrors.EllipticalError; +import Localiser.algorithms.locErrors.ErrorEllipse; +import PamUtils.LatLong; +import PamUtils.PamArrayUtils; +import PamView.GeneralProjector; +import PamView.TransformShape; +import PamguardMVC.PamDataUnit; +import pamMaths.PamVector; + + +/** + * Plots some point son the map + * @author Jamie Macaulay + * + */ +public class MCMCErrorDraw extends EllipseLocErrorDraw { + + private MCMCEllipticalError ellipticalErr; + + MCMCErrorDraw(MCMCEllipticalError ellipticalError) { + super(ellipticalError); + this.ellipticalErr = ellipticalError; + } + + @Override + public TransformShape drawOnMap(Graphics g, PamDataUnit pamDetection, LatLong errorOrigin, + GeneralProjector generalProjector, Color ellipseColor) { + if (getEllipticalError() == null) { + return null; + } + + if (ellipticalErr.getPoints()!=null) { + drawMCMCCloudOnMap(g, pamDetection, errorOrigin, generalProjector, ellipseColor); + } + + switch(getDrawType()) { + case DRAW_LINES: + return drawLinesOnMap(g, pamDetection, errorOrigin, generalProjector, ellipseColor); + case DRAW_OVALS: + return drawOvalsOnMap(g, pamDetection, errorOrigin, generalProjector, ellipseColor); + } + + + return null; + } + + + public TransformShape drawMCMCCloudOnMap(Graphics g, PamDataUnit pamDetection, LatLong errorOrigin, + GeneralProjector generalProjector, Color ellipseColor) { + + + //System.out.println("Start MCMCM Plot errors: start " + ellipticalErr.getPoints().length); + Graphics2D g2d = (Graphics2D)g; + + ellipticalErr.getPoints(); + + LatLong point1 = null; + LatLong point2 = null; + Point point1xy = null; + Point point2xy = null; + for (int i=0; i { + + private MCMCPane mainPane; + + public MCMCLocaliserPane() { + mainPane = new MCMCPane(); + ((Pane) mainPane.getContentNode()).setPadding(new Insets(5,5,5,5)); + } + + + @Override + public MCMCParams2 getParams(MCMCParams2 currParams) { + if (currParams==null) return mainPane.getParams(new MCMCParams2()); + return mainPane.getParams(currParams); + } + + @Override + public void setParams(MCMCParams2 input) { + if (input==null) input= new MCMCParams2(); + mainPane.setParams(input); + } + + @Override + public String getName() { + return "MCMC Settings"; + } + + @Override + public Node getContentNode() { + return mainPane.getContentNode(); + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + +} diff --git a/src/group3dlocaliser/algorithm/toadmcmc/ToadMCMCLocaliser.java b/src/group3dlocaliser/algorithm/toadmcmc/ToadMCMCLocaliser.java new file mode 100644 index 00000000..20546f90 --- /dev/null +++ b/src/group3dlocaliser/algorithm/toadmcmc/ToadMCMCLocaliser.java @@ -0,0 +1,233 @@ +package group3dlocaliser.algorithm.toadmcmc; + +import java.util.ArrayList; + +import org.apache.commons.math.distribution.ChiSquaredDistributionImpl; + +import Array.ArrayManager; +import Array.SnapshotGeometry; +import Localiser.LocaliserPane; +import Localiser.algorithms.genericLocaliser.MinimisationFunction; +import Localiser.algorithms.genericLocaliser.MCMC.MCMC; +import Localiser.algorithms.genericLocaliser.MCMC.MCMCResult; +import Localiser.algorithms.locErrors.EllipticalError; +import Localiser.detectionGroupLocaliser.GroupLocResult; +import Localiser.detectionGroupLocaliser.GroupLocalisation; +import Localiser.algorithms.genericLocaliser.MCMC.MCMCParams2; +import PamDetection.AbstractLocalisation; +import PamDetection.LocContents; +import PamUtils.CPUMonitor; +import PamUtils.LatLong; +import PamUtils.PamArrayUtils; +import PamguardMVC.PamDataUnit; +import generalDatabase.SQLLoggingAddon; +import group3dlocaliser.Group3DLocaliserControl; +import group3dlocaliser.algorithm.Chi2Data; +import group3dlocaliser.algorithm.LocaliserAlgorithmParams; +import group3dlocaliser.algorithm.crossedbearing.CrossedBearingSQLAddon; +import group3dlocaliser.algorithm.hyperbolic.HyperbolicParams; +import group3dlocaliser.algorithm.toadbase.TOADBaseAlgorithm; +import group3dlocaliser.algorithm.toadbase.TOADInformation; +import group3dlocaliser.grouper.DetectionGroupedSet; +import pamMaths.PamVector; + + + +public class ToadMCMCLocaliser extends TOADBaseAlgorithm { + + MCMC mcmc = new MCMC(); + + /** + * CPU monitor + */ + private CPUMonitor cpuMCMC; + + private MCMCLocaliserPane mcmcSettingsPane; + + public ToadMCMCLocaliser(Group3DLocaliserControl group3dLocaliser) { + super(group3dLocaliser); + cpuMCMC = new CPUMonitor(); + + } + + + public DetectionGroupedSet preFilterLoc(DetectionGroupedSet preGroups) { + + + return preGroups; + } + + @Override + public String getName() { + return "MCMC"; + } + + @Override + public String getToolTipText() { + return "Time delay of arrival Markov chain Monte Carlo (MCMC) based localisation Computationaly very slow but less prone to runaway and calculates more accurate error distributions" ; + } + + @Override + public LocContents getLocContents() { + // TODO Auto-generated method stub + return null; + } + + @Override + public LocaliserPane getAlgorithmSettingsPane() { + if (mcmcSettingsPane==null) { + mcmcSettingsPane = new MCMCLocaliserPane(); + } + return mcmcSettingsPane; + } + + @Override + public boolean hasParams() { + return true; + } + + @Override + public void notifyModelProgress(double progress) { + // TODO Auto-generated method stub + } + + + + @Override + public AbstractLocalisation processTOADs(PamDataUnit groupDataUnit, SnapshotGeometry geometry, + TOADInformation toadInformation) { + +// System.out.println("Run MCMC: ------ " + groupDataUnit.getUID()); +// PamArrayUtils.printArray(toadInformation.getToadSeconds()); + + /** + * This module is a little odd in that it stores paramters for each algorithm in it's own has table without acc + */ + MCMCParams2 params = (MCMCParams2) group3dLocaliser.getLocaliserAlgorithmParams(this).getAlgorithmParameters(); + + cpuMCMC.start(); + PamVector centre = geometry.getGeometricCentre(); + + + MCMCChi2Function chi2Func = new MCMCChi2Function(geometry, toadInformation); + mcmc.setChi2(chi2Func); + + //set the parameters. + mcmc.setSettings(params); + + //these are the *best results. + ArrayList mcmcResult = mcmc.runMCMCAlgorithm(); + + + + GroupLocalisation groupLocalisation = null; + for (int i=0; i { + + private MCMCPane mcmcPane; + + private PamVBox mainPane; + + private PamToggleSwitch toggleSwitch; + + public MimplexLocaliserPane() { + + mcmcPane = new MCMCPane(); + + mainPane = new PamVBox(); + mainPane.setSpacing(5); + + mainPane.getChildren().add( createMimplexPane()); + mainPane.getChildren().add(mcmcPane.getContentNode()); + + mainPane.setPadding(new Insets(5,5,5,5)); + + } + + private Pane createMimplexPane() { + + PamVBox holder = new PamVBox(); + + toggleSwitch = new PamToggleSwitch("Always localise first combination"); + + Label label = new Label("Pre-localisation"); + label.setFont(Font.font(null,FontWeight.BOLD, 11)); + + + holder.getChildren().addAll(label, toggleSwitch); + + return holder; + } + + @Override + public MimplexParams getParams(MimplexParams currParams) { + + MimplexParams params; + if (currParams==null) params = new MimplexParams(); + else params = currParams; + + params.useFirstCombination = toggleSwitch.isSelected(); + + + params = (MimplexParams) mcmcPane.getParams(params); + + return params; + } + + + @Override + public void setParams(MimplexParams input) { + if (input==null) input= new MimplexParams(); + + toggleSwitch.setSelected(input.useFirstCombination); + mcmcPane.setParams(input); + } + + @Override + public String getName() { + return "Mimplex Settings"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + + +} diff --git a/src/group3dlocaliser/algorithm/toadmimplex/MimplexParams.java b/src/group3dlocaliser/algorithm/toadmimplex/MimplexParams.java new file mode 100644 index 00000000..83c22067 --- /dev/null +++ b/src/group3dlocaliser/algorithm/toadmimplex/MimplexParams.java @@ -0,0 +1,17 @@ +package group3dlocaliser.algorithm.toadmimplex; + +import Localiser.algorithms.genericLocaliser.MCMC.MCMCParams2; + +public class MimplexParams extends MCMCParams2 { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Always use the first combination + */ + public boolean useFirstCombination = true; + +} diff --git a/src/group3dlocaliser/algorithm/toadmimplex/ToadMimplexLocaliser.java b/src/group3dlocaliser/algorithm/toadmimplex/ToadMimplexLocaliser.java new file mode 100644 index 00000000..797fd542 --- /dev/null +++ b/src/group3dlocaliser/algorithm/toadmimplex/ToadMimplexLocaliser.java @@ -0,0 +1,196 @@ +package group3dlocaliser.algorithm.toadmimplex; + +import java.util.ArrayList; + +import Localiser.LocaliserModel; +import Localiser.LocaliserPane; +import Localiser.algorithms.genericLocaliser.MCMC.MCMCParams2; +import Localiser.detectionGroupLocaliser.GroupLocResult; +import Localiser.detectionGroupLocaliser.GroupLocalisation; +import PamDetection.AbstractLocalisation; +import PamguardMVC.PamDataBlock; +import group3dlocaliser.Group3DDataUnit; +import group3dlocaliser.Group3DLocaliserControl; +import group3dlocaliser.algorithm.hyperbolic.HyperbolicLocaliser; +import group3dlocaliser.algorithm.toadbase.TOADBaseAlgorithm; +import group3dlocaliser.algorithm.toadmcmc.MCMCLocaliserPane; +import group3dlocaliser.algorithm.toadmcmc.ToadMCMCLocaliser; +import group3dlocaliser.algorithm.toadsimplex.ToadSimplexLocaliser; +import group3dlocaliser.grouper.DetectionGroupedSet; + + +/** + * The Mimplex localiser is similar to MCMC but it localises multiple possible combinations using fast algorithms then uses MCMC to localiser the final combination. + * @author Jamie Macaulay + * + */ +public class ToadMimplexLocaliser extends ToadMCMCLocaliser { + + + /** + * The simplex localiser for pre-filtering detections. + */ + ToadSimplexLocaliser simplexLocaliser; + + + /** + * The hyperbolic localiser for pre-filtering detections. + */ + HyperbolicLocaliser hyperbolicLoclaiser; + + ArrayList preLocaliserModels = new ArrayList(); + + /** + * A settings pane for the Mimplex localiser + */ + private MimplexLocaliserPane mimplexSettingsPane; + + + public ToadMimplexLocaliser(Group3DLocaliserControl group3dLocaliser) { + super(group3dLocaliser); + + //note that Mimplex only supports 3D localisations. + simplexLocaliser = new ToadSimplexLocaliser(group3dLocaliser, 3); + + hyperbolicLoclaiser = new HyperbolicLocaliser(group3dLocaliser); + + preLocaliserModels.add(hyperbolicLoclaiser); + preLocaliserModels.add(simplexLocaliser); + + } + + @Override + public String getName() { + return "Mimplex"; + } + + + @Override + public String getToolTipText() { + return "Uses a combination of faster and slower algorithms to localise. Useful if there is match uncertainty between detections"; + } + + @Override + public boolean prepare(PamDataBlock sourceBlock) { + //need to prep our pre-localiser models. + for (TOADBaseAlgorithm model: preLocaliserModels) { + model.prepare(sourceBlock); + + //important to set the toad params here or nothing will work... + model.getToadBaseParams().setChannelBitmap(this.getToadBaseParams().getChannelBitmap()); + model.getToadBaseParams().setMinCorrelatedGroups(this.getToadBaseParams().getMinCorrelatedGroups()); + model.getToadBaseParams().setMinCorrelation(this.getToadBaseParams().getMinCorrelation()); + model.getToadBaseParams().setMinCorrelatedGroups(this.getToadBaseParams().getMinCorrelatedGroups()); + + } + + return super.prepare(sourceBlock); + } + + /** + * Option to pre-filter the localisation results. This can be useful when using algorithms that + * internally handle detection match uncertainty. + * @param - the initial set of detection matches to filter. + */ + public DetectionGroupedSet preFilterLoc(DetectionGroupedSet preGroups) { + + System.out.println("Pre filter groups: " + preGroups.getNumGroups()); + + MimplexParams params = (MimplexParams) group3dLocaliser.getLocaliserAlgorithmParams(this).getAlgorithmParameters(); + + if (params==null) params = new MimplexParams(); + + //no need to do any more processing + if (preGroups.getNumGroups()<=1) { + return preGroups; + } + + //no need to do a y more processing. + if (preGroups.getNumGroups()<=2 && params.useFirstCombination) { + return preGroups; + } + + //localiser using both hyperbolic and the + // will have to make a data unit for each group now... + Group3DDataUnit[] group3dDataUnits = new Group3DDataUnit[preGroups.getNumGroups()]; + + ArrayList preLocalisations = new ArrayList(); + + + for (int i=0; i=0) { + groupedSet.addGroup(preGroups.getGroup(bestLocIndex)); + } + + System.out.println("Number of groups out: " + groupedSet.getNumGroups()); + + return groupedSet; + } + + @Override + public LocaliserPane getAlgorithmSettingsPane() { + if (mimplexSettingsPane==null) { + mimplexSettingsPane = new MimplexLocaliserPane(); + } + return mimplexSettingsPane; + } + + +} diff --git a/src/group3dlocaliser/algorithm/toadsimplex/ToadSimplexLocaliser.java b/src/group3dlocaliser/algorithm/toadsimplex/ToadSimplexLocaliser.java index d362a8ef..1b2e8242 100644 --- a/src/group3dlocaliser/algorithm/toadsimplex/ToadSimplexLocaliser.java +++ b/src/group3dlocaliser/algorithm/toadsimplex/ToadSimplexLocaliser.java @@ -1,6 +1,6 @@ package group3dlocaliser.algorithm.toadsimplex; - +import java.io.Serializable; import java.util.Arrays; import java.util.Random; @@ -13,28 +13,24 @@ import org.apache.commons.math.optimization.OptimizationException; import org.apache.commons.math.optimization.RealPointValuePair; import org.apache.commons.math.optimization.SimpleScalarValueChecker; import org.apache.commons.math.optimization.direct.NelderMead; -import org.apache.commons.math3.distribution.ChiSquaredDistribution; - import Array.ArrayManager; import Array.SnapshotGeometry; import Localiser.LocaliserPane; +import Localiser.algorithms.genericLocaliser.MinimisationFunction; import Localiser.algorithms.locErrors.EllipticalError; -import Localiser.algorithms.locErrors.SimpleError; +import Localiser.algorithms.locErrors.LikilihoodError; import Localiser.detectionGroupLocaliser.GroupLocResult; import Localiser.detectionGroupLocaliser.GroupLocalisation; -import PamDetection.AbstractLocalisation; import PamDetection.LocContents; import PamUtils.CPUMonitor; import PamUtils.LatLong; import PamguardMVC.PamDataUnit; -import PamguardMVC.debug.Debug; import generalDatabase.SQLLoggingAddon; import group3dlocaliser.Group3DLocaliserControl; import group3dlocaliser.algorithm.Chi2Data; +import group3dlocaliser.algorithm.LocaliserAlgorithmParams; import group3dlocaliser.algorithm.LogLikelihoodData; import group3dlocaliser.algorithm.crossedbearing.CrossedBearingSQLAddon; -import group3dlocaliser.algorithm.gridsearch.TOADGridSearch; -import group3dlocaliser.algorithm.hyperbolic.HyperbolicLocaliser; import group3dlocaliser.algorithm.toadbase.TOADBaseAlgorithm; import group3dlocaliser.algorithm.toadbase.TOADInformation; import pamMaths.PamHistogram; @@ -87,7 +83,15 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { public GroupLocalisation processTOADs(PamDataUnit groupDataUnit, SnapshotGeometry geometry, TOADInformation toadInformation) { // get the channel geometry. nCalls++; + + boolean usell = false; + +// ///////////////////////////// +// System.out.println("TOADInformation: " + toadInformation); +// PamArrayUtils.printArray(toadInformation.getToadSeconds()); +// ////////////////////// + PamVector centre = geometry.getGeometricCentre(); double arraySize = geometry.getMaxSeparation(); @@ -101,6 +105,9 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { RealPointValuePair[] results = new RealPointValuePair[nStartPositions]; Chi2Data[] resultChiData = new Chi2Data[nStartPositions]; cpuSimplex.start(); + + MultivariateRealFunction chiFunc = null; + for (int iStart = 0; iStart < nStartPositions; iStart ++) { /* * Start the first iteration in the centre, then randomly jump about guided by the array size. @@ -122,11 +129,10 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { // deal with 2D case start = Arrays.copyOf(start, nDimensions); } - - boolean usell = true; - MultivariateRealFunction chiFunc; + //MultivariateRealFunction chiFunc; GoalType goal; if (usell) { + //This does not seem to work very well! chiFunc = new LogLikelihoodFunction(geometry, toadInformation); goal = GoalType.MAXIMIZE; } @@ -156,9 +162,14 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { // double[] pos = result.getPoint(); // resultVectors[iStart] = new PamVector(pos); // lastGoodDelays = delays; + +// System.out.println("Simplex result " + iStart); +// PamArrayUtils.printArray( result.getPoint()); Chi2Data chiData = calcChi2(geometry, toadInformation, result.getPoint()); resultChiData[iStart] = chiData; + + } cpuSimplex.stop(); @@ -166,8 +177,6 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { if (iBest < 0) { return null; } - - Chi2Data chiData = resultChiData[iBest]; if (chiData.getChi2() / chiData.getDegreesOfFreedom() < 100) { @@ -176,6 +185,12 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { double[] posVec = results[iBest].getPoint(); double r = new PamVector(posVec).norm(nDimensions); + + + // lastGoodDelays = delays; + +// System.out.println("Simplex best " + iBest); +// PamArrayUtils.printArray(posVec); if (chiData.getChi2() / chiData.getDegreesOfFreedom() < 100 && r < 30) { // some diagnostic book keeping for better results. @@ -208,18 +223,39 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { // System.out.printf("Res %d = %3.5f, ", i, posVec[i]); // } // SimpleError cartErr = estimateCartesianError(geometry, toadInformation, posVec); + + //FIXME - this return super weird results EllipticalError ellipErr = estimateEllipticalError(geometry, toadInformation, posVec); +// if (usell) { +// ellipErr = estimateEllipticalError(geometry, toadInformation, posVec); +// } +// else { +// ellipErr = estimateEllipticalError(geometry, toadInformation, posVec); +// +//// SimpleMinimisation simpleMin = new SimpleMinimisation(chiFunc, nDimensions, start, firstStep); +//// ellipErr = new LikilihoodError(simpleMin, posVec); +// } + + // System.out.printf(", Chi2 = %3.1f, p=%3.1f, ndf = %d, Err=%s\n", - // chiData.getChi2(), cumProb, chiData.getNdf(), cartErr.getJsonErrorString()); - + // chiData.getChi2(), cumProb, chiData.getNdf(), cartErr.getJsonErrorString()); + // System.out.println(ellipErr.getJsonErrorString()); /** * Calculated position was relative to array centre, so now need to add the array * centre to the estimated position and then reference this to the ref GPS position. */ LatLong pos = geometry.getReferenceGPS().addDistanceMeters(new PamVector(posVec).add(centre)); + + + + // TargetMotionResult tmResult = new TargetMotionResult(geometry.getTimeMilliseconds(), null, pos, 0, 0); + +// System.out.println("New group localisation: height: " + pos.getHeight() + " ref height: " + +// geometry.getReferenceGPS().getHeight() + " posVec: " + posVec[2]); + GroupLocResult glr = new GroupLocResult(pos, 0, chiData.getChi2()); glr.setError(ellipErr); glr.setPerpendicularDistance(0.); @@ -240,7 +276,7 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { glr.setProbability(null); } GroupLocalisation groupLocalisation = new GroupLocalisation(groupDataUnit, glr); - + return groupLocalisation; } @@ -410,7 +446,7 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { * @see Localiser.LocaliserModel#getSettingsPane() */ @Override - public LocaliserPane getSettingsPane() { + public LocaliserPane getAlgorithmSettingsPane() { // TODO Auto-generated method stub return null; } @@ -447,6 +483,56 @@ public class ToadSimplexLocaliser extends TOADBaseAlgorithm { } return arrayShape >= ArrayManager.ARRAY_TYPE_PLANE; } + + + /** + * Wrapper for converting a MultivariateRealFunction to a MinimisationFunction. + * + * @author Jamie Macaulay + * + */ + class SimpleMinimisation implements MinimisationFunction { + + private MultivariateRealFunction fucntion; + private int nDim; + private double[] start; + private double[] firstStep; + + public SimpleMinimisation(MultivariateRealFunction fucntion, int nDim, double[] start, double[] firstStep) { + this.fucntion=fucntion; + this.nDim= nDim; + this.start= start; + this.firstStep= firstStep; + } + + @Override + public double value(double[] location) { + try { + return fucntion.value(location); + } catch (FunctionEvaluationException | IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return Double.NaN; + + } + } + + @Override + public int getDim() { + return nDim; + } + + @Override + public double[] getStart() { + return start; + } + + @Override + public double[] getFirstStep() { + return firstStep; + } + + } } diff --git a/src/group3dlocaliser/dialog/GroupLocSettingPaneFX.java b/src/group3dlocaliser/dialog/GroupLocSettingPaneFX.java index da7f91c7..af0872a7 100644 --- a/src/group3dlocaliser/dialog/GroupLocSettingPaneFX.java +++ b/src/group3dlocaliser/dialog/GroupLocSettingPaneFX.java @@ -1,47 +1,31 @@ package group3dlocaliser.dialog; +import java.io.Serializable; import java.util.ArrayList; -import javax.swing.JFrame; - import Array.ArrayManager; import PamController.SettingsPane; -import PamDetection.PamDetection; import PamView.GroupedDataSource; -import PamView.symbol.SwingSymbolOptionsPanel; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import group3dlocaliser.Group3DLocaliserControl; import group3dlocaliser.Group3DParams; +import group3dlocaliser.ToadManagedSettingsPane; import group3dlocaliser.algorithm.LocaliserAlgorithm3D; import group3dlocaliser.algorithm.LocaliserAlgorithmParams; -import group3dlocaliser.algorithm.LocaliserAlgorithmProvider; import group3dlocaliser.grouper.DetectionGrouperParams; import group3dlocaliser.grouper.dialog.GrouperSettingsPane; -import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; -import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; -import javafx.scene.control.Label; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; -import javafx.scene.control.Tooltip; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; -import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamTabPane; import pamViewFX.fxNodes.PamTitledBorderPane; import pamViewFX.fxNodes.pamDialogFX.ManagedSettingsPane; @@ -65,7 +49,7 @@ public class GroupLocSettingPaneFX extends SettingsPane{ private ManagedSettingsPane algorithmSourcePane; - private Button algoOptsButton; +// private Button algoOptsButton; private PamBorderPane algoSourceHolder; @@ -75,9 +59,10 @@ public class GroupLocSettingPaneFX extends SettingsPane{ public GroupLocSettingPaneFX(Group3DLocaliserControl group3dLocaliserControl, Object ownerWindow) { super(ownerWindow); + this.ownerWindow = ownerWindow; this.group3dLocaliserControl = group3dLocaliserControl; - + PamTabPane tabPane = new PamTabPane(); tabPane.setAddTabButton(false); tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); @@ -89,10 +74,10 @@ public class GroupLocSettingPaneFX extends SettingsPane{ public void changed(ObservableValue observable, PamDataBlock oldValue, PamDataBlock newValue) { newDataBlockSelection(newValue); - +// grouperSettingsPane.newSourceGroup(((GroupedDataSource) newValue).getGroupSourceParameters()); } }); - + grouperSettingsPane = new GrouperSettingsPane(ownerWindow, "Detection matching options"); PamBorderPane gsp = new PamBorderPane(); gsp.setTop(grouperSettingsPane.getContentNode()); @@ -107,21 +92,21 @@ public class GroupLocSettingPaneFX extends SettingsPane{ borderPane.setCenter(gsp); tabPane.getTabs().add(new Tab("Detection source", borderPane)); - - algorithms = new ChoiceBox<>(); setAlgorithmList(); // call here so that box gets correct size. PamBorderPane algoGrid = new PamBorderPane(); + // HBox.setHgrow(algoGrid, Priority.ALWAYS); algoGrid.setCenter(algorithms); algorithms.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); GridPane.setFillWidth(algorithms, true); // algoGrid.add(new Label("Algorithm Options "), 0, 1); // algoOptsButton = new Button("",PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS,Color.WHITE, PamGuiManagerFX.iconSize)); - algoOptsButton = new Button("",PamGlyphDude.createPamIcon("mdi2c-cog",Color.WHITE, PamGuiManagerFX.iconSize)); - algoGrid.setRight(algoOptsButton); - algoOptsButton.setTooltip(new Tooltip("More Algorithm Options ...")); +// algoOptsButton = new Button("",PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); +// algoGrid.setRight(algoOptsButton); +// PamBorderPane.setMargin(algoOptsButton, new Insets(0,0,0,5)); +// algoOptsButton.setTooltip(new Tooltip("More Algorithm Options ...")); algoSourceHolder = new PamBorderPane(); // HBox.setHgrow(algoSourceHolder, Priority.ALWAYS); // algoGrid.add(algoSourceHolder, 0, 2, 4, 1); @@ -129,14 +114,15 @@ public class GroupLocSettingPaneFX extends SettingsPane{ PamTitledBorderPane ptb = new PamTitledBorderPane("Select Localisation Algorithm", algoGrid); algoMainPane.setTop(ptb); algoMainPane.setCenter(algoSourceHolder); - tabPane.getTabs().add(new Tab("Algorithm", algoMainPane)); + tabPane.getTabs().add(new Tab("Localisation", algoMainPane)); - algoOptsButton.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - moreAlgorithmOptions(); - } - }); + +// algoOptsButton.setOnAction(new EventHandler() { +// @Override +// public void handle(ActionEvent event) { +// moreAlgorithmOptions(); +// } +// }); algorithms.setOnAction(new EventHandler() { @Override @@ -145,14 +131,16 @@ public class GroupLocSettingPaneFX extends SettingsPane{ } }); - mainPane.setCenter(tabPane); + } /** * Called whenever the algorithm selection is changed. */ protected void selectAlgorithm() { + + /** * Algorithm options fall into two separate parts. The first, which primarily involves * timing measurements is included in the bottom half of the main dialog tab, so @@ -165,16 +153,18 @@ public class GroupLocSettingPaneFX extends SettingsPane{ */ String algoName = algorithms.getSelectionModel().getSelectedItem(); if (algoName == null) { - algoOptsButton.setDisable(true); + //algoOptsButton.setDisable(true); return; } LocaliserAlgorithm3D localiserAlgorithm = group3dLocaliserControl.findAlgorithm(algoName); + //System.out.println(" selectAlgorithm: object " + localiserAlgorithm); + if (localiserAlgorithm == null) { - algoOptsButton.setDisable(true); + //algoOptsButton.setDisable(true); return; } // also enable / disable the more options button ... - algoOptsButton.setDisable(localiserAlgorithm.hasParams() == false); + //algoOptsButton.setDisable(localiserAlgorithm.hasParams() == false); /** * Need to immediately tell the algorithm which input we're using so that it can @@ -192,13 +182,14 @@ public class GroupLocSettingPaneFX extends SettingsPane{ * what options to display, i.t TOAD options for clicks, whistles and generic * Time frequency things are NOT the same. */ - ManagedSettingsPane newPane = localiserAlgorithm.getSourceSettingsPane(getAWTWindow(), currSource); - if (newPane == algorithmSourcePane) { - return; - } - if (algorithmSourcePane != null) { - algoSourceHolder.setCenter(null); - } + ToadManagedSettingsPane newPane = localiserAlgorithm.getSourceSettingsPane(getAWTWindow(), currSource); + + //System.out.println(" selectAlgorithm: newPane " + newPane); + + + algoSourceHolder.setCenter(null); + + if (newPane != null) { // PamTitledBorderPane p = new PamTitledBorderPane("Algorithm options", newPane.getSettingsPane().getContentNode()); PamBorderPane borderPane = new PamBorderPane(newPane.getSettingsPane().getContentNode()); @@ -206,10 +197,67 @@ public class GroupLocSettingPaneFX extends SettingsPane{ borderPane.setRightSpace(5); borderPane.setBottomSpace(5); algoSourceHolder.setCenter(borderPane); - LocaliserAlgorithmParams locParams = group3dLocaliserControl.getLocaliserAlgorithmParams(localiserAlgorithm); - newPane.setParams(); +// LocaliserAlgorithmParams locParams = group3dLocaliserControl.getLocaliserAlgorithmParams(localiserAlgorithm); +// newPane.setParams(); + // newPane.setDetectionSource(sourcePanel.getSource()); } + + //System.out.println(" selectAlgorithm: newPane " + localiserAlgorithm.getAlgorithmSettingsPane()); + + /** + * Now set up the advanced settings for the localiser algorothm. If FX add as a tab. If Swing then add a settings button to a new tab. + */ + if (localiserAlgorithm.getAlgorithmSettingsPane()!=null) { + + Node settingsPane = null; + + if (localiserAlgorithm.getAlgorithmSettingsPane()!=null) { + //preferentially select FX pane of one exists. + settingsPane = localiserAlgorithm.getAlgorithmSettingsPane().getContentNode(); + } + //there must be a dialog - otherwise what is the point of a settings pane that does nothing - it should be null. +// else { +// PamHBox hBox = new PamHBox(); +// hBox.setSpacing(5); +// hBox.setAlignment(Pos.CENTER); +// hBox.setPadding(new Insets(5,5,5,5)); +// +// PamButton algoOptsButton = new PamButton("",PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); +// algoOptsButton.setOnAction((action)->{ +// moreAlgorithmOptions() ; +// }); +// algoOptsButton.setTooltip(new Tooltip("More Algorithm Options ...")); +// +// hBox.getChildren().addAll(new Label(localiserAlgorithm.getName() + " settings"), algoOptsButton); +// +// settingsPane = hBox; +// } +// + + if (newPane==null) { + //System.out.println("Set settings pane: " + settingsPane); + algoSourceHolder.setCenter(settingsPane); + } + else { + //clear the tab pane + for (int i=0; i{ BorderPaneFX2AWT.repackSwingDialog(getContentNode()); } - /** - * Handle algorithm options ... - */ - protected void moreAlgorithmOptions() { - String algoName = algorithms.getSelectionModel().getSelectedItem(); - if (algoName == null) { - return; - } - LocaliserAlgorithm3D localiserAlgorithm = group3dLocaliserControl.findAlgorithm(algoName); - if (localiserAlgorithm == null) { - return; - } - if (localiserAlgorithm.hasParams() == false) { - return; - } - LocaliserAlgorithmParams algorithmPaams = group3dLocaliserControl.getLocaliserAlgorithmParams(localiserAlgorithm); - algorithmPaams = localiserAlgorithm.showAlgorithmDialog(getAWTWindow(), algorithmPaams); - if (algorithmPaams != null) { - group3dLocaliserControl.setAlgorithmParams(localiserAlgorithm, algorithmPaams); - } - } + + @Override public Group3DParams getParams(Group3DParams currParams) { + if (currParams==null) currParams = new Group3DParams(); + PamDataBlock currSource = sourcePanel.getSource(); if (currSource == null) { SwingFXDialogWarning.showWarning(this.getOwnerWindow(), "Invalid DataBlock", "You must select a data source"); @@ -279,34 +310,87 @@ public class GroupLocSettingPaneFX extends SettingsPane{ SwingFXDialogWarning.showWarning(this.getOwnerWindow(), "Localisation algorithm", "You must select a localisation algorithm"); return null; } - currParams.setAlgorithmName(algoName); - LocaliserAlgorithm3D algoProvider = group3dLocaliserControl.findAlgorithm(algoName); - if (algorithmSourcePane != null && currParams != null && algoProvider != null) { - LocaliserAlgorithmParams locParams = currParams.getAlgorithmParams(algoProvider); - if (algorithmSourcePane != null) { - Object ans = algorithmSourcePane.getParams(); - if (ans == null) { - return null; - } -// else { -// locParams.setAlgorithmParams(algoProvider, ans); -// } -// -// if (ans instanceof LocaliserAlgorithmParams) { -// currParams.setAlgorithmParams(algoProvider, (LocaliserAlgorithmParams) ans); -// } + //set the currently select algorithm + currParams.setAlgorithmName(algoName); + + //now iterate through all the algorithms and save both the algorithm source settings and the save settings. If the user has changed multiple + //a settings don't want just the current algorithm settings being saved. + for (int i = 0; i< algorithms.getItems().size(); i++ ) { + + LocaliserAlgorithm3D algoProvider = group3dLocaliserControl.findAlgorithm(algorithms.getItems().get(i)); + + LocaliserAlgorithmParams locAlgorithmParams = currParams.getAlgorithmParams(algoProvider); + + + if (algoProvider.getAlgorithmSettingsPane()!=null) { + + //get the algorithm paramters. + Serializable newAlgorithmParams = algoProvider.getAlgorithmSettingsPane().getParams( + currParams.getAlgorithmParams(algoProvider) == null? null : currParams.getAlgorithmParams(algoProvider).getAlgorithmParameters()); + + if (locAlgorithmParams==null) locAlgorithmParams = new LocaliserAlgorithmParams(); + locAlgorithmParams.setAlgorithmParameters(newAlgorithmParams); + + //FIXME - note that, if a swing dialog has been used the algorithms have already been set....not great - want to + //try and get swing out of this if possible. } -//// LocaliserAlgorithmParams sourceParams = algorithmSourcePane.getParams(locParams); -// if (sourceParams == null) { -//// SwingFXDialogWarning.showWarning(this.getOwnerWindow(), "Localisation algorithm", "Invalid al"); -// return null; -// } -// currParams.setAlgorithmParams(algoProvider, sourceParams); + + if (algoProvider.getSourceSettingsPane(null, currSource)!=null) { + + //set only the selected algorithm to show the user warning sif settings are wrong + if (algorithms.getSelectionModel().getSelectedIndex()==i) { + //this is the currently selected algorithm + algoProvider.getSourceSettingsPane(null, currSource).setErrorWarn(true); + } + else { + algoProvider.getSourceSettingsPane(null, currSource).setErrorWarn(false); + } + + //get the source pane paramters. + Serializable newSourceParams = algoProvider.getSourceSettingsPane(null, currSource).getParams(); + + if (locAlgorithmParams==null) locAlgorithmParams = new LocaliserAlgorithmParams(); + locAlgorithmParams.setXtraSourceParameters(newSourceParams); + } + + + //now set the source paramters. + currParams.setAlgorithmParams(algoProvider, locAlgorithmParams); + + + //System.out.println("Get params" + algoProvider.getName() + " " + currParams.getAlgorithmParams(algoProvider)); + } +// if (algorithmSourcePane != null && currParams != null && algoProvider != null) { +// LocaliserAlgorithmParams locParams = currParams.getAlgorithmParams(algoProvider); +// if (algorithmSourcePane != null) { +// Object ans = algorithmSourcePane.getParams(); +// if (ans == null) { +// return null; +// } +//// else { +//// locParams.setAlgorithmParams(algoProvider, ans); +//// } +//// +//// if (ans instanceof LocaliserAlgorithmParams) { +//// currParams.setAlgorithmParams(algoProvider, (LocaliserAlgorithmParams) ans); +//// } +// } +////// LocaliserAlgorithmParams sourceParams = algorithmSourcePane.getParams(locParams); +//// if (sourceParams == null) { +////// SwingFXDialogWarning.showWarning(this.getOwnerWindow(), "Localisation algorithm", "Invalid al"); +//// return null; +//// } +//// currParams.setAlgorithmParams(algoProvider, sourceParams); +// } + + return currParams; } + + private LocaliserAlgorithm3D getSelectedAlgorithm() { String algoName = algorithms.getSelectionModel().getSelectedItem(); if (algoName == null) { @@ -317,6 +401,7 @@ public class GroupLocSettingPaneFX extends SettingsPane{ @Override public void setParams(Group3DParams input) { + sourcePanel.setSourceList(); sourcePanel.setSource(input.getSourceName()); // sourcePanel.setChannelList(input.getGroupedSourceParams().getChanOrSeqBitmap()); @@ -328,6 +413,7 @@ public class GroupLocSettingPaneFX extends SettingsPane{ // algorithms.getSelectionModel().select(input.getAlgorithmName()); currentParams = input; newDataBlockSelection(sourcePanel.getSource()); + } /** @@ -340,22 +426,32 @@ public class GroupLocSettingPaneFX extends SettingsPane{ PamDataBlock inputDataBlock = sourcePanel.getSource(); algorithms.getItems().clear(); ArrayList algoList = group3dLocaliserControl.getAlgorithmProviders(); + + int shape = 0; + + //Note that, if there is a lot of data in the hydrophone data block this next chunk can take a second or so to prcoess. + //Must keep it outside the for loop or there is a long freeze. + if (inputDataBlock != null) { + int phones = inputDataBlock.getHydrophoneMap(); + shape = ArrayManager.getArrayManager().getArrayType(phones); + } + for (int i = 0; i < algoList.size(); i++) { LocaliserAlgorithm3D algo = algoList.get(i); if (inputDataBlock != null && algo.canLocalise(inputDataBlock) == false) { continue; } + if (inputDataBlock != null) { - int phones = inputDataBlock.getHydrophoneMap(); - int shape = ArrayManager.getArrayManager().getArrayType(phones); - ArrayManager.getArrayManager().getArrayType(phones); if (algo.canArrayShape(shape) == false) { continue; } } + if (algo == currAlgo) { currentIndex = algorithms.getItems().size(); } + algorithms.getItems().add(algo.getName()); } if (currentIndex >= 0) { @@ -373,10 +469,41 @@ public class GroupLocSettingPaneFX extends SettingsPane{ if (algorithmSourcePane == null) { return; } + + PamDataBlock currSource = sourcePanel.getSource(); + + //note that we get the algorithms from the choice box here because these will already have + //been filtered for suitability depending on array geometry. + for (int i=0; i{ } setAlgorithmList(); setAlgorithmSourceParameters(); + grouperSettingsPane.newSourceGroup(((GroupedDataSource) pamDataBlock).getGroupSourceParameters()); } @Override diff --git a/src/group3dlocaliser/grouper/DetectionGrouper.java b/src/group3dlocaliser/grouper/DetectionGrouper.java index c5ce50aa..0774f3e2 100644 --- a/src/group3dlocaliser/grouper/DetectionGrouper.java +++ b/src/group3dlocaliser/grouper/DetectionGrouper.java @@ -242,7 +242,7 @@ public class DetectionGrouper { /** * Close the existing group and make a new one. */ - private synchronized void closeMotherGroup() { + public synchronized void closeMotherGroup() { processFirstGroup(motherGroup); motherGroup = new FirstGrouping(maxInterGroupSamples.length, 0, null); @@ -422,7 +422,7 @@ public class DetectionGrouper { } } if (nAccepted > 0) { -// System.out.printf("Accepted %d of %d data combinations\n", detectionGroupedSet.getNumGroups(), totalCombinations); + System.out.printf("Accepted %d of %d data combinations\n", detectionGroupedSet.getNumGroups(), totalCombinations); detectionGroupMonitor.newGroupedDataSet(detectionGroupedSet); } diff --git a/src/group3dlocaliser/grouper/DetectionGrouperParams.java b/src/group3dlocaliser/grouper/DetectionGrouperParams.java index d789b054..b3e40163 100644 --- a/src/group3dlocaliser/grouper/DetectionGrouperParams.java +++ b/src/group3dlocaliser/grouper/DetectionGrouperParams.java @@ -10,7 +10,14 @@ public class DetectionGrouperParams implements Serializable, Cloneable, ManagedP public static final long serialVersionUID = 1L; + /** + * Use all the possible combinations + */ public static final int GROUPS_ALL_POSSIBLE = 0; + + /** + * Use only the first combination + */ public static final int GROUPS_FIRST_ONLY = 1; public int maxPerSubGroup = 10; @@ -19,6 +26,13 @@ public class DetectionGrouperParams implements Serializable, Cloneable, ManagedP public int minSubGroups = 2; + /** + * The primary detection groups. The slected detection groups must be included in the combination for a loclaisation + * + * null means there is no primary group. + */ + public int[] primaryDetGroup = null; + public int groupingChoice = GROUPS_ALL_POSSIBLE; // data selection options diff --git a/src/group3dlocaliser/grouper/dialog/GrouperSettingsPane.java b/src/group3dlocaliser/grouper/dialog/GrouperSettingsPane.java index edeeb1ac..33bd36ce 100644 --- a/src/group3dlocaliser/grouper/dialog/GrouperSettingsPane.java +++ b/src/group3dlocaliser/grouper/dialog/GrouperSettingsPane.java @@ -1,6 +1,9 @@ package group3dlocaliser.grouper.dialog; + import PamController.SettingsPane; +import PamUtils.PamArrayUtils; +import PamView.GroupedSourceParameters; import PamguardMVC.PamDataBlock; import PamguardMVC.dataSelector.DataSelector; import group3dlocaliser.grouper.DetectionGrouperParams; @@ -14,8 +17,8 @@ import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; @@ -23,10 +26,13 @@ import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamLabel; import pamViewFX.fxNodes.PamTitledBorderPane; import pamViewFX.fxNodes.pamDialogFX.SwingFXDialogWarning; +import pamViewFX.fxNodes.utilityPanes.GroupedChannelBox; public class GrouperSettingsPane extends SettingsPane{ + private static final double LABELED_WIDTH = 40; + private PamBorderPane mainPane = new PamBorderPane(); private ChoiceBox groupOptions; @@ -40,36 +46,66 @@ public class GrouperSettingsPane extends SettingsPane{ private DataSelector currentDataSelector; private Button dataSelButton; + + private GroupedChannelBox checkGroupBox; + + private DetectionGrouperParams currentParams; public GrouperSettingsPane(Object ownerWindow, String borderTitle) { super(ownerWindow); // PamBorderPane setPane = new PamBorderPane(); groupOptions = new ChoiceBox(); + groupOptions.setMaxWidth(Double.MAX_VALUE); groupOptions.getItems().add("Return all possible combinations"); groupOptions.getItems().add("Return only the first combination"); // setPane.setTop(groupOptions); int x = 0, y = 0; PamGridPane gridPane = new PamGridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + gridPane.add(new Label("Min Detections "), x, y); gridPane.add(minDets = new TextField("123"), ++x, y); + minDets.setPrefColumnCount(3); + gridPane.add(new Label("Max Detections "), x=0, ++y); gridPane.add(maxDets = new TextField("123"), ++x, y); - minDets.setPrefColumnCount(3); maxDets.setPrefColumnCount(3); + + minDets.setMaxWidth(LABELED_WIDTH); + maxDets.setMaxWidth(LABELED_WIDTH); + + gridPane.add(new Label("Requires channels "), x=0, ++y); + checkGroupBox = new GroupedChannelBox(); + checkGroupBox.setTooltip(new Tooltip("Select channels that are required to loclaise. \n" + + "No selected channels means any combination of channels can be used.")); + + PamGridPane.setColumnSpan(checkGroupBox, 2); + PamGridPane.setColumnSpan(minDets, 1); + PamGridPane.setColumnSpan(maxDets, 1); + + gridPane.add(checkGroupBox, ++x, y); + // dataSelButton = new Button("",PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS,Color.WHITE, PamGuiManagerFX.iconSize)); - dataSelButton = new Button("",PamGlyphDude.createPamIcon("mdi2c-cog",Color.WHITE, PamGuiManagerFX.iconSize)); + dataSelButton = new Button("",PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); + PamGridPane selPane = gridPane; //new PamGridPane(); - selPane.add(new PamLabel("Data selection ", Pos.CENTER_RIGHT), x=0, ++y); + selPane.add(new PamLabel("Data selection ", Pos.CENTER_LEFT), x=0, ++y); selPane.add(dataSelButton, ++x, +y); + dataSelButton.setMaxWidth(LABELED_WIDTH); selPane.add(requireAll = new RadioButton("Require all detections pass data selection"), x=0, ++y, 4, 1); selPane.add(requireSome = new RadioButton("Require minimum of "), x=0, ++y, 2, 1); x+=2; selPane.add(requiredN = new TextField(), x, y); selPane.add(new Label(" pass data selection"), ++x, y); requiredN.setPrefColumnCount(2); + + requireAll.setTooltip(new Tooltip("Select if all detections within a combination must pass the data selector")); + requiredN.setTooltip(new Tooltip("Select if at least N detections within a combination must pass the data selector")); + ToggleGroup group = new ToggleGroup(); requireAll.setToggleGroup(group); requireSome.setToggleGroup(group); @@ -93,7 +129,7 @@ public class GrouperSettingsPane extends SettingsPane{ }); VBox vBox = new VBox(groupOptions, gridPane); - + vBox.setSpacing(5); // setPane.setBottom(gridPane); if (borderTitle == null) { @@ -103,6 +139,8 @@ public class GrouperSettingsPane extends SettingsPane{ mainPane.setCenter(new PamTitledBorderPane(borderTitle, vBox)); } + enableControls(); + } protected void requireOptions() { @@ -126,7 +164,12 @@ public class GrouperSettingsPane extends SettingsPane{ boolean d = dataSelector == null; requireAll.setDisable(d); requireSome.setDisable(d); - requiredN.setDisable(d); + + + //need to disable if no data selector but do not enable unless requireSome selected + if (!d) enableControls(); + else requiredN.setDisable(d); + dataSelButton.setDisable(d); } @@ -165,12 +208,16 @@ public class GrouperSettingsPane extends SettingsPane{ } } + currParams.primaryDetGroup = getGroupParams(); + return currParams; } @Override public void setParams(DetectionGrouperParams params) { + groupOptions.getSelectionModel().select(params.groupingChoice); + minDets.setText(String.format("%d", params.minSubGroups)); maxDets.setText(String.format("%d", params.maxPerGroup)); @@ -178,8 +225,76 @@ public class GrouperSettingsPane extends SettingsPane{ requireSome.setSelected(params.dataSelectOption == DetectionGrouperParams.DATA_SELECT_MIN_N); requiredN.setText(String.format("%d", params.dataSelectMinimum)); + setGroupParams(params.primaryDetGroup); + enableControls(); } + + /** + * Set which primary groups are ticked. + * @param groups - the groups. + */ + private int[] getGroupParams(){ + + //the check box should have already had the correct source params set. + + int n = 0; + for (int i=0; i{ // TODO Auto-generated method stub } + + private void populateChannelBox(GroupedSourceParameters source) { + int[] selected = null; + + //Need to preserve the selection - check whether the groups are equal and if so set the selected back again + if (checkGroupBox.getGroupedParams()!=null && (PamArrayUtils.arrEquals(source.getChannelGroups(), checkGroupBox.getGroupedParams().getChannelGroups()))) { + selected = getGroupParams(); + } + + checkGroupBox.setSource(source); + + setGroupParams(selected); + + } + + /** + * Called whenever there is a new source group. + * @param source + */ + public void newSourceGroup(GroupedSourceParameters source) { + populateChannelBox(source); + } } diff --git a/src/group3dlocaliser/offline/Group3DOfflineTask.java b/src/group3dlocaliser/offline/Group3DOfflineTask.java index 000f022f..ab525a7b 100644 --- a/src/group3dlocaliser/offline/Group3DOfflineTask.java +++ b/src/group3dlocaliser/offline/Group3DOfflineTask.java @@ -2,6 +2,7 @@ package group3dlocaliser.offline; import java.util.ArrayList; +import Array.ArrayManager; import PamController.PamController; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; @@ -30,6 +31,12 @@ public class Group3DOfflineTask extends OfflineTask{ if (parentData != null) { this.addRequiredDataBlock(parentData); } + + //here be large bugs in all data and select data processing if not included + this.addRequiredDataBlock(ArrayManager.getArrayManager().getHydrophoneDataBlock()); + this.addRequiredDataBlock(ArrayManager.getArrayManager().getStreamerDatabBlock()); + this.addRequiredDataBlock(ArrayManager.getGPSDataBlock()); + } @Override diff --git a/src/localiserDisplay/LocaliserDisplayControlAWT.java b/src/localiserDisplay/LocaliserDisplayControlAWT.java index c60b7241..cd8c63b8 100644 --- a/src/localiserDisplay/LocaliserDisplayControlAWT.java +++ b/src/localiserDisplay/LocaliserDisplayControlAWT.java @@ -106,7 +106,7 @@ public class LocaliserDisplayControlAWT extends LoclaiserDisplayControl implemen Group root= new Group(); Scene scene = new Scene(root, Color.GRAY); scene.getStylesheets().clear(); - scene.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); + scene.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); localiser = new LocaliserDisplayFX(this); root.getChildren().add(localiser); 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/matchedTemplateClassifer/ImportTemplateMAT.java b/src/matchedTemplateClassifer/ImportTemplateMAT.java index 6b905042..a1203b32 100644 --- a/src/matchedTemplateClassifer/ImportTemplateMAT.java +++ b/src/matchedTemplateClassifer/ImportTemplateMAT.java @@ -4,9 +4,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import com.jmatio.io.MatFileReader; -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLStructure; +import PamUtils.PamArrayUtils; +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; /** @@ -22,7 +24,7 @@ public class ImportTemplateMAT implements TemplateImport { /** * The matlab file rader. */ - private MatFileReader mfr; + private Mat5File mfr; @Override public MatchTemplate importTemplate(File filePath){ @@ -31,7 +33,7 @@ public class ImportTemplateMAT implements TemplateImport { //System.out.println("Import MAT file waveform"); //the MATLAB file reader. - mfr = new MatFileReader(filePath); + mfr = Mat5.readFromFile(filePath); MatchTemplate matchTemplate; @@ -72,34 +74,33 @@ public class ImportTemplateMAT implements TemplateImport { * @param mfr - .mat file reader. * @return the match template. */ - private MatchTemplate getTemplateStruct(MatFileReader mfr2) { + private MatchTemplate getTemplateStruct(Mat5File mfr2) { //the MATLAB file reader. - MLStructure clicksStruct = (MLStructure) mfr.getMLArray("clicks"); - MLDouble sampleRateML = (MLDouble) mfr.getMLArray("clicks_sR"); + Struct clicksStruct = mfr.getStruct("clicks"); + Double sampleRateML = getDouble(mfr,"clicks_sR"); - - - if (clicksStruct==null) { - clicksStruct = (MLStructure) mfr.getMLArray("raw_data_units"); - sampleRateML = (MLDouble) mfr.getMLArray("raw_data_units_sR"); - } - - - +// if (clicksStruct==null) { +// clicksStruct = (MLStructure) mfr.getMLArray("raw_data_units"); +// sampleRateML = (MLDouble) mfr.getMLArray("raw_data_units_sR"); +// } if (sampleRateML==null || clicksStruct==null) { return null; } - MLDouble waveML = (MLDouble) clicksStruct.getField("wave", 0); - double[][] waveform = waveML.getArray(); - - - MatchTemplate matchedTemplate = new MatchTemplate(null, waveform[0], (float) sampleRateML.get(0).doubleValue()); + Matrix waveML = clicksStruct.get("wave", 0); + double[][] waveform = PamArrayUtils.matrix2array(waveML); + MatchTemplate matchedTemplate = new MatchTemplate(null, waveform[0], (float) sampleRateML.doubleValue()); return matchedTemplate; - + } + + + private Double getDouble(Mat5File mfr, String field) { + Matrix data = mfr.getMatrix(field); + if (data==null) return null; + return data.getDouble(0); } @@ -108,27 +109,25 @@ public class ImportTemplateMAT implements TemplateImport { * @param mfr - .mat file reader. * @return the match template. */ - private MatchTemplate getTemplateStandard(MatFileReader mfr) { + private MatchTemplate getTemplateStandard(Mat5File mfr) { //System.out.println("Import MAT file waveform"); - - //the MATLAB file reader. - MLDouble sampleRateML = (MLDouble) mfr.getMLArray("sR"); - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sr"); //try a different name for the sample rate - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("fs"); //try a different name for the sample rate - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("samplerate"); //try a different name for the sample rate - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sample_rate"); //try a different name for the sample rate - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("sampleRate"); //try a different name for the sample rate - if (sampleRateML==null) sampleRateML = (MLDouble) mfr.getMLArray("clicks_sR"); //try a different name for the sample rate + Double sampleRateML = getDouble(mfr,"sR"); + if (sampleRateML==null) sampleRateML = getDouble(mfr,"sr"); //try a different name for the sample rate + if (sampleRateML==null) sampleRateML = getDouble(mfr,"fs"); //try a different name for the sample rate + if (sampleRateML==null) sampleRateML = getDouble(mfr,"samplerate");//try a different name for the sample rate + if (sampleRateML==null) sampleRateML = getDouble(mfr,"sample_rate"); //try a different name for the sample rate + if (sampleRateML==null) sampleRateML = getDouble(mfr,"sampleRate"); //try a different name for the sample rate + if (sampleRateML==null) sampleRateML = getDouble(mfr,"clicks_sR"); //try a different name for the sample rate //get the waveform or spectrum - MLDouble waveformML = (MLDouble) mfr.getMLArray("waveform"); + Matrix waveformML = mfr.getMatrix("waveform"); - if (waveformML==null) waveformML = (MLDouble) mfr.getMLArray("wave"); //try a different name for the waveform - if (waveformML==null) waveformML = (MLDouble) mfr.getMLArray("spectrum"); //might be a spectrum + if (waveformML==null) waveformML = mfr.getMatrix("wave"); //try a different name for the waveform + if (waveformML==null) waveformML = mfr.getMatrix("spectrum"); //might be a spectrum if (sampleRateML==null || waveformML==null) { @@ -136,7 +135,7 @@ public class ImportTemplateMAT implements TemplateImport { } //import a wave in column or row dimension - int size = Math.max(waveformML.getM(), waveformML.getN()); + int size = Math.max(waveformML.getNumRows(), waveformML.getNumCols()); double[] waveform = new double[size]; if (sizewaveformML.getN() ? waveformML.get(i, 0) : waveformML.get(0, i); + waveform[i]= waveformML.getNumRows()>waveformML.getNumCols() ? waveformML.getDouble(i, 0): waveformML.getDouble(0, i); } - float sR= Float.valueOf((float) sampleRateML.getArray()[0][0]); + float sR= (float) sampleRateML.doubleValue(); //now create waveform diff --git a/src/matchedTemplateClassifer/MTClassifier.java b/src/matchedTemplateClassifer/MTClassifier.java index 9065daac..d111c5c4 100644 --- a/src/matchedTemplateClassifer/MTClassifier.java +++ b/src/matchedTemplateClassifer/MTClassifier.java @@ -4,22 +4,21 @@ import java.io.Serializable; import java.lang.reflect.Field; import org.apache.commons.lang3.ArrayUtils; +import org.jamdev.jdl4pam.utils.DLMatFile; import org.jamdev.jpamutils.wavFiles.WavInterpolator; -import com.jmatio.types.MLArray; -import com.jmatio.types.MLDouble; -import com.jmatio.types.MLStructure; - import Filters.SmoothingFilter; import Localiser.DelayMeasurementParams; import PamModel.parametermanager.ManagedParameters; import PamModel.parametermanager.PamParameterSet; import PamModel.parametermanager.PrivatePamParameterData; -import PamModel.parametermanager.PamParameterSet.ParameterSetType; import PamUtils.PamArrayUtils; import PamUtils.PamInterp; import PamUtils.complex.ComplexArray; import fftManager.FastFFT; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.types.Matrix; +import us.hebi.matlab.mat.types.Struct; /** * Parameters and useful functions for a single MT classifier. @@ -135,7 +134,6 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters //re-sample the waveform if the sample rate is different this.interpWaveformMatch=interpWaveform(this.waveformMatch, sR); - //System.out.println("interpWaveformMatch: " + interpWaveformMatch.length + " sR " + sR); //normalise //this.interpWaveformMatch=PamArrayUtils.normalise(interpWaveformMatch); @@ -143,8 +141,7 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters //this.inteprWaveformReject=PamArrayUtils.divide(interpWaveformMatch, PamArrayUtils.max(interpWaveformMatch)); this.interpWaveformMatch = normaliseWaveform(interpWaveformMatch, this.normalisation); -// System.out.println("MatchNorm: MATCH"); -// MTClassifierTest.normalizeTest(interpWaveformMatch); + //System.out.println("interpWaveformMatch: " + interpWaveformMatch.length + " sR " + sR + " max: " + PamArrayUtils.max(interpWaveformMatch)); /** * There is an issue here because, if we have a long template waveform, then it @@ -249,13 +246,15 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters * @return */ public static double[] normaliseWaveform(double[] waveform, int normeType) { +// System.out.println("Normalise waveform: " + normeType); double[] newWaveform = null; switch(normeType) { case MatchedTemplateParams.NORMALIZATION_NONE: newWaveform = waveform; break; case MatchedTemplateParams.NORMALIZATION_PEAK: - newWaveform =PamUtils.PamArrayUtils.divide(waveform, PamUtils.PamArrayUtils.max(waveform)); + //important to clone here or the template waveforms are normalised! + newWaveform =PamUtils.PamArrayUtils.divide(waveform.clone(), PamUtils.PamArrayUtils.max(waveform)); break; case MatchedTemplateParams.NORMALIZATION_RMS: newWaveform =PamUtils.PamArrayUtils.normalise(waveform); @@ -305,8 +304,8 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters * @param value - the value * @return */ - private MLDouble mlDouble(double value) { - return new MLDouble(null, new double[]{value}, 1); + private Matrix mlDouble(double value) { + return Mat5.newScalar(value); } @@ -322,10 +321,11 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters * @return an ML Structure with results of all stuff. */ - public MLStructure calcCorrelationMatchTest(ComplexArray click, float sR) { + public Struct calcCorrelationMatchTest(ComplexArray click, float sR) { - MLStructure mlStruct = new MLStructure("matchdata", new int[] {1,1}); - mlStruct.setField("waveform_fft", complexArray2MLArray(click)); +// Struct mlStruct = new MLStructure("matchdata", new int[] {1,1}); + Struct mlStruct = Mat5.newStruct(); + mlStruct.set("waveform_fft", complexArray2MLArray(click)); //set the stored sR @@ -354,14 +354,14 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters } //add data to struct her ebecause some arrays get overwritten - mlStruct.setField("match_template_waveform", new MLDouble(null, new double[][] {this.interpWaveformMatch})); - mlStruct.setField("reject_template_waveform", new MLDouble(null, new double[][] {this.inteprWaveformReject})); + mlStruct.set("match_template_waveform", DLMatFile.array2Matrix(this.interpWaveformMatch)); + mlStruct.set("reject_template_waveform", DLMatFile.array2Matrix(this.inteprWaveformReject)); //add data to struct her ebecause some arrays get overwritten - mlStruct.setField("match_template_fft", complexArray2MLArray(matchTemplate)); - mlStruct.setField("reject_template_fft", complexArray2MLArray(rejectTemplate)); - mlStruct.setField("match_result_corr", complexArray2MLArray(matchResult)); - mlStruct.setField("reject_result_corr", complexArray2MLArray(rejectResult)); + mlStruct.set("match_template_fft", complexArray2MLArray(matchTemplate)); + mlStruct.set("reject_template_fft", complexArray2MLArray(rejectTemplate)); + mlStruct.set("match_result_corr", complexArray2MLArray(matchResult)); + mlStruct.set("reject_result_corr", complexArray2MLArray(rejectResult)); //must use scaling to get the same result as MATLAB if (fft==null) fft= new FastFFT(); @@ -380,10 +380,10 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters double result = 2*(PamArrayUtils.max(matchReal)-PamArrayUtils.max(rejectReal)); - mlStruct.setField("match_result_ifft", complexArray2MLArray(matchResult)); - mlStruct.setField("reject_result_ifft", complexArray2MLArray(rejectResult)); - mlStruct.setField("result", mlDouble(result)); - mlStruct.setField("sR", mlDouble(sR)); + mlStruct.set("match_result_ifft", complexArray2MLArray(matchResult)); + mlStruct.set("reject_result_ifft", complexArray2MLArray(rejectResult)); + mlStruct.set("result", mlDouble(result)); + mlStruct.set("sR", mlDouble(sR)); return mlStruct; @@ -395,13 +395,14 @@ public class MTClassifier implements Serializable, Cloneable, ManagedParameters * @param complexArray the complex array to export. * @return the ML array. */ - private MLDouble complexArray2MLArray(ComplexArray complexArray) { - MLDouble matchTemplateML = new MLDouble(null, new int[]{complexArray.length(),1}, MLArray.mxDOUBLE_CLASS, MLArray.mtFLAG_COMPLEX ); + private Matrix complexArray2MLArray(ComplexArray complexArray) { + + Matrix matchTemplateML = Mat5.newComplex(complexArray.length(),1); +// MLDouble matchTemplateML = new MLDouble(null, new int[]{complexArray.length(),1}, MLArray.mxDOUBLE_CLASS, MLArray.mtFLAG_COMPLEX ); for (int i=0; i templates) { + private static Struct testClassifier(double[] testWaveform, float sR, ArrayList templates) { //create the classifier object MTClassifier mtclassifier = new MTClassifier(); @@ -63,11 +66,11 @@ public class MTClassifierTest { System.out.println("Click waveform length: " + testWaveform.length + " FFT bins: " + complexArray.length() + " FFT Length: " + fftSize); - MLStructure struct = mtclassifier.calcCorrelationMatchTest(complexArray, sR); + Struct struct = mtclassifier.calcCorrelationMatchTest(complexArray, sR); - struct.setField("waveform", new MLDouble(null, new double[][] {testWaveform})); - - System.out.println("Match: " + ((MLDouble) struct.getField("result")).get(0)); + struct.set("waveform", DLMatFile.array2Matrix(testWaveform)); + + System.out.println("Match: " + struct.getMatrix("result").getDouble(0)); return struct; } @@ -78,8 +81,8 @@ public class MTClassifierTest { * @param sR - the sample rate of the waveform. * @param templates - the match templates to test. */ - private static void testCorrelation(double[] testWaveform, float sR, ArrayList templates) { - testCorrelation(testWaveform, sR, templates, MatchedTemplateParams.NORMALIZATION_RMS); + public static List testCorrelation(double[] testWaveform, float sR, ArrayList templates) { + return testCorrelation(testWaveform, sR, templates, MatchedTemplateParams.NORMALIZATION_RMS); } @@ -91,13 +94,16 @@ public class MTClassifierTest { * @param sR - the sample rate of the waveform. * @param templates - the match templates to test. * @param normalisation - the normalisation type to use e.g. MatchedTemplateParams.NORMALIZATION_RMS + * @return a list of the correlation results. */ - private static void testCorrelation(double[] testWaveform, float sR, ArrayList templates, int normalisation) { + public static List testCorrelation(double[] testWaveform, float sR, ArrayList templates, int normalisation) { + + List matchedTemplateResult = new ArrayList(); //create the classifier object for (int i=0; i importClicks(String filePath, float sR) { - MatFileReader mfr; try { - mfr = new MatFileReader(filePath); - // //get array of a name "my_array" from file - MLStructure mlArrayRetrived = (MLStructure) mfr.getMLArray( "clicks" ); + Mat5File mfr = Mat5.readFromFile(filePath); - int numClicks= mlArrayRetrived.getN(); + // //get array of a name "my_array" from file + Struct mlArrayRetrived = mfr.getStruct( "clicks" ); + + int numClicks= mlArrayRetrived.getNumCols(); ArrayList clicks = new ArrayList(numClicks); for (int i=0; i importTemplates(String filePath) { - MatFileReader mfr; + Mat5File mfr; try { - mfr = new MatFileReader(filePath); + mfr = Mat5.readFromFile(filePath); // //get array of a name "my_array" from file - MLStructure mlArrayMatch = (MLStructure) mfr.getMLArray( "templates" ); - //System.out.println(mlArrayMatch.getType()); + Struct mlArrayMatch = mfr.getStruct( "templates" ); +// System.out.println(mlArrayMatch.getType() + " " + mlArrayMatch.getNumElements()); ArrayList templates = new ArrayList(); - int numTemplates= mlArrayMatch.getN(); + int numTemplates= mlArrayMatch.getNumElements(); for (int i=0; i importTemplate(String filePath) { - MatFileReader mfr; + Mat5File mfr; try { - mfr = new MatFileReader(filePath); + mfr = Mat5.readFromFile(filePath); // //get array of a name "my_array" from file - MLDouble mlArrayMatch = (MLDouble) mfr.getMLArray( "matchtemplate" ); - double[][] mlMatch= mlArrayMatch.getArray(); - MLDouble mlArrayReject = (MLDouble) mfr.getMLArray( "rejectemplate" ); - double[][] mlReject= mlArrayReject.getArray(); + Matrix mlArrayMatch = mfr.getArray( "matchtemplate" ); + double[][] mlMatch= PamArrayUtils.matrix2array(mlArrayMatch); + Matrix mlArrayReject = mfr.getArray( "rejectemplate" ); + double[][] mlReject= PamArrayUtils.matrix2array(mlArrayReject); ArrayList templates = new ArrayList(2); @@ -314,18 +322,19 @@ public class MTClassifierTest { System.out.println(""); System.out.println("Testing the classifier"); - ArrayList mlResults = new ArrayList(); - MLStructure strcut; + Struct mlResults = Mat5.newStruct(); + Struct strcut; int N = 3; // templates.remove(1); // templates.add(clicks.get(0)); for (int i=0; i templates = importTemplates(templteFilePath); @@ -382,7 +391,8 @@ public class MTClassifierTest { public static void main(String args[]) { - testMatchCorrLen(); +// testMatchCorrLen(); + testMatchCorr(); } diff --git a/src/matchedTemplateClassifer/MTProcess.java b/src/matchedTemplateClassifer/MTProcess.java index 92e86a2f..1e9c305f 100644 --- a/src/matchedTemplateClassifer/MTProcess.java +++ b/src/matchedTemplateClassifer/MTProcess.java @@ -421,7 +421,7 @@ public class MTProcess extends PamInstantProcess { ArrayList results = new ArrayList(); - System.out.println("Click waveform max: " + PamArrayUtils.max(clickWaveform) + " sample rate: " + sR); +// System.out.println("Click waveform max: " + PamArrayUtils.max(clickWaveform) + " sample rate: " + sR); //normalisation and picking peak has already been performed diff --git a/src/matchedTemplateClassifer/matched_click_classifer_help.md b/src/matchedTemplateClassifer/matched_click_classifer_help.md index d28e532b..608aed2a 100644 --- a/src/matchedTemplateClassifer/matched_click_classifer_help.md +++ b/src/matchedTemplateClassifer/matched_click_classifer_help.md @@ -14,7 +14,7 @@ The classifier is based on a matched filter i.e. a candidate click detection is 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_ @@ -38,9 +38,9 @@ _Restrict parameter extraction to XX samples_ sets the maximum length of the wav _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. +* _norm_ - this is the default - the click is divided by it's RMS amplitude. A waveform correalted with itself will return 1 as the maximum correlation value. * _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). +* _none_ no normalisation (not recommended). A waveform correlated with itself will return a value dependent on the amplitude and frequency content of the waveform. ### Template settings 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); + } diff --git a/src/metadata/swing/MetaDataDialog.java b/src/metadata/swing/MetaDataDialog.java index 9f744391..a0028bef 100644 --- a/src/metadata/swing/MetaDataDialog.java +++ b/src/metadata/swing/MetaDataDialog.java @@ -98,7 +98,8 @@ public class MetaDataDialog extends PamDialog { @Override public boolean getParams() { Deployment deployment = pamguardMetaData.getDeployment(); - boolean ok = descriptionPanel.getParams(deployment.getDescription()); + boolean ok = projectInformationPanel.getParams(deployment); + ok &= descriptionPanel.getParams(deployment.getDescription()); ok &= responsiblePanel.getParams(deployment.getMetadataInfo().getContact()); ok &= deploymentPeriodPanel.getParams(pamguardMetaData); diff --git a/src/noiseBandMonitor/NoiseBandControl.java b/src/noiseBandMonitor/NoiseBandControl.java index 7776b4e5..c9b0242d 100644 --- a/src/noiseBandMonitor/NoiseBandControl.java +++ b/src/noiseBandMonitor/NoiseBandControl.java @@ -236,6 +236,9 @@ public class NoiseBandControl extends PamControlledUnit implements PamSettings { @Override public boolean restoreSettings(PamControlledUnitSettings pamControlledUnitSettings) { noiseBandSettings = ((NoiseBandSettings)pamControlledUnitSettings.getSettings()).clone(); + System.out.println("********************************************************"); + System.out.println("NOISE BAND SETTINGS: " + noiseBandSettings.rawDataSource); + System.out.println("********************************************************"); return true; } diff --git a/src/noiseBandMonitor/NoiseBandDialog.java b/src/noiseBandMonitor/NoiseBandDialog.java index 65dfd940..fef945ce 100644 --- a/src/noiseBandMonitor/NoiseBandDialog.java +++ b/src/noiseBandMonitor/NoiseBandDialog.java @@ -231,6 +231,9 @@ public class NoiseBandDialog extends PamDialog { singleInstance = new NoiseBandDialog(parentFrame, noiseBandControl); } singleInstance.setupComplete = false; +// System.out.println("********************************************************"); +// System.out.println("NOISE BAND DIALOG: " + singleInstance.noiseBandSettings.rawDataSource); +// System.out.println("********************************************************"); singleInstance.noiseBandSettings = noiseBandControl.noiseBandSettings.clone(); singleInstance.setParams(); singleInstance.setVisible(true); @@ -287,7 +290,7 @@ public class NoiseBandDialog extends PamDialog { if (dataBlock == null) { return showWarning("You must select a source of raw audio data"); } - noiseBandSettings.rawDataSource = dataBlock.getDataName(); + noiseBandSettings.rawDataSource = dataBlock.getLongDataName(); noiseBandSettings.channelMap = sourcePanel.getChannelList(); if (noiseBandSettings.channelMap == 0) { return showWarning("You must select at least one data channel"); diff --git a/src/noiseBandMonitor/NoiseBandProcess.java b/src/noiseBandMonitor/NoiseBandProcess.java index 744f71b1..0da99e14 100644 --- a/src/noiseBandMonitor/NoiseBandProcess.java +++ b/src/noiseBandMonitor/NoiseBandProcess.java @@ -63,6 +63,10 @@ public class NoiseBandProcess extends PamProcess { public void setupProcess() { super.setupProcess(); PamDataBlock sourceData = noiseBandControl.getPamConfiguration().getDataBlock(RawDataUnit.class, noiseBandControl.noiseBandSettings.rawDataSource); + +// System.out.println("********************************************************"); +// System.out.println("NOISE BAND PROCESS: " + sourceData + " " + noiseBandControl.noiseBandSettings.rawDataSource); +// System.out.println("********************************************************"); if (sourceData == null) { return; } diff --git a/src/offlineProcessing/OLProcessDialog.java b/src/offlineProcessing/OLProcessDialog.java index 9302b10b..79601623 100644 --- a/src/offlineProcessing/OLProcessDialog.java +++ b/src/offlineProcessing/OLProcessDialog.java @@ -1,6 +1,7 @@ package offlineProcessing; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -12,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; @@ -24,10 +24,14 @@ import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.border.TitledBorder; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.swing.FontIcon; + 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; @@ -89,17 +93,31 @@ public class OLProcessDialog extends PamDialog { private PamPanel timeChunkDataPanel; - public static ImageIcon settings = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); +// public static ImageIcon settings = new ImageIcon(ClassLoader.getSystemResource("Resources/SettingsButtonSmall2.png")); + public static FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); + TaskStatus currentStatus = TaskStatus.IDLE; + /** + * Reference to the main panel + */ + 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); this.taskGroup = taskGroup; taskGroup.setTaskMonitor(new OLMonitor()); - JPanel mainPanel = new JPanel(); + mainPanel = new JPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); JPanel dataSelectPanel = new PamAlignmentPanel(BorderLayout.WEST); @@ -153,7 +171,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"); @@ -173,11 +191,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); @@ -207,6 +228,23 @@ public class OLProcessDialog extends PamDialog { setResizable(true); } + + /** + * 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. + */ + public JPanel getMainPanel() { + return mainPanel; + } /** * Get the delete old data check box @@ -290,7 +328,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++; @@ -348,7 +386,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; @@ -356,6 +394,7 @@ public class OLProcessDialog extends PamDialog { return true; } + public void setTaskToolTips() { int nTasks = taskGroup.getNTasks(); @@ -822,5 +861,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/offlineProcessing/OfflineTaskGroup.java b/src/offlineProcessing/OfflineTaskGroup.java index e99fc61b..40c37c66 100644 --- a/src/offlineProcessing/OfflineTaskGroup.java +++ b/src/offlineProcessing/OfflineTaskGroup.java @@ -66,6 +66,7 @@ public class OfflineTaskGroup implements PamSettings { private DataTimeLimits dataTimeLimits; private volatile TaskStatus completionStatus = TaskStatus.IDLE; + /** * PamControlledunit required in constructor since some bookkeeping will * be going on in the background which will need the unit type and name. @@ -76,7 +77,7 @@ public class OfflineTaskGroup implements PamSettings { public OfflineTaskGroup(PamControlledUnit pamControlledUnit, String settingsName) { super(); this.pamControlledUnit = pamControlledUnit; - pamControlledUnit.addOfflineTaskGroup(this); + if (pamControlledUnit!=null) pamControlledUnit.addOfflineTaskGroup(this); this.settingsName = settingsName; PamSettingManager.getInstance().registerSettings(this); } @@ -469,6 +470,9 @@ public class OfflineTaskGroup implements PamSettings { OfflineDataMap dataMap = primaryDataBlock.getPrimaryDataMap(); int nMapPoints = dataMap.getNumMapPoints(startTime, endTime); int iMapPoint = 0; + + System.out.println("N MAP POINTS: "+ nMapPoints + " dataMap: " + dataMap.getParentDataBlock() + " start: " + PamCalendar.formatDateTime(startTime) + " " +PamCalendar.formatDateTime(endTime)); + publish(new TaskMonitorData(TaskStatus.RUNNING, TaskActivity.PROCESSING, nMapPoints, 0, "", taskGroupParams.startRedoDataTime)); OfflineDataStore dataSource = dataMap.getOfflineDataSource(); @@ -598,7 +602,7 @@ public class OfflineTaskGroup implements PamSettings { /** * Called to process data currently in memory. i.e. get's called - * once when processing loaded data, multiple times when pocessing all data. + * once when processing loaded data, multiple times when processing all data. * @param globalProgress * @param mapPoint * @param processStartTime @@ -962,4 +966,14 @@ public class OfflineTaskGroup implements PamSettings { public OfflineSuperDetFilter getSuperDetectionFilter() { return superDetectionFilter; } + + /** + * Clear all task from the task group. This also clears affected and required datablocks. + */ + public void clearTasks() { + requiredDataBlocks.clear(); + affectedDataBlocks.clear(); + offlineTasks.clear(); + + } } diff --git a/src/pamMaths/PamVector.java b/src/pamMaths/PamVector.java index d491f01e..cbea40c9 100644 --- a/src/pamMaths/PamVector.java +++ b/src/pamMaths/PamVector.java @@ -384,6 +384,7 @@ public class PamVector implements Serializable, Cloneable, PamCoordinate, Manage public double norm(int nDim) { return Math.sqrt(normSquared(nDim)); } + /** * * @param vec a PamVector diff --git a/src/pamScrollSystem/LoadQueueProgressData.java b/src/pamScrollSystem/LoadQueueProgressData.java index bebb4a9a..949c4cec 100644 --- a/src/pamScrollSystem/LoadQueueProgressData.java +++ b/src/pamScrollSystem/LoadQueueProgressData.java @@ -34,11 +34,13 @@ public class LoadQueueProgressData extends PamTaskUpdate { this.loadEnd = loadEnd; this.loadCurrent = loadCurrent; this.nLoaded = nLoaded; + setDualProgressUpdate(true); } public LoadQueueProgressData(int status) { super(); this.setStatus(PamTaskUpdate.STATUS_DONE); + setDualProgressUpdate(true); } @@ -119,6 +121,7 @@ public class LoadQueueProgressData extends PamTaskUpdate { @Override public double getProgress() { +// System.out.println( getStreamName() + " progress: " + iStream + " of " + totalStreams); if (getStreamName() != null) { return (double )iStream/ (double) totalStreams; } @@ -127,6 +130,7 @@ public class LoadQueueProgressData extends PamTaskUpdate { @Override public String getProgressString() { + if (getState() == LoadQueueProgressData.STATE_LINKINGSUBTABLE) { return String.format("Linking subtable data %d of %d", getIStream(), getTotalStreams()); @@ -141,13 +145,15 @@ public class LoadQueueProgressData extends PamTaskUpdate { } @Override - public double getProgress2() { + public double getProgress2() { +// System.out.println( getStreamName() + " progress2: " + getLoadStart() ); long interval =getLoadEnd() - getLoadStart(); - if (interval == 0) { + if (interval <= 0) { return ProgressIndicator.INDETERMINATE_PROGRESS; } else { long done =getLoadCurrent() - getLoadStart(); +// System.out.println( getStreamName() + " progress2: " + done + " " + interval + " start " + getLoadStart() + " end " + getLoadEnd()) return ((double) done )/ interval; } } diff --git a/src/pamScrollSystem/PamScrollSlider.java b/src/pamScrollSystem/PamScrollSlider.java index e0438a57..d6d36491 100644 --- a/src/pamScrollSystem/PamScrollSlider.java +++ b/src/pamScrollSystem/PamScrollSlider.java @@ -39,6 +39,7 @@ public class PamScrollSlider extends AbstractPamScrollerAWT { panel.add(BorderLayout.CENTER, slider); panel.add(BorderLayout.SOUTH, getButtonPanel()); } +// slider.setFocusable(false); } class SliderListener implements ChangeListener { @@ -180,5 +181,11 @@ public class PamScrollSlider extends AbstractPamScrollerAWT { // TODO Auto-generated method stub super.setPageStep(pageStep); } + /** + * @return the slider + */ + public JSlider getSlider() { + return slider; + } } diff --git a/src/pamViewFX/DataLoadingPane.java b/src/pamViewFX/DataLoadingPane.java index 30b15781..4a9b3ef7 100644 --- a/src/pamViewFX/DataLoadingPane.java +++ b/src/pamViewFX/DataLoadingPane.java @@ -57,12 +57,16 @@ public class DataLoadingPane extends PamBorderPane { PamVBox holderPane=new PamVBox(); holderPane.setSpacing(5); + holderPane.getChildren().add(allProgress); + holderPane.getChildren().add(streamProgress); + return holderPane; } public void setData(LoadQueueProgressData progressData) { + streamProgress.setProgress(-1); if (progressData.getStreamName() != null) { allProgress.setProgress(progressData.getTotalStreams()/progressData.getIStream()); // storeName.setText(progressData.getStoreType()); diff --git a/src/pamViewFX/PAMGuiFXSettings.java b/src/pamViewFX/PAMGuiFXSettings.java index 8a5af7a9..fed74564 100644 --- a/src/pamViewFX/PAMGuiFXSettings.java +++ b/src/pamViewFX/PAMGuiFXSettings.java @@ -36,7 +36,7 @@ public class PAMGuiFXSettings implements Serializable, Cloneable { /** * True if PG is in fullscreen on startup */ - public boolean fullscreen = true; + public boolean fullscreen = false; /* (non-Javadoc) diff --git a/src/pamViewFX/PamGuiFX.java b/src/pamViewFX/PamGuiFX.java index f6e87e84..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 @@ -174,7 +176,7 @@ public class PamGuiFX extends StackPane implements PamViewInterface { // showButtonRight.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_LEFT, PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); showButtonRight.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-left", PamGuiManagerFX.iconColor, PamGuiManagerFX.iconSize)); //showLeftButton.setText(PamFontAwesome.ICON_CHEVRON_LEFT); - showButtonRight.getStyleClass().add("close-button-left"); + showButtonRight.getStyleClass().add("close-button-left-trans"); showButtonRight.setStyle("-fx-background-radius: 0 0 0 0;"); //alter animations to remove/add showButton to tab pane. @@ -199,8 +201,8 @@ public class PamGuiFX extends StackPane implements PamViewInterface { }); PamButton closeButtonLeft=hidingSidePane.getHideButton(); + closeButtonLeft.getStyleClass().add("close-button-right-trans"); // closeButtonLeft.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.CHEVRON_RIGHT, Color.DARKGRAY.darker(), PamGuiManagerFX.iconSize)); - closeButtonLeft.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-right", Color.DARKGRAY.darker(), PamGuiManagerFX.iconSize)); closeButtonLeft.prefHeightProperty().bind(mainTabPane.getHeaderHeightProperty()); //add hiding pane to main pane. @@ -212,12 +214,12 @@ public class PamGuiFX extends StackPane implements PamViewInterface { hidingPaneLeft=new HidingPane(Side.LEFT, settingsPane, this, false); hidingPaneLeft.showHidePane(false); - hidingPaneLeft.getStylesheets().add(pamGuiManagerFX.getPamSettingsCSS()); + hidingPaneLeft.getStylesheets().addAll(pamGuiManagerFX.getPamSettingsCSS()); PamButton showButtonLeft=hidingPaneLeft.getShowButton(); // showButtonLeft.setGraphic(PamGlyphDude.createPamGlyph(FontAwesomeIcon.BARS, Color.LIGHTGRAY, PamGuiManagerFX.iconSize)); showButtonLeft.setGraphic(PamGlyphDude.createPamIcon("mdi2m-menu", Color.LIGHTGRAY, PamGuiManagerFX.iconSize)); - showButtonLeft.getStyleClass().add("close-button-right"); + showButtonLeft.getStyleClass().add("close-button-right-trans"); showButtonLeft.setStyle(" -fx-background-radius: 0 0 0 0;"); PamButton closeRightButton=hidingPaneLeft.getHideButton(); @@ -242,22 +244,23 @@ 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); + addPamTab(new TabInfo("Display " + (this.getNumTabs()+1)), null ,true); mainTabPane.layout(); }); //now have a holder - add the loading pane. /**create left hiding pane**/ loadPane=new PamLoadingPane(this.pamGuiManagerFX); - loadPane.setPrefWidth(250); - hidingLoadPane=new HidingPane(Side.LEFT, loadPane, this, false); + hidingLoadPane=new HidingPane(Side.TOP, loadPane, this, false); + hidingLoadPane.setPrefHeight(110); hidingLoadPane.removeHideButton(); hidingLoadPane.showHidePane(false); PamBorderPane layoutHolder=new PamBorderPane(layout); - layoutHolder.setLeft(hidingLoadPane); + layoutHolder.setTop(hidingLoadPane); return layoutHolder; @@ -306,8 +309,23 @@ public class PamGuiFX extends StackPane implements PamViewInterface { } } - newTab.setDetachable(detachable); + newTab.setOnClosed((action)->{ + //when a tab is closer. + for (int i=0; i allTabs = getAllTabs(); for (int i=0; i settingsDialog=new PamSettingsDialogFX(tabSelectionPane); - settingsDialog.getDialogPane().getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); + settingsDialog.getDialogPane().getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); settingsDialog.initStyle(StageStyle.UNDECORATED); // ChoiceDialog dialog = new ChoiceDialog<>(tabStrings.get(1), tabStrings); @@ -492,7 +507,7 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { * Get CSS for PAMGUARD setting 'look and feel' for sliding dialogs * @return the CSS for settings feels. */ - public String getPamSettingsCSS() {//return new PrimerDark().getUserAgentStylesheet(); + public ArrayList getPamSettingsCSS() {//return new PrimerDark().getUserAgentStylesheet(); // return getClass().getResource("/Resources/css/pamSettingsCSS.css").toExternalForm(); return PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS(); } @@ -501,7 +516,7 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { * Get CSS for PAMGUARD GUI standard 'look and feel' * @return the standard CSS fro PAMGUARD. */ - public String getPamCSS() { + public ArrayList getPamCSS() { //return new PrimerLight().getUserAgentStylesheet(); // return getClass().getResource("/Resources/css/pamCSS.css").toExternalForm(); return PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS(); @@ -511,7 +526,7 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { * Get CSS for PAMGUARD GUI standard 'look and feel' for regular dialogs * @return the standard CSS fro PAMGUARD. */ - public String getPamDialogCSS() {//return new PrimerDark().getUserAgentStylesheet(); + public ArrayList getPamDialogCSS() {//return new PrimerDark().getUserAgentStylesheet(); return PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS(); } @@ -577,7 +592,6 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { public Stage getMainScene() { - // TODO Auto-generated method stub return primaryStage; } @@ -653,7 +667,7 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { mainPane.getChildren().addAll(title, namePane); dialog.getDialogPane().setContent(mainPane); - dialog.getDialogPane().getStylesheets().add(this.getPamDialogCSS()); + dialog.getDialogPane().getStylesheets().addAll(this.getPamDialogCSS()); //add listener to prevent close request if the dialog final Button btOk = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK); @@ -1022,6 +1036,10 @@ public class PamGuiManagerFX implements PAMControllerGUI, PamSettings { } + public Window getPrimaryStage() { + return this.primaryStage; + } + } diff --git a/src/pamViewFX/PamGuiTabFX.java b/src/pamViewFX/PamGuiTabFX.java index cf03e5d2..9e1f3fb2 100644 --- a/src/pamViewFX/PamGuiTabFX.java +++ b/src/pamViewFX/PamGuiTabFX.java @@ -40,7 +40,7 @@ public class PamGuiTabFX extends PamTabFX { /** * List of internal panes within the tab content pane. */ - ArrayList internalPanes=new ArrayList(); + private ArrayList internalPanes=new ArrayList(); /** * True if panes are editable. @@ -87,34 +87,25 @@ public class PamGuiTabFX extends PamTabFX { /** * Constructor for a new tab - * @param text. Name of tab + * @param tabInfo - info on the tab such as name + * @param pamGui - reference to the PamGuiFX the pane belongs to. */ public PamGuiTabFX(TabInfo tabInfo, PamGuiFX pamGui) { - super(tabInfo.tabName); - this.pamGui=pamGui; - this.tabInfo=tabInfo; - - contentHolder=new PamBorderPane(); - - //needs to be in this order so toolbar pane sits on top of center pane. - holder=new Pane(); - contentHolder.setCenter(holder); - - super.setContent(contentHolder); - setPanesEditable(editable); + this(tabInfo, null, pamGui); } /** * Constructor for a new tab - * @param text - Name of tab - * @param toolbar - pane which sits just below tab - * @param content - content for tab pane. + * @param tabInfo - info on the tab such as name + * @param newContent - content to display + * @param pamGui - reference to the PamGuiFX the pane belongs to. */ public PamGuiTabFX(TabInfo tabInfo, UserDisplayNodeFX newContent, PamGuiFX pamGui) { super(tabInfo.tabName); this.pamGui=pamGui; this.tabInfo=tabInfo; + contentHolder=new PamBorderPane(); //needs to be in this order so toolbar pane sits on top of center pane. @@ -165,7 +156,7 @@ public class PamGuiTabFX extends PamTabFX { public Pane createNewPane(Tab tab, PamTabPane tabPane, Stage newStage){ //create a new GUI frame. PamGuiFX pamGUIFX=new PamGuiFX(tabPane, newStage, pamGui.getPamGuiManagerFX()); - pamGUIFX.getStylesheets().add(pamGui.getPamGuiManagerFX().getPamCSS()); + pamGUIFX.getStylesheets().addAll(pamGui.getPamGuiManagerFX().getPamCSS()); //need to add PamGUIFX to list in PamGUIManagerFX. @@ -210,7 +201,17 @@ public class PamGuiTabFX extends PamTabFX { * @return the internal pane which has been added */ public PamGuiInternalPane addInternalPane(UserDisplayNodeFX userDisplayNodeFX){ +// System.out.println("UserDisplayNodeFX: " + userDisplayNodeFX); if (userDisplayNodeFX==null || userDisplayNodeFX.getNode()==null) return null; + + for (PamGuiInternalPane internalPane: this.internalPanes) { + if (userDisplayNodeFX == internalPane.getUserDisplayNode()) { + System.err.println("UserDisplayNodeFX is laready in pane"); + return null; + } + } + + PamGuiInternalPane newInternalPane=new PamGuiInternalPane(userDisplayNodeFX, holder); if (!userDisplayNodeFX.isResizeableDisplay()) newInternalPane.showResizeControls(false); holder.getChildren().add(newInternalPane); @@ -404,7 +405,9 @@ public class PamGuiTabFX extends PamTabFX { * @return name of tabs. */ public String getName() { - return this.getText(); + //note that this should not be getText() because + //this is not set in the parent class constructor. + return this.tabInfo.tabName; } /** @@ -413,7 +416,7 @@ public class PamGuiTabFX extends PamTabFX { * @author Jamie Macaulay * */ - private class PamGuiInternalPane extends PamInternalPane { + class PamGuiInternalPane extends PamInternalPane { private UserDisplayNodeFX mainPane; diff --git a/src/pamViewFX/PamSettingsMenuPane.java b/src/pamViewFX/PamSettingsMenuPane.java index e1cfc2bd..545c8b00 100644 --- a/src/pamViewFX/PamSettingsMenuPane.java +++ b/src/pamViewFX/PamSettingsMenuPane.java @@ -13,24 +13,36 @@ import PamController.PamSettingManager; import PamController.SettingsPane; import PamController.StorageOptions; import PamController.StorageParameters; +import PamController.soundMedium.GlobalMedium; +import PamController.soundMedium.GlobalMedium.SoundMedium; import PamModel.PamModuleInfo; +import PamView.dialog.warn.WarnOnce; import pamViewFX.fxNodes.PamButton; import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; import pamViewFX.fxNodes.pamDialogFX.PamSettingsDialogFX; import pamViewFX.fxNodes.utilityPanes.SettingsDialog; import pamViewFX.fxSettingsPanes.StorageOptionsPane; +import pamViewFX.fxStyles.PamStylesManagerFX; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.geometry.Side; import javafx.scene.control.Label; import javafx.scene.control.Labeled; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; +import javafx.scene.control.Separator; import javafx.scene.text.TextAlignment; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.Priority; /** * Pane which holds settings menu. All primary settings are accessed from this pane which sits in a hiding pane to the right * of the main GUI. + * * @author Jamie Macaulay * */ @@ -41,6 +53,12 @@ public class PamSettingsMenuPane extends PamVBox { StorageOptionsPane storageOptionPane; + public String warning = " Changing to between air and water requires a PAMGuard restart\n" + + " for some display changes to take effect. Settings such as\n" + + " sound speed, reciever sensitivity values and data unit amplitudes\n" + + " will be recalculated or set to new default values.\n" + + "

Data processing changes are ready to use immediately."; + public PamSettingsMenuPane(){ this.setSpacing(0); @@ -48,12 +66,14 @@ public class PamSettingsMenuPane extends PamVBox { this.setPrefWidth(250); this.setStyle("-fx-background-color: -fx-darkbackground"); + + this.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); Label settingsLabel=new Label("Settings"); settingsLabel.setPadding(new Insets(14,0,10,0)); settingsLabel.setAlignment(Pos.CENTER); settingsLabel.setPrefWidth(Double.MAX_VALUE); - settingsLabel.setTextAlignment(TextAlignment.CENTER); + settingsLabel.setTextAlignment(TextAlignment.LEFT); settingsLabel.setStyle("-fx-font-weight: bold;"); PamButton saveConfig=new PamButton("Save"); @@ -68,12 +88,57 @@ public class PamSettingsMenuPane extends PamVBox { }); styleButton(saveConfigAs); + + //Air or water mode + ToggleButton toggleButton1 = new ToggleButton("Water"); + toggleButton1.setPrefWidth(60); + toggleButton1.setTooltip(new Tooltip(GlobalMedium.getToolTip(SoundMedium.Water))); + toggleButton1.setOnAction((action)->{ + if (PamController.getInstance().getGlobalMediumManager().getGlobalMediumParameters().currentMedium==SoundMedium.Water) return; //do nothing. + PamController.getInstance().getGlobalMediumManager().setCurrentMedium(SoundMedium.Water); + }); + + ToggleButton toggleButton2 = new ToggleButton("Air"); + toggleButton2.setPrefWidth(60); + toggleButton2.setTooltip(new Tooltip(GlobalMedium.getToolTip(SoundMedium.Air))); + toggleButton2.setOnAction((action)->{ + if (PamController.getInstance().getGlobalMediumManager().getGlobalMediumParameters().currentMedium==SoundMedium.Air) return; //do nothing. + PamController.getInstance().getGlobalMediumManager().setCurrentMedium(SoundMedium.Air); + }); + + ToggleGroup toggleGroup = new ToggleGroup(); + toggleButton1.setToggleGroup(toggleGroup); + toggleButton2.setToggleGroup(toggleGroup); + toggleGroup.selectToggle(toggleButton1); + + Label mediumLabel = new Label("Sound Medium"); + mediumLabel.setAlignment(Pos.CENTER_LEFT); + mediumLabel.setPadding(new Insets(0,0,0,15)); + //styleButton(mediumLabel); + + + PamHBox toggleButtonBox = new PamHBox(); + toggleButtonBox.setAlignment(Pos.CENTER_RIGHT); + toggleButtonBox.setSpacing(5); + toggleButtonBox.getChildren().addAll(toggleButton1, toggleButton2); + toggleButtonBox.setMaxWidth(Double.MAX_VALUE); + + PamHBox mediumToggleBox = new PamHBox(mediumLabel,toggleButtonBox); + PamHBox.setHgrow(toggleButtonBox, Priority.ALWAYS); + mediumToggleBox.setAlignment(Pos.CENTER_LEFT); + mediumToggleBox.setSpacing(5); + mediumToggleBox.setPadding(new Insets(0,5,0,0)); + + PamButton generalSettings=new PamButton("General Settings..."); styleButton(generalSettings); MenuButton settings=new MenuButton("Module Settings"); - settings.setStyle("-fx-background-radius: 0;" - + " -fx-border-color: transparent; -fx-padding: 0 0 0 0;"); + settings.setPopupSide(Side.RIGHT); +// settings.setStyle("-fx-background-radius: 0;" +// + " -fx-border-color: transparent; -fx-padding: 0 0 0 0;"); + + styleButton(settings); settings.setAlignment(Pos.CENTER_LEFT); settings.setPrefWidth(Double.MAX_VALUE); settings.setPrefHeight(40); @@ -146,16 +211,20 @@ public class PamSettingsMenuPane extends PamVBox { PamButton about=new PamButton("About..."); styleButton(about); - PamButton tip=new PamButton("Tip of the day..."); - styleButton(tip); +// PamButton tip=new PamButton("Tip of the day..."); +// styleButton(tip); PamButton website=new PamButton("Website"); styleButton(website); - PamButton contact=new PamButton("Contact and Support"); + PamButton contact=new PamButton("Found a bug?"); styleButton(contact); - this.getChildren().addAll(settingsLabel,saveConfig,saveConfigAs, generalSettings, settings, storageManager, database, binaryStorage, help, about, tip, website, contact); + PamButton checkForUpdates=new PamButton("Check for updates"); + styleButton(checkForUpdates); + + this.getChildren().addAll(settingsLabel,saveConfig,saveConfigAs, new Separator(), mediumToggleBox, generalSettings, settings, new Separator(), + storageManager, database, binaryStorage, new Separator(), help, checkForUpdates, website, contact, about); } @@ -168,6 +237,7 @@ public class PamSettingsMenuPane extends PamVBox { public void styleButton(Labeled control){ Insets buttonInsets=new Insets(0,leftInset,0,leftInset); control.setAlignment(Pos.CENTER_LEFT); + control.setStyle("-fx-alignment: center-left;"); control.setPadding(buttonInsets); control.getStyleClass().add("square-button-trans"); control.setPrefWidth(Double.MAX_VALUE); @@ -198,6 +268,4 @@ public class PamSettingsMenuPane extends PamVBox { } } - - } diff --git a/src/pamViewFX/TabSelectionPane.java b/src/pamViewFX/TabSelectionPane.java index 25f862e8..2abdcbbd 100644 --- a/src/pamViewFX/TabSelectionPane.java +++ b/src/pamViewFX/TabSelectionPane.java @@ -57,14 +57,14 @@ public class TabSelectionPane extends SettingsPane { 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/fxGlyphs/PamSVGIcon.java b/src/pamViewFX/fxGlyphs/PamSVGIcon.java index ed82172a..2df67799 100644 --- a/src/pamViewFX/fxGlyphs/PamSVGIcon.java +++ b/src/pamViewFX/fxGlyphs/PamSVGIcon.java @@ -4,6 +4,7 @@ import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; +import javafx.scene.shape.FillRule; import javafx.scene.shape.SVGPath; import pamViewFX.fxNodes.utilsFX.PamUtilsFX; @@ -62,12 +63,18 @@ public class PamSVGIcon { } return instance; } + + @Deprecated + public PamSVGIcon create(URL path, Color color, double lineWidth) throws Exception { + return create(path); + } - public PamSVGIcon create(URL path, Color color) throws Exception { -// System.out.println("Create icon start"); + public PamSVGIcon create(URL path) throws Exception { - String col = PamUtilsFX.toRGBCode(color); + // System.out.println("Create icon start"); + +// String col = PamUtilsFX.toRGBCode(color); // System.out.println("Create icon getDocument()"); @@ -87,13 +94,27 @@ public class PamSVGIcon { for(int i=0; i0) 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/connectionPane/ConnectionLine.java b/src/pamViewFX/fxNodes/connectionPane/ConnectionLine.java index 9a143cdd..d4219198 100644 --- a/src/pamViewFX/fxNodes/connectionPane/ConnectionLine.java +++ b/src/pamViewFX/fxNodes/connectionPane/ConnectionLine.java @@ -12,6 +12,7 @@ import javafx.scene.shape.Shape; /** * Line which connects a plug or socket to a ConnectionRectangle or other ConnectionLine. + * * @author Jamie Macaulay * */ diff --git a/src/pamViewFX/fxNodes/connectionPane/ConnectionPane.java b/src/pamViewFX/fxNodes/connectionPane/ConnectionPane.java index 8c741a50..855e9344 100644 --- a/src/pamViewFX/fxNodes/connectionPane/ConnectionPane.java +++ b/src/pamViewFX/fxNodes/connectionPane/ConnectionPane.java @@ -411,26 +411,29 @@ public class ConnectionPane extends Pane { if (includeBranch) { StandardConnectionSocket socket; for (int i = 0; i < parentNode.getConnectionPlugs().size(); i++) { - socket = (StandardConnectionSocket) parentNode.getConnectionPlugs().get(i).getConnectedShape(); - if (socket == null || !socket.isBranch()) - continue; - // is this socket connected to child node? - if (socket.getParentConnectionPlug().getConnectedShape() != null) { + if (parentNode.getConnectionPlugs().get(i).getConnectedShape() instanceof StandardConnectionSocket) { + socket = (StandardConnectionSocket) parentNode.getConnectionPlugs().get(i).getConnectedShape(); + if (socket == null || !socket.isBranch()) + continue; + // is this socket connected to child node? + if (socket.getParentConnectionPlug().getConnectedShape() != null) { - if (socket.getParentConnectionPlug().getConnectedShape().getConnectionNode() == childNode) { - // return socket.getParentConnectionPlug(); - node = socket.getConnectedShape(); - } - else if (bypassStructure && socket.getParentConnectionPlug().getConnectedShape() - .getConnectionNode() instanceof ConnectionStructure) { - // go down another level - node = getConnectionPlug(parentNode, - socket.getParentConnectionPlug().getConnectedShape().getConnectionNode(), includeBranch, - bypassStructure); + if (socket.getParentConnectionPlug().getConnectedShape().getConnectionNode() == childNode) { + // return socket.getParentConnectionPlug(); + node = socket.getConnectedShape(); + } + else if (bypassStructure && socket.getParentConnectionPlug().getConnectedShape() + .getConnectionNode() instanceof ConnectionStructure) { + // go down another level + node = getConnectionPlug(parentNode, + socket.getParentConnectionPlug().getConnectedShape().getConnectionNode(), includeBranch, + bypassStructure); + } } + + if (node != null) + return node; } - if (node != null) - return node; } } diff --git a/src/pamViewFX/fxNodes/flipPane/FlipPane.java b/src/pamViewFX/fxNodes/flipPane/FlipPane.java index a3dc0adf..63fd16ec 100644 --- a/src/pamViewFX/fxNodes/flipPane/FlipPane.java +++ b/src/pamViewFX/fxNodes/flipPane/FlipPane.java @@ -48,7 +48,7 @@ public class FlipPane extends StackPane { private double flipTime; private Orientation flipDirection; - private BooleanProperty flippedFrontProperty = new SimpleBooleanProperty(); + protected BooleanProperty flippedFrontProperty = new SimpleBooleanProperty(); // ******************** Constructors ************************************** public FlipPane() { @@ -62,7 +62,9 @@ public class FlipPane extends StackPane { backRotate = new Rotate(180, Orientation.HORIZONTAL == FLIP_DIRECTION ? Rotate.Y_AXIS : Rotate.X_AXIS); front = new StackPane(); + front.setMaxWidth(Double.MAX_VALUE); back = new StackPane(); + back.setMaxWidth(Double.MAX_VALUE); back.setVisible(false); @@ -73,7 +75,7 @@ public class FlipPane extends StackPane { flipTime = 700; flipDirection = FLIP_DIRECTION; flippedFrontProperty.setValue(true); - + registerListeners(); } diff --git a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java index c5a12aaf..756748bd 100644 --- a/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java +++ b/src/pamViewFX/fxNodes/flipPane/PamFlipPane.java @@ -1,13 +1,16 @@ package pamViewFX.fxNodes.flipPane; import javafx.application.Platform; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Node; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.Labeled; +import javafx.scene.control.MenuItem; import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -34,13 +37,31 @@ import pamViewFX.fxNodes.utilsFX.TextUtilsFX; * */ public class PamFlipPane extends FlipPane { - + public static final double FLIP_TIME =250; //milliseconds - the default is 700ms whihc is way too long. + /** + * The back is pressed- equivelent to an OK. + */ + public static final int OK_BACK_BUTTON = 0; + + /** + * The back is pressed- equivelent to an OK. + */ + public static final int NORESPONE_BACK_BUTTON = -1; + + /** + * The back button is cancelled + */ + public static final int CANCEL_BACK_BUTTON = 1; + + /** + * Advanced pane. + */ private PamBorderPane advPane; - + private PamBorderPane frontPane; - + private PamButton backButton; @@ -59,29 +80,34 @@ public class PamFlipPane extends FlipPane { * then editable to change the name of a parameter. */ private Label postLabel; + + /** + * Property for the response to the back button. The response can be OK_BACK_BUTTON or CANCEL_BACK_BUTTON. + */ + private SimpleIntegerProperty backButtonResponse = new SimpleIntegerProperty(); + - public PamFlipPane() { - super(); - this.advPane = createAdvSettingsPane(); - this.getFront().getChildren().add(frontPane = new PamBorderPane()); - -// this.getFront().setStyle("-fx-background-color: grey;"); -// this.getBack().setStyle("-fx-background-color: grey;"); - - this.getBack().getChildren().add(advPane); - this.setFlipTime(FLIP_TIME); - + this(Orientation.HORIZONTAL); } + public PamFlipPane(Orientation FLIP_DIRECTION) { super(FLIP_DIRECTION); this.advPane = createAdvSettingsPane(); this.getFront().getChildren().add(frontPane = new PamBorderPane()); this.getBack().getChildren().add(advPane); this.setFlipTime(FLIP_TIME); + + this.flipFrontProperty().addListener((obsVal, oldval, newVal)->{ + if (!newVal.booleanValue()) { + //need to set the back button so that listeners are triggered whenever the back + //button is pressed. + this.backButtonResponse.set(NORESPONE_BACK_BUTTON); + } + }); } - + /** * Get the front pane. * @return the front pane. @@ -89,7 +115,7 @@ public class PamFlipPane extends FlipPane { public PamBorderPane getFrontPane() { return frontPane; } - + /** * Get the back pane. * @return the back pane. @@ -105,16 +131,16 @@ public class PamFlipPane extends FlipPane { public PamBorderPane getAdvPane() { return advPane; } - - + + /** * Set the advanced pane content. * @param - the content to set. */ public void setAdvPaneContent(Node content) { - advPane.setCenter(content); + advPane.setCenter(content); } - + /** * Set the front pane content. * @param - the content to set. @@ -128,25 +154,42 @@ public class PamFlipPane extends FlipPane { * Create the advanced settings pane which can be accessed by DAQ panes if needed. */ private PamBorderPane createAdvSettingsPane() { - + backButton = new PamButton(); backButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-left", Color.WHITE, PamGuiManagerFX.iconSize)); -// backButton.setStyle("-fx-background-color: -color-base-6"); + // backButton.setStyle("-fx-background-color: -color-base-6"); //backButton.setStyle("-fx-padding: 0,0,0,0"); - - backButton.setOnAction((action)->{ - // System.out.println("FLIP BACK TO FRONT"); - this.flipToFront(); - }); - + //make the back button blue so users can easily see the button. backButton.setStyle("-fx-background-radius: 0 5 5 0; -fx-border-radius: 0 5 5 0; -fx-background-color: -color-accent-6"); + backButton.setOnAction((action)->{ +// System.out.println("FLIP BACK TO FRONT"); + this.backButtonResponse.setValue(OK_BACK_BUTTON); + this.flipToFront(); + }); + + //Allow the user to cancel instead of pressing back button to confirm. + ContextMenu contextMenu = new ContextMenu(); + //Creating the menu Items for the context menu + MenuItem item1 = new MenuItem("Cancel"); + +// item1.setStyle("-fx-highlight-color: red"); + + item1.setOnAction((action)->{ + // System.out.println("FLIP BACK TO FRONT"); + this.backButtonResponse.setValue(CANCEL_BACK_BUTTON); + this.flipToFront(); + }); + contextMenu.getItems().addAll(item1); + + backButton.setContextMenu(contextMenu); + + //create a title that can be set. //backButton.setPrefWidth(150); PamBorderPane advPane = new PamBorderPane(); //advPane.setPadding(new Insets(5,5,5,5)); - - + // holds the title of the advanced pane. This consists of a label for a graphic, // an editable text field and a label after the editable settings field PamHBox titleHolder = new PamHBox(); @@ -158,27 +201,27 @@ public class PamFlipPane extends FlipPane { postLabel.setAlignment(Pos.CENTER_LEFT); advLabel.setAlignment(Pos.CENTER); -// advLabel.prefColumnCountProperty().bind(advLabel.textProperty().length().subtract(3)); + // advLabel.prefColumnCountProperty().bind(advLabel.textProperty().length().subtract(3)); // Set Max and Min Width to PREF_SIZE so that the TextField is always PREF advLabel.setMinWidth(Region.USE_PREF_SIZE); advLabel.setMaxWidth(Region.USE_PREF_SIZE); - + //pretty complicated to make sure the text field is the same size as the text that is being typed. advLabel.textProperty().addListener((ov, prevText, currText) -> { - // Do this in a Platform.runLater because of Textfield has no padding at first time and so on - Platform.runLater(() -> { - Text text = new Text(currText); - text.setFont(advLabel.getFont()); // Set the same font, so the size is the same - double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField - + advLabel.getPadding().getLeft() + advLabel.getPadding().getRight() // Add the padding of the TextField - + 2d; // Add some spacing - advLabel.setPrefWidth(width); // Set the width - advLabel.positionCaret(advLabel.getCaretPosition()); // If you remove this line, it flashes a little bit - }); + // Do this in a Platform.runLater because of Textfield has no padding at first time and so on + Platform.runLater(() -> { + Text text = new Text(currText); + text.setFont(advLabel.getFont()); // Set the same font, so the size is the same + double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField + + advLabel.getPadding().getLeft() + advLabel.getPadding().getRight() // Add the padding of the TextField + + 2d; // Add some spacing + advLabel.setPrefWidth(width); // Set the width + advLabel.positionCaret(advLabel.getCaretPosition()); // If you remove this line, it flashes a little bit + }); }); advLabel.setId("label-title2"); advLabel.setStyle("-fx-background-color: transparent"); - + titleHolder.setMaxWidth(Double.MAX_VALUE); //need to make sure label is in center. //holds the back button and the title pane. @@ -190,31 +233,31 @@ public class PamFlipPane extends FlipPane { advLabel.setAlignment(Pos.CENTER); advLabel.setMaxWidth(Double.MAX_VALUE); //need to make sure label is in center. -// PamGuiManagerFX.titleFont2style(advLabel); + // PamGuiManagerFX.titleFont2style(advLabel); advLabel.setAlignment(Pos.CENTER); HBox.setHgrow(titleHolder, Priority.ALWAYS); - + advPane.setTop(buttonHolder); - + return advPane; - + } - + public TextField getAdvLabel() { return advLabel; } -// public void setAdvLabel(Label advLabel) { -// this.advLabel = advLabel; -// } + // public void setAdvLabel(Label advLabel) { + // this.advLabel = advLabel; + // } public PamButton getBackButton() { return backButton; } - + /** * Get the label located before the editable label in the title * @return the label before the editable label @@ -222,7 +265,7 @@ public class PamFlipPane extends FlipPane { public Label getPreAdvLabel() { return preLabel; } - + /** * Get the label located after the editable label in the title * @return the label after the editable label @@ -240,7 +283,14 @@ public class PamFlipPane extends FlipPane { public void setAdvLabelEditable(boolean b) { this.advLabel.setEditable(b); - + } + + /** + * The back button property. Called whenever the back button is pressed. + * @return the back button integer proeprty. + */ + public SimpleIntegerProperty backButtonProperty(){ + return this.backButtonResponse; } diff --git a/src/pamViewFX/fxNodes/hidingPane/HidingPane.java b/src/pamViewFX/fxNodes/hidingPane/HidingPane.java index b60a5429..9e2ef7d8 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.8; + + /** * 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/fxNodes/navigationDrawer/NavigationDrawer.java b/src/pamViewFX/fxNodes/navigationDrawer/NavigationDrawer.java new file mode 100644 index 00000000..0f175c9e --- /dev/null +++ b/src/pamViewFX/fxNodes/navigationDrawer/NavigationDrawer.java @@ -0,0 +1,179 @@ +package pamViewFX.fxNodes.navigationDrawer; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener.Change; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Labeled; +import javafx.scene.control.SingleSelectionModel; +import javafx.scene.control.Tab; +import javafx.scene.layout.BorderPane; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamVBox; + +/** + * A pane which is somewhat related to a tab pane but where the tabs are on the + * left or right and horizontal instead of vertical. + */ +public class NavigationDrawer extends PamBorderPane { + + private double leftInset=20; + + private ObservableList tabs = FXCollections.observableArrayList(); + + //the currently selected tab is null. + private Tab currentlySelectTab = null; + + PamVBox tabHolder; + + private NavSlectionModel selectionModel; + + /** + *

The tabs to display in this TabPane. Changing this ObservableList will + * immediately result in the NavigationDrawer updating to display the new contents + * of this ObservableList.

+ * + *

If the tabs ObservableList changes, the selected tab will remain the previously + * selected tab, if it remains within this ObservableList. If the previously + * selected tab is no longer in the tabs ObservableList, the selected tab will + * become the first tab in the ObservableList.

+ * @return the list of tabs + */ + public final ObservableList getTabs() { + return tabs; + } + + + public NavigationDrawer() { + + tabHolder = new PamVBox(); + tabHolder.setSpacing(0); + + tabHolder.setPrefWidth(200); + + BorderPane.setMargin(tabHolder, new Insets(0,0,0,0)); + +// tabHolder.setStyle("-fx-background-color: -fx-darkbackground"); +// tabHolder.setStyle("-fx-background-color: red; -fx-padding: 0px 0px 0px 0px;"); + + this.setLeft(tabHolder); + + this.tabs.addListener((Change c) -> { + layoutNavigationDrawer(); + }); + +// this.setStyle("-fx-background-color: orange"); + + //this is required for some styles ot make sure there are no insets. + this.setStyle("-fx-padding: 0px 0px 0px 0px"); + + + selectionModel = new NavSlectionModel(); + + + } + + /** + * Layout the navigation drawer. + */ + private void layoutNavigationDrawer() { + +// System.out.println("LAYOUT NAVIGATION DRAWER:"); + + tabHolder.getChildren().clear(); + for (int i = 0; i{ + setNavigationTab(ii); + }); + tabHolder.getChildren().add(button); + } + + //make sure previous tab is selected. + if (currentlySelectTab!=null) { + int ind = tabs.indexOf(currentlySelectTab); + if (ind>=0) { + setNavigationTab(ind); + } + else if (tabs.size()>0) { + setNavigationTab(0); + } + else { + this.setCenter(null); + } + } + else if (tabs.size()>0) { + setNavigationTab(0); + } + else { + this.setCenter(null); + } + } + + public void setNavigationTab(int ii){ + //set all tabs to default style. + for (int i=0; i{ + + @Override + protected Tab getModelItem(int index) { + return tabs.get(index); + } + + @Override + protected int getItemCount() { + return tabs.size(); + } + + } + + + + + +} + + diff --git a/src/pamViewFX/fxNodes/pamAxis/PamDateAxis.java b/src/pamViewFX/fxNodes/pamAxis/PamDateAxis.java new file mode 100644 index 00000000..e877beec --- /dev/null +++ b/src/pamViewFX/fxNodes/pamAxis/PamDateAxis.java @@ -0,0 +1,827 @@ +package pamViewFX.fxNodes.pamAxis; + +import com.sun.javafx.charts.ChartLayoutAnimator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.css.CssMetaData; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableProperty; +import javafx.css.converter.SizeConverter; +import javafx.geometry.Dimension2D; +import javafx.geometry.Side; +import javafx.scene.chart.ValueAxis; +import javafx.util.Duration; +import javafx.util.StringConverter; +import javafx.util.converter.TimeStringConverter; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +/* +* Created with IntelliJ IDEA. +* User: Pedro Duque Vieira +* Date: 15-08-2013 +* Time: 18:33 +* To change this template use File | Settings | File Templates. +*/ +public class PamDateAxis extends ValueAxis { + + /** We use these for auto ranging to pick a user friendly tick unit. (must be increasingly bigger)*/ + private static final double[] TICK_UNIT_DEFAULTS = { + 86400000, // 1 day + 172800000, // 2 das + 259200000, // 3 days + 345600000, // 4 days + 432000000, // 5 days + 518400000, // 6 days + 604800000, // 7 days + 691200000, // 8 days + 777600000, // 9 days + 864000000, // 10 days + 216000000E1, // 15 days + 388800000E1, // 20 days + 604800000E1, // 25 days + 872640000E1, // 31 days ~ 1 month + 1226880000E1, // 41 days + 1667520000E1, // 51 days + 2203200000E1, // 62 days ~ 2 months + 2868480000E1, // 77 days + 3672000000E1, // 93 days ~ 3 months + 4605120000E1, // 108 days + 5676480000E1, // 124 days ~ 4 months + 6877440000E1, // 139 days + 8216640000E1, // 155 days ~ 5 months + 9685440000E1, // 170 days + 1129248000E2, // 186 days ~ 6 months + 1445472000E2 // 366 days ~ 1 year + }; + + /** These are matching date formatter strings */ + private static final String[] TICK_UNIT_FORMATTER_DEFAULTS = { + "MM/dd/yy", // 1 day + "MM/dd/yy", // 2 das + "MM/dd/yy", // 3 days + "MM/dd/yy", // 4 days + "MM/dd/yy", // 5 days + "MM/dd/yy", // 6 days + "MM/dd/yy", // 7 days + "MM/dd/yy", // 8 days + "MM/dd/yy", // 9 days + "MM/dd/yy", // 10 days + "MM/dd/yy", // 15 days + "MM/dd/yy", // 20 days + "MM/dd/yy", // 25 days + "MMM-yyyy", // 31 days ~ 1 month + "MMM-yyyy", // 41 days + "MMM-yyyy", // 51 days + "MMM-yyyy", // 62 days ~ 2 months + "MMM-yyyy", // 77 days + "MMM-yyyy", // 93 days ~ 3 months + "MMM-yyyy", // 108 days + "MMM-yyyy", // 124 days ~ 4 months + "MMM-yyyy", // 139 days + "MMM-yyyy", // 155 days ~ 5 months + "MMM-yyyy", // 170 days + "MMM-yyyy", // 186 days ~ 6 months + "yyyy" // 366 days ~ 1 year + }; + + + private Object currentAnimationID; + private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this); + private IntegerProperty currentRangeIndexProperty = new SimpleIntegerProperty(this, "currentRangeIndex", -1); + private DefaultFormatter defaultFormatter = new DefaultFormatter(this); + + // -------------- PUBLIC PROPERTIES -------------------------------------------------------------------------------- + + /** When true zero is always included in the visible range. This only has effect if auto-ranging is on. */ + private BooleanProperty forceZeroInRange = new BooleanPropertyBase(true) { + @Override protected void invalidated() { + // This will effect layout if we are auto ranging + if(isAutoRanging()) requestAxisLayout(); + } + + @Override + public Object getBean() { + return PamDateAxis.this; + } + + @Override + public String getName() { + return "forceZeroInRange"; + } + }; + public final boolean isForceZeroInRange() { return forceZeroInRange.getValue(); } + public final void setForceZeroInRange(boolean value) { forceZeroInRange.setValue(value); } + public final BooleanProperty forceZeroInRangeProperty() { return forceZeroInRange; } + + /** The value between each major tick mark in data units. This is automatically set if we are auto-ranging. */ + private DoubleProperty tickUnit = new StyleableDoubleProperty(5) { + @Override protected void invalidated() { + if(!isAutoRanging()) { + invalidateRange(); + requestAxisLayout(); + } + } + + @Override + public CssMetaData getCssMetaData() { + return StyleableProperties.TICK_UNIT; + } + + @Override + public Object getBean() { + return PamDateAxis.this; + } + + @Override + public String getName() { + return "tickUnit"; + } + }; + public final double getTickUnit() { return tickUnit.get(); } + public final void setTickUnit(double value) { tickUnit.set(value); } + public final DoubleProperty tickUnitProperty() { return tickUnit; } + + // -------------- CONSTRUCTORS ------------------------------------------------------------------------------------- + + /** + * Create a auto-ranging DateAxis + */ + public PamDateAxis() { + forceZeroInRange.set(false); + } + + /** + * Create a non-auto-ranging DateAxis with the given upper bound, lower bound and tick unit + * + * @param lowerBound The lower bound for this axis, ie min plottable value + * @param upperBound The upper bound for this axis, ie max plottable value + * @param tickUnit The tick unit, ie space between tickmarks + */ + public PamDateAxis(double lowerBound, double upperBound, double tickUnit) { + super(lowerBound, upperBound); + setTickUnit(tickUnit); + } + + /** + * Create a non-auto-ranging DateAxis with the given upper bound, lower bound and tick unit + * + * @param axisLabel The name to display for this axis + * @param lowerBound The lower bound for this axis, ie min plottable value + * @param upperBound The upper bound for this axis, ie max plottable value + * @param tickUnit The tick unit, ie space between tickmarks + */ + public PamDateAxis(String axisLabel, double lowerBound, double upperBound, double tickUnit) { + super(lowerBound, upperBound); + setTickUnit(tickUnit); + setLabel(axisLabel); + } + + // -------------- PROTECTED METHODS -------------------------------------------------------------------------------- + + /** + * Get the string label name for a tick mark with the given value + * + * @param value The value to format into a tick label string + * @return A formatted string for the given value + */ + @Override protected String getTickMarkLabel(Long value) { + StringConverter formatter = getTickLabelFormatter(); + if (formatter == null) formatter = defaultFormatter; + return formatter.toString(value); + } + + /** + * Called to get the current axis range. + * + * @return A range object that can be passed to setRange() and calculateTickValues() + */ + @Override protected Object getRange() { + double[] newParams = recalculateTicks(); + double newMin = newParams[0]; + double newMax = newParams[1]; + double newIndex = newParams[2]; + double newTickUnit = newParams[3]; + return new double[]{ + newMin, + newMax, + newTickUnit, + getScale(), + newIndex + }; + } + + public double[] recalculateTicks() + { + final Side side = getSide(); + final boolean vertical = Side.LEFT.equals(side) || Side.RIGHT.equals(side); + final double length = vertical ? getHeight() : getWidth(); + // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally + double labelSize = getTickLabelFont().getSize() * 2; + + double currentRange = getUpperBound() - getLowerBound(); + + // calculate the number of tick-marks we can fit in the given length + int numOfTickMarks = (int)Math.floor(Math.abs(length)/labelSize); + // can never have less than 2 tick marks one for each end + numOfTickMarks = Math.max(numOfTickMarks, 2); + // calculate tick unit for the number of ticks can have in the given data range + double tickUnit = currentRange/(double)numOfTickMarks; + // search for the best tick unit that fits + double tickUnitRounded = 0; + double minRounded = 0; + double maxRounded = 0; + int count = 0; + double reqLength = Double.MAX_VALUE; + int rangeIndex = 10; + // loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks + while (reqLength > length || count > 20) { + // find a user friendly match from our default tick units to match calculated tick unit + for (int i=0; i tickUnit) { + tickUnitRounded = tickUnitDefault; + rangeIndex = i; + break; + } + } + // move min and max to nearest tick mark + minRounded = Math.floor(getLowerBound() / tickUnitRounded) * tickUnitRounded; + maxRounded = Math.ceil(getUpperBound() / tickUnitRounded) * tickUnitRounded; + // calculate the required length to display the chosen tick marks for real, this will handle if there are + // huge numbers involved etc or special formatting of the tick mark label text + double maxReqTickGap = 0; + double last = 0; + count = 0; + for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++) { + double size = (vertical) ? measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getHeight() : + measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getWidth(); + if (major == minRounded) { // first + last = size/2; + } else { + maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) ); + } + } + reqLength = (count-1) * maxReqTickGap; + tickUnit = tickUnitRounded; + // check if we already found max tick unit + if (tickUnitRounded == TICK_UNIT_DEFAULTS[TICK_UNIT_DEFAULTS.length-1]) { + // nothing we can do so just have to use this + break; + } + } + return new double[]{minRounded, maxRounded, rangeIndex, tickUnit}; + } + + /** + * Called to set the current axis range to the given range. If isAnimating() is true then this method should + * animate the range to the new range. + * + * @param range A range object returned from autoRange() + * @param animate If true animate the change in range + */ + @Override protected void setRange(Object range, boolean animate) { + final double[] rangeProps = (double[]) range; + final double lowerBound = rangeProps[0]; + final double upperBound = rangeProps[1]; + final double tickUnit = rangeProps[2]; + final double scale = rangeProps[3]; + final double rangeIndex = rangeProps[4]; + currentRangeIndexProperty.set((int)rangeIndex); + final double oldLowerBound = getLowerBound(); + setLowerBound(lowerBound); + setUpperBound(upperBound); + setTickUnit(tickUnit); + + ReadOnlyDoubleWrapper scalePropertyImplValue = (ReadOnlyDoubleWrapper) ReflectionUtils.forceMethodCall(ValueAxis.class, "scalePropertyImpl", this); + + if(animate) { + animator.stop(currentAnimationID); + currentAnimationID = animator.animate( + new KeyFrame(Duration.ZERO, + new KeyValue(currentLowerBound, oldLowerBound), + new KeyValue(scalePropertyImplValue, getScale()) + ), + new KeyFrame(Duration.millis(700), + new KeyValue(currentLowerBound, lowerBound), + new KeyValue(scalePropertyImplValue, scale) + ) + ); + } else { + currentLowerBound.set(lowerBound); + setScale(scale); + } + } + + /** + * Calculate a list of all the data values for each tick mark in range + * + * @param length The length of the axis in display units + * @param range A range object returned from autoRange() + * @return A list of tick marks that fit along the axis if it was the given length + */ + @Override protected List calculateTickValues(double length, Object range) { + final double[] rangeProps = (double[]) range; + final double lowerBound = rangeProps[0]; + final double upperBound = rangeProps[1]; + final double tickUnit = rangeProps[2]; + List tickValues = new ArrayList(); + if (tickUnit <= 0 || lowerBound == upperBound) { + tickValues.add((long)lowerBound); + } else if (getTickUnit() > 0) { + for (double major = lowerBound; major <= upperBound; major += tickUnit) { + tickValues.add((long)major); + if(tickValues.size()>2000) { + // This is a ridiculous amount of major tick marks, something has probably gone wrong + System.err.println("Warning we tried to create more than 2000 major tick marks on a NumberAxis. " + + "Lower Bound=" + lowerBound + ", Upper Bound=" + upperBound + ", Tick Unit=" + tickUnit); + break; + } + } + } + return tickValues; + } + + /** + * Calculate a list of the data values for every minor tick mark + * + * @return List of data values where to draw minor tick marks + */ + protected List calculateMinorTickMarks() { + final List minorTickMarks = new ArrayList(); + final double lowerBound = getLowerBound(); + final double upperBound = getUpperBound(); + final double tickUnit = getTickUnit(); + final double minorUnit = tickUnit/getMinorTickCount(); + if (getTickUnit() > 0) { + for (double major = lowerBound; major < upperBound; major += tickUnit) { + for (double minor=major+minorUnit; minor < (major+tickUnit); minor += minorUnit) { + minorTickMarks.add((long)minor); + if(minorTickMarks.size()>10000) { + // This is a ridiculous amount of major tick marks, something has probably gone wrong + System.err.println("Warning we tried to create more than 10000 minor tick marks on a NumberAxis. " + + "Lower Bound=" + getLowerBound() + ", Upper Bound=" + getUpperBound() + ", Tick Unit=" + tickUnit); + break; + } + } + } + } + return minorTickMarks; + } + + /** + * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks + * + * @param value tick mark value + * @param range range to use during calculations + * @return size of tick mark label for given value + */ + @Override protected Dimension2D measureTickMarkSize(Long value, Object range) { + final double[] rangeProps = (double[]) range; + final double rangeIndex = rangeProps[4]; + return measureTickMarkSize(value, getTickLabelRotation(), (int)rangeIndex); + } + + /** + * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks + * + * @param value tick mark value + * @param rotation The text rotation + * @param rangeIndex The index of the tick unit range + * @return size of tick mark label for given value + */ + private Dimension2D measureTickMarkSize(Long value, double rotation, int rangeIndex) { + String labelText; + StringConverter formatter = getTickLabelFormatter(); + if (formatter == null) formatter = defaultFormatter; + if(formatter instanceof DefaultFormatter) { + labelText = ((DefaultFormatter)formatter).toString(value, rangeIndex); + } else { + labelText = formatter.toString(value); + } + return measureTickMarkLabelSize(labelText, rotation); + } + + /** + * Called to set the upper and lower bound and anything else that needs to be auto-ranged + * + * @param minValue The min data value that needs to be plotted on this axis + * @param maxValue The max data value that needs to be plotted on this axis + * @param length The length of the axis in display coordinates + * @param labelSize The approximate average size a label takes along the axis + * @return The calculated range + */ + @Override protected Object autoRange(double minValue, double maxValue, double length, double labelSize) { + final Side side = getSide(); + final boolean vertical = Side.LEFT.equals(side) || Side.RIGHT.equals(side); + // check if we need to force zero into range + if (isForceZeroInRange()) { + if (maxValue < 0) { + maxValue = 0; + } else if (minValue > 0) { + minValue = 0; + } + } + final double range = maxValue-minValue; + +// // pad min and max by 2%, checking if the range is zero + final double paddedRange = (range==0) ? 2 : Math.abs(range)*1.02; + + final double padding = (paddedRange - range) / 2; + // if min and max are not zero then add padding to them + double paddedMin = minValue - padding; + double paddedMax = maxValue + padding; + // check padding has not pushed min or max over zero line + if ((paddedMin < 0 && minValue >= 0) || (paddedMin > 0 && minValue <= 0)) { + // padding pushed min above or below zero so clamp to 0 + paddedMin = 0; + } + if ((paddedMax < 0 && maxValue >= 0) || (paddedMax > 0 && maxValue <= 0)) { + // padding pushed min above or below zero so clamp to 0 + paddedMax = 0; + } + // calculate the number of tick-marks we can fit in the given length + int numOfTickMarks = (int)Math.floor(Math.abs(length)/labelSize); + // can never have less than 2 tick marks one for each end + numOfTickMarks = Math.max(numOfTickMarks, 2); + // calculate tick unit for the number of ticks can have in the given data range + double tickUnit = paddedRange/(double)numOfTickMarks; + // search for the best tick unit that fits + double tickUnitRounded = 0; + double minRounded = 0; + double maxRounded = 0; + int count = 0; + double reqLength = Double.MAX_VALUE; + int rangeIndex = 10; + // loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks + while (reqLength > length || count > 20) { + // find a user friendly match from our default tick units to match calculated tick unit + for (int i=0; i tickUnit) { + tickUnitRounded = tickUnitDefault; + rangeIndex = i; + break; + } + } + // move min and max to nearest tick mark + minRounded = Math.floor(paddedMin / tickUnitRounded) * tickUnitRounded; + maxRounded = Math.ceil(paddedMax / tickUnitRounded) * tickUnitRounded; + // calculate the required length to display the chosen tick marks for real, this will handle if there are + // huge numbers involved etc or special formatting of the tick mark label text + double maxReqTickGap = 0; + double last = 0; + count = 0; + for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++) { + double size = (vertical) ? measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getHeight() : + measureTickMarkSize((long)major, getTickLabelRotation(), rangeIndex).getWidth(); + if (major == minRounded) { // first + last = size/2; + } else { + maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) ); + } + } + reqLength = (count-1) * maxReqTickGap; + tickUnit = tickUnitRounded; + // check if we already found max tick unit + if (tickUnitRounded == TICK_UNIT_DEFAULTS[TICK_UNIT_DEFAULTS.length-1]) { + // nothing we can do so just have to use this + break; + } + } + // calculate new scale + final double newScale = calculateNewScale(length, minRounded, maxRounded); + // return new range + return new double[]{minRounded, maxRounded, tickUnitRounded, newScale, rangeIndex}; + } + + // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ + + /** @treatAsPrivate implementation detail */ + private static class StyleableProperties { + private static final CssMetaData TICK_UNIT = + new CssMetaData("-fx-tick-unit", + SizeConverter.getInstance(), 5.0) { + + @Override + public boolean isSettable(PamDateAxis n) { + return n.tickUnit == null || !n.tickUnit.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(PamDateAxis n) { + return (StyleableProperty)n.tickUnitProperty(); + } + }; + + private static final List> STYLEABLES; + static { + final List> styleables = + new ArrayList>(ValueAxis.getClassCssMetaData()); + styleables.add(TICK_UNIT); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + * @since JavaFX 8.0 + */ + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + /** + * {@inheritDoc} + * @since JavaFX 8.0 + */ + @Override + public List> getCssMetaData() { + return getClassCssMetaData(); + } + + // -------------- INNER CLASSES ------------------------------------------------------------------------------------ + + /** + * Default number formatter for DateAxis, this stays in sync with auto-ranging and formats values appropriately. + * You can wrap this formatter to add prefixes or suffixes; + * @since JavaFX 2.0 + */ + public static class DefaultFormatter extends StringConverter { + private TimeStringConverter formatter; + private String prefix = null; + private String suffix = null; + + private Date tempDate = new Date(); + + /** used internally */ + private DefaultFormatter() { + formatter = new TimeStringConverter("MM/dd/yy"); + } + + /** + * Construct a DefaultFormatter for the given DateAxis + * + * @param axis The axis to format tick marks for + */ + public DefaultFormatter(final PamDateAxis axis) { + formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1); + final ChangeListener axisListener = new ChangeListener() { + @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { + formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1); + } + }; + axis.currentRangeIndexProperty.addListener(axisListener); + axis.autoRangingProperty().addListener(axisListener); + } + + /** + * Construct a DefaultFormatter for the given DateAxis with a prefix and/or suffix. + * + * @param axis The axis to format tick marks for + * @param prefix The prefix to append to the start of formatted number, can be null if not needed + * @param suffix The suffix to append to the end of formatted number, can be null if not needed + */ + public DefaultFormatter(PamDateAxis axis, String prefix, String suffix) { + this(axis); + this.prefix = prefix; + this.suffix = suffix; + } + + private static TimeStringConverter getFormatter(int rangeIndex) { + if (rangeIndex < 0) { + return new TimeStringConverter("MM/dd/yy"); + } else if(rangeIndex >= TICK_UNIT_FORMATTER_DEFAULTS.length) { + return new TimeStringConverter(TICK_UNIT_FORMATTER_DEFAULTS[TICK_UNIT_FORMATTER_DEFAULTS.length-1]); + } else { + return new TimeStringConverter(TICK_UNIT_FORMATTER_DEFAULTS[rangeIndex]); + } + } + + /** + * Converts the object provided into its string form. + * Format of the returned string is defined by this converter. + * @return a string representation of the object passed in. + * @see StringConverter#toString + */ + @Override public String toString(Long object) { + return toString(object, formatter); + } + + private String toString(Long object, int rangeIndex) { + return toString(object, getFormatter(rangeIndex)); + } + + private String toString(Long object, TimeStringConverter formatter) { + tempDate.setTime(object); + if (prefix != null && suffix != null) { + return prefix + formatter.toString(tempDate) + suffix; + } else if (prefix != null) { + return prefix + formatter.toString(tempDate); + } else if (suffix != null) { + return formatter.toString(tempDate) + suffix; + } else { + return formatter.toString(tempDate); + } + } + + /** + * Converts the string provided into a Number defined by the this converter. + * Format of the string and type of the resulting object is defined by this converter. + * @return a Number representation of the string passed in. + * @see StringConverter#toString + */ + @Override public Long fromString(String string) { + int prefixLength = (prefix == null)? 0: prefix.length(); + int suffixLength = (suffix == null)? 0: suffix.length(); + return formatter.fromString(string.substring(prefixLength, string.length() - suffixLength)).getTime(); + } + } + + + public static void main (String [] args) + { + // Date construction test + GregorianCalendar calendar = new GregorianCalendar(1900, 0, 1); // year, month, day + Date date = calendar.getTime(); + TimeStringConverter timeConverter = new TimeStringConverter("MM/dd/yyyy"); + System.out.println("This is the date toString = " + timeConverter.toString(date)); + + // What is 1 day converted to long + calendar = new GregorianCalendar(1900, 0, 1); + date = calendar.getTime(); + calendar.add(Calendar.DAY_OF_MONTH, 1); + Date secondDate = calendar.getTime(); + long firstDateValue = date.getTime(); + long secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (1 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 2 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 2); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (2 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 3 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 3); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (3 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 4 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 4); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (4 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 5 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 5); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (5 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 6 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 6); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (6 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 7 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 7); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (7 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 8 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 8); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (8 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 9 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 9); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (9 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 10 days converted to long + calendar = new GregorianCalendar(1900, 0, 1); + calendar.add(Calendar.DAY_OF_MONTH, 10); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (10 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 15 days? With a long type + calendar.add(Calendar.DAY_OF_MONTH, 15); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (15 days) - \t" + (secondDateValue - firstDateValue)); + + // What is 20 days? With a long type + calendar.add(Calendar.DAY_OF_MONTH, 20); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (20 days) - \t" + (secondDateValue - firstDateValue)); + + // What is 25 days? With a long type + calendar.add(Calendar.DAY_OF_MONTH, 25); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (25 days) - \t" + (secondDateValue - firstDateValue)); + + + // What is 1 mont converted to long + calendar.add(Calendar.DAY_OF_MONTH, 31); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (31 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 41 days + calendar.add(Calendar.DAY_OF_MONTH, 41); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (41 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 51 days + calendar.add(Calendar.DAY_OF_MONTH, 51); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (51 day) - \t" + (secondDateValue - firstDateValue)); + + // What is 62 days ( 2 monhts + calendar.add(Calendar.DAY_OF_MONTH, 62); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (62 days - 2 months) - \t" + (secondDateValue - firstDateValue)); + + // What is 77 days ( 2 monhts and a half + calendar.add(Calendar.DAY_OF_MONTH, 77); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (77 days - 2.5 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 93 days ( 3 monhts + calendar.add(Calendar.DAY_OF_MONTH, 93); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (93 days - 3 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 108 ( 3 monhts and a half + calendar.add(Calendar.DAY_OF_MONTH, 108); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (108 days - 3.5 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 124 ( 4 monhts + calendar.add(Calendar.DAY_OF_MONTH, 124); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (124 days - 4 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 139 ( 4.5 monhts + calendar.add(Calendar.DAY_OF_MONTH, 139); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (139 days - 4.5 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 139 ( 5 monhts + calendar.add(Calendar.DAY_OF_MONTH, 155); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (155 days - 5 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 139 ( 5.5 monhts + calendar.add(Calendar.DAY_OF_MONTH, 170); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (170 days - 5.5 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 139 ( 6 monhts + calendar.add(Calendar.DAY_OF_MONTH, 186); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (186 days - 6 month) - \t" + (secondDateValue - firstDateValue)); + + // What is 366 ( 1 year) + calendar.add(Calendar.DAY_OF_MONTH, 366); + secondDate = calendar.getTime(); + secondDateValue = secondDate.getTime(); + System.out.println("This is the difference of value between the first and second date (366 days - 1 year) - \t" + (secondDateValue - firstDateValue)); + } + +} \ No newline at end of file diff --git a/src/pamViewFX/fxNodes/pamAxis/ReflectionUtils.java b/src/pamViewFX/fxNodes/pamAxis/ReflectionUtils.java new file mode 100644 index 00000000..ff42894a --- /dev/null +++ b/src/pamViewFX/fxNodes/pamAxis/ReflectionUtils.java @@ -0,0 +1,58 @@ +package pamViewFX.fxNodes.pamAxis; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Pedro + * Date: 11-09-2013 + * Time: 23:21 + * To change this template use File | Settings | File Templates. + */ +public class ReflectionUtils { + + public static Object forceMethodCall(Class classInstance, String methodName, Object source, Class[] paramTypes, Object[] params) { + Object returnedObject = null; + try { + Method method = classInstance.getDeclaredMethod(methodName, paramTypes); + method.setAccessible(true); + returnedObject = method.invoke(source, params); + } catch (Exception e) { + e.printStackTrace(); + } + return returnedObject; + } + + public static Object forceMethodCall(Class classInstance, String methodName, Object source, Object... params) { + Class[] paramTypes = new Class[]{}; + if (params == null) { + params = new Object[]{}; + } + List derivedTypes = new ArrayList<>(); + for (Object p : params) { + derivedTypes.add(p.getClass()); + } + if (derivedTypes.size() > 0) { + paramTypes = derivedTypes.toArray(new Class[derivedTypes.size()]); + } + return forceMethodCall(classInstance, methodName, source, paramTypes, params); + } + + public static Object forceFieldCall(Class classInstance, String fieldName, Object source) { + Object returnedObject = null; + try { + Field field = classInstance.getDeclaredField(fieldName); + field.setAccessible(true); + returnedObject = field.get(source); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + + return returnedObject; + } +} \ No newline at end of file diff --git a/src/pamViewFX/fxNodes/pamDialogFX/PamDialogFX.java b/src/pamViewFX/fxNodes/pamDialogFX/PamDialogFX.java index 62192f14..d83b0dd7 100644 --- a/src/pamViewFX/fxNodes/pamDialogFX/PamDialogFX.java +++ b/src/pamViewFX/fxNodes/pamDialogFX/PamDialogFX.java @@ -20,6 +20,7 @@ import javafx.stage.Window; /** * Creates a dialog with some useful PAMGUARD customisation. + * * @author Jamie Macaulay * */ @@ -253,6 +254,14 @@ public abstract class PamDialogFX extends Dialog { public static boolean showError(String content) { return showMessageDialog(null, "Error", content, AlertType.ERROR); } + + /** + * Show error dialog with default OK and CANCEL. + * @param content - the error message. + */ + public static boolean showError(String title, String content) { + return showMessageDialog(null, title, content, AlertType.ERROR); + } public static boolean showMessageDialog(Window owner, String string, String string2, ButtonType yes, ButtonType cancel) { diff --git a/src/pamViewFX/fxNodes/pamDialogFX/PamInternalDialogFX.java b/src/pamViewFX/fxNodes/pamDialogFX/PamInternalDialogFX.java index e3247eef..d8fa8fec 100644 --- a/src/pamViewFX/fxNodes/pamDialogFX/PamInternalDialogFX.java +++ b/src/pamViewFX/fxNodes/pamDialogFX/PamInternalDialogFX.java @@ -54,7 +54,7 @@ public class PamInternalDialogFX extends StackPane { mainPane.setPadding(new Insets(5,5,5,5)); PamStylesManagerFX stylesManager = PamStylesManagerFX.getPamStylesManagerFX(); - this.getStylesheets().add(stylesManager.getCurStyle().getSlidingDialogCSS()); + this.getStylesheets().addAll(stylesManager.getCurStyle().getSlidingDialogCSS()); //title and drag pane if (title!=null) { diff --git a/src/pamViewFX/fxNodes/pamDialogFX/PamJFXPanel.java b/src/pamViewFX/fxNodes/pamDialogFX/PamJFXPanel.java index 1774e0ce..e64ce8fe 100644 --- a/src/pamViewFX/fxNodes/pamDialogFX/PamJFXPanel.java +++ b/src/pamViewFX/fxNodes/pamDialogFX/PamJFXPanel.java @@ -30,7 +30,7 @@ public class PamJFXPanel extends JFXPanel { this.scene = new Scene(midBorderPane); - scene.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); + scene.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); setScene(scene); diff --git a/src/pamViewFX/fxNodes/pamDialogFX/PamSettingsDialogFX.java b/src/pamViewFX/fxNodes/pamDialogFX/PamSettingsDialogFX.java index 57fc5839..54767d94 100644 --- a/src/pamViewFX/fxNodes/pamDialogFX/PamSettingsDialogFX.java +++ b/src/pamViewFX/fxNodes/pamDialogFX/PamSettingsDialogFX.java @@ -32,7 +32,7 @@ public class PamSettingsDialogFX extends PamDialogFX { // this.getDialogPane().getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); // } PamStylesManagerFX stylesManager = PamStylesManagerFX.getPamStylesManagerFX(); - this.getDialogPane().getStylesheets().add(stylesManager.getCurStyle().getDialogCSS()); + this.getDialogPane().getStylesheets().addAll(stylesManager.getCurStyle().getDialogCSS()); this.setOnShown((value)->{ settingsPane.paneInitialized(); }); diff --git a/src/pamViewFX/fxNodes/pamScrollers/AbstractPamScrollerFX.java b/src/pamViewFX/fxNodes/pamScrollers/AbstractPamScrollerFX.java index 376f247c..bedb8603 100644 --- a/src/pamViewFX/fxNodes/pamScrollers/AbstractPamScrollerFX.java +++ b/src/pamViewFX/fxNodes/pamScrollers/AbstractPamScrollerFX.java @@ -158,7 +158,7 @@ public abstract class AbstractPamScrollerFX extends AbstractPamScroller implemen navigationdialog.setParams(this.getScrollerData()); PamSettingsDialogFX settingsDialog=new PamSettingsDialogFX(navigationdialog); - settingsDialog.getDialogPane().getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); + settingsDialog.getDialogPane().getStylesheets().addAll(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); Optional newData=settingsDialog.showAndWait(); //PamScrollerData newData=NavigationDialog.showDialog(null, false, StageStyle.UNDECORATED, this); diff --git a/src/pamViewFX/fxNodes/pamScrollers/NavigationDialog.java b/src/pamViewFX/fxNodes/pamScrollers/NavigationDialog.java index 38220dde..92384cf1 100644 --- a/src/pamViewFX/fxNodes/pamScrollers/NavigationDialog.java +++ b/src/pamViewFX/fxNodes/pamScrollers/NavigationDialog.java @@ -101,7 +101,7 @@ public class NavigationDialog extends SettingsPane { //FIXME for some reason when styling with settings CSS the dialog has strange resizing issues. This is due //to padding being set in the CSS- have fixed now but kept commented code to show workaround if ever needed in future. - outerPane.getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); + outerPane.getStylesheets().addAll(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); // this.getDialogPane().getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamCSS()); // this.getDialogPane().getStyleClass().add("dialog-pane-dark"); // this.getDialogPane().lookupButton(ButtonType.OK).getStyleClass().add("dialog-button-dark"); diff --git a/src/pamViewFX/fxNodes/popOver/PamPopOver.java b/src/pamViewFX/fxNodes/popOver/PamPopOver.java index d6460564..ecf1981d 100644 --- a/src/pamViewFX/fxNodes/popOver/PamPopOver.java +++ b/src/pamViewFX/fxNodes/popOver/PamPopOver.java @@ -33,7 +33,7 @@ public class PamPopOver extends PopOver { public PamPopOver(Pane content) { super(); super.setContentNode(makeResizableContent(content)); - content.setMaxSize(5000, 5000); + content.setMaxSize(2000, 2000); } /** diff --git a/src/pamViewFX/fxNodes/table/TableSettingsPane.java b/src/pamViewFX/fxNodes/table/TableSettingsPane.java index c0c15ee3..55ca656b 100644 --- a/src/pamViewFX/fxNodes/table/TableSettingsPane.java +++ b/src/pamViewFX/fxNodes/table/TableSettingsPane.java @@ -1,6 +1,7 @@ package pamViewFX.fxNodes.table; import pamViewFX.fxNodes.PamBorderPane; +import atlantafx.base.theme.Styles; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Orientation; @@ -9,6 +10,8 @@ import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; +import javafx.geometry.Insets; + /** * Pane which holds data within a table and has add, settings and delete buttons. @@ -33,6 +36,7 @@ public abstract class TableSettingsPane extends PamBorderPane { public TableSettingsPane(ObservableList data){ this.data=data; table = new TableView(); + this.setCenter(createPane()); } @@ -70,6 +74,7 @@ public abstract class TableSettingsPane extends PamBorderPane { //create pane holding add, edit and remove controls buttonPane=new TableButtonPane(Orientation.VERTICAL); + buttonPane.getAddButton().setOnAction((event)->{ createNewData(); }); @@ -87,8 +92,8 @@ public abstract class TableSettingsPane extends PamBorderPane { //make sure table resized with pane to stop blank column - getTableView().setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - + getTableView().setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS); + getButtonPane().getSettingsButton().setDisable(table.getItems().size()<=0); getButtonPane().getDeleteButton().setDisable(table.getItems().size()<=0); @@ -146,6 +151,11 @@ public abstract class TableSettingsPane extends PamBorderPane { public TableButtonPane getButtonPane() { return buttonPane; } + + + public ObservableList getData() { + return data; + } diff --git a/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java b/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java index 33eb11c1..d546f855 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java +++ b/src/pamViewFX/fxNodes/utilityPanes/FilterPaneFX.java @@ -242,13 +242,17 @@ public class FilterPaneFX extends SettingsPane { // freqList.add(defaultFrequencyVals[i]); // } - highCut=new PamSpinner(10.,500000.,2000.,2000.); + highCut=new PamSpinner(2.,500000.,2000.,2000.); highCut.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); highCut.getValueFactory().valueProperty().addListener((obs, before, after)->{ - if (after>=lowPass.getValue()) highCut.getValueFactory().setValue(Math.max(10,lowPass.getValue()-100)); + + if (after>=lowPass.getValue() && !lowPass.isDisable()) highCut.getValueFactory().setValue(Math.max(10,lowPass.getValue()-100)); if (after>sampleRate/2.) highCut.getValueFactory().setValue(sampleRate/2.); + filterParams.highPassFreq=highCut.getValue().floatValue(); - this.updateBodeGraph(); + + enableControls(); + updateBodeGraph(); }); highCut.setEditable(true); //highCut.setPrefColumnCount(6); @@ -256,15 +260,20 @@ public class FilterPaneFX extends SettingsPane { gridPaneFreq.add(highCut, 1, 0); gridPaneFreq.add(new Label("Hz"), 2, 0); - lowPass=new PamSpinner(10.,500000.,2000.,2000.); + lowPass=new PamSpinner(1.,500000.,2000.,2000.); lowPass.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + lowPass.getValueFactory().valueProperty().addListener((obs, before, after)->{ - if (after<=highCut.getValue()) lowPass.getValueFactory().setValue(Math.min(sampleRate/2.,highCut.getValue()+100)); + + if (after<=highCut.getValue() && !highCut.isDisable()) lowPass.getValueFactory().setValue(Math.min(sampleRate/2.,highCut.getValue()+100)); if (after>sampleRate/2.) lowPass.getValueFactory().setValue(sampleRate/2.); - filterParams.lowPassFreq=lowPass.getValue().floatValue(); + + filterParams.lowPassFreq=after.floatValue(); + enableControls(); - this.updateBodeGraph(); + updateBodeGraph(); }); + lowPass.setEditable(true); //lowCut.setPrefColumnCount(6); gridPaneFreq.add(new Label("Low Pass"), 0, 1); @@ -416,12 +425,15 @@ public class FilterPaneFX extends SettingsPane { logScale = new PamRadioButton("Log Scale"); logScale.setOnAction((action)->{ setGraphLogAxis(logScale.isSelected()); + updateBodeGraph(); }); + logScale.setToggleGroup(group); linScale = new PamRadioButton("Linear Scale"); linScale.setToggleGroup(group); linScale.setOnAction((action)->{ setGraphLogAxis(!linScale.isSelected()); + updateBodeGraph(); }); final PamHBox scalePane=new PamHBox(); @@ -466,7 +478,7 @@ public class FilterPaneFX extends SettingsPane { public void changed(ObservableValue observable, Number oldValue, Number newValue) { if (axisInitialised) { - updateBodeGraph(); + if (filterMethod!=null) updateBodeGraph(); axisInitialised=false; } } @@ -482,7 +494,6 @@ public class FilterPaneFX extends SettingsPane { @Override public void setParams(FilterParams filterParams) { this.filterParams=filterParams.clone(); - setSettings(); } @@ -490,7 +501,7 @@ public class FilterPaneFX extends SettingsPane { * Set controls to input params. */ private void setSettings() { - + if (filterParams == null) { filterParams = new FilterParams(); } @@ -529,11 +540,11 @@ public class FilterPaneFX extends SettingsPane { break; } - // highCut.setText(String.format("%1.1f", filterParams.highPassFreq)); - highCut.getValueFactory().setValue(Double.valueOf(Float.valueOf(filterParams.highPassFreq).doubleValue())); + highCut.getValueFactory().setValue(Double.valueOf(filterParams.highPassFreq)); // lowCut.setText(String.format("%1.1f", filterParams.lowPassFreq)); - lowPass.getValueFactory().setValue(Double.valueOf(Float.valueOf(filterParams.lowPassFreq).doubleValue())); + lowPass.getValueFactory().setValue(Double.valueOf(filterParams.lowPassFreq)); + filterOrder.getValueFactory().setValue(filterParams.filterOrder); setRippleParam(); @@ -689,6 +700,8 @@ public class FilterPaneFX extends SettingsPane { * @return a data series to add to the line chart */ private Series createFilterPoints(ValueAxis axis){ + + if (filterMethod==null) return null; Series series = new Series(); /* diff --git a/src/pamViewFX/fxNodes/utilityPanes/GroupedChannelBox.java b/src/pamViewFX/fxNodes/utilityPanes/GroupedChannelBox.java new file mode 100644 index 00000000..e752f9a8 --- /dev/null +++ b/src/pamViewFX/fxNodes/utilityPanes/GroupedChannelBox.java @@ -0,0 +1,54 @@ +package pamViewFX.fxNodes.utilityPanes; + +import org.controlsfx.control.CheckComboBox; + +import PamUtils.PamArrayUtils; +import PamView.GroupedSourceParameters; + +/** + * A ComboBox which shows a selectable list of channel groups. + * + * @author Jamie Macaulay + * + */ +public class GroupedChannelBox extends CheckComboBox { + + + /** + * The last set grouped parameters. + */ + GroupedSourceParameters params; + + public GroupedSourceParameters getGroupedParams() { + return params; + } + + /** + * Set the channel grouping for the check box. + * @param params - the parameters. + */ + public void setSource(GroupedSourceParameters params) { + + this.params = params; + + this.getItems().clear(); + + for (int i=0; i { if (!isAChannelSelected() ) { c.error("At least one channel needs to be selected for the module to work"); @@ -158,7 +150,7 @@ public class GroupedSourcePaneFX extends SourcePaneFX { for (int i = 0; i < PamConstants.MAX_CHANNELS; i++){ channelBoxes[i] = new CheckBox("Channel " + i); channelValidator.createCheck() - .dependsOn(("channel " + i), channelBoxes[i].selectedProperty()) + .dependsOn(("channel_" + i + "_" + this), channelBoxes[i].selectedProperty()) .withMethod(c -> { if (!isAChannelSelected() ) { c.error("At least one channel needs to be selected for the module to work"); @@ -197,34 +189,6 @@ public class GroupedSourcePaneFX extends SourcePaneFX { } - /** - * Get the validator for the channel. This can identify errors - * in - * @return - */ - public Validator getChannelValidator() { - return channelValidator; - } - - /** - * Check if - * @return - */ - private boolean isAChannelSelected() { - int channels = 0; - PamDataBlock sb = getSource(); - if (sb != null) { - // channels = sb.getChannelMap(); - channels = sb.getSequenceMap(); - } - int n=0; - //remove all channels from vertical box pane. - for (int i = 0; i < Math.min(PamConstants.MAX_CHANNELS, channelBoxes.length); i++) { - if ((channels & 1<{ public LatLongPane(String title) { super(null); - - + + mainPane = new PamVBox(); mainPane.setSpacing(5); mainPane.setAlignment(Pos.CENTER); - + Label titleLabel = new Label(title); titleLabel.maxWidth(Double.MAX_VALUE); titleLabel.setTextAlignment(TextAlignment.LEFT); + titleLabel.setAlignment(Pos.CENTER_LEFT); PamGuiManagerFX.titleFont2style(titleLabel); mainPane.getChildren().add(titleLabel); @@ -109,7 +110,7 @@ public class LatLongPane extends SettingsPane{ cent.getChildren().add(latStrip = new LatLongStrip(true)); cent.getChildren().add(longStrip = new LatLongStrip(false)); - + //bit of a hack that makes sure controls are aligned for the latitude and longitude. latStrip.getTitleLabel().prefWidthProperty().bind(longStrip.getTitleLabel().widthProperty()); @@ -123,46 +124,24 @@ public class LatLongPane extends SettingsPane{ * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(javafx.event.ActionEvent action) { - - - if (action.getSource() == decimalMinutes) { - LatLong.setFormatStyle(LatLong.FORMAT_DECIMALMINUTES); - // if (latStrip != null) { - // latStrip.setDecimalMinutes(true); - // longStrip.setDecimalMinutes(true); - // } - latStrip.showControls(LatLong.FORMAT_DECIMALMINUTES); - longStrip.showControls(LatLong.FORMAT_DECIMALMINUTES); - } - else if (action.getSource() == minutesSeconds){ - LatLong.setFormatStyle(LatLong.FORMAT_MINUTESSECONDS); - // if (latStrip != null) { - // latStrip.setDecimalMinutes(false); - // longStrip.setDecimalMinutes(false); - //// latStrip.showControls(); - //// longStrip.showControls(); - // } - latStrip.showControls(LatLong.FORMAT_MINUTESSECONDS); - longStrip.showControls(LatLong.FORMAT_MINUTESSECONDS); - } - else if (action.getSource() == decimal){ - LatLong.setFormatStyle(LatLong.FORMAT_DECIMAL); - latStrip.showControls(LatLong.FORMAT_DECIMAL); - longStrip.showControls(LatLong.FORMAT_DECIMAL); - } - + + int format = getSelectedLatLongFormat(); + + LatLong.setFormatStyle(format); + latStrip.showControls(format); + longStrip.showControls(format); } private void showLatLong() { - + decimalMinutes .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_DECIMALMINUTES); minutesSeconds .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_MINUTESSECONDS); decimal .setSelected(LatLong.getFormatStyle() == LatLong.FORMAT_DECIMAL); - latStrip .showControls(LatLong.getFormatStyle() ); - longStrip .showControls(LatLong.getFormatStyle() ); + latStrip .showControls(LatLong.getFormatStyle()); + longStrip .showControls(LatLong.getFormatStyle()); latStrip .setValue(latLong.getLatitude()); longStrip .setValue(latLong.getLongitude()); } @@ -174,21 +153,11 @@ public class LatLongPane extends SettingsPane{ */ @Override public LatLong getParams(LatLong currentParams) { - - Toggle selectedButton = this.segmentedButton.getToggleGroup().getSelectedToggle(); - - if (selectedButton == decimalMinutes) { - LatLong.setFormatStyle(LatLong.FORMAT_DECIMALMINUTES); - } - else if (selectedButton == minutesSeconds){ - LatLong.setFormatStyle(LatLong.FORMAT_MINUTESSECONDS); - - } - else if (selectedButton == decimal){ - LatLong.setFormatStyle(LatLong.FORMAT_DECIMAL); - } - + int format = getSelectedLatLongFormat(); + + LatLong.setFormatStyle(format); + latLong = new LatLong(latStrip.getValue(), longStrip.getValue()); if (Double.isNaN(latLong.getLatitude()) || Double.isNaN(latLong.getLongitude())) { return null; @@ -196,6 +165,26 @@ public class LatLongPane extends SettingsPane{ return latLong; } + /** + * Get the selected format for showing latitude and longitude values. + * @return the selected format flag or -1 if no format is selected. + */ + private int getSelectedLatLongFormat() { + Toggle selectedButton = this.segmentedButton.getToggleGroup().getSelectedToggle(); + if (selectedButton == decimalMinutes) { + return LatLong.FORMAT_DECIMALMINUTES; + + } + else if (selectedButton == minutesSeconds){ + return LatLong.FORMAT_MINUTESSECONDS; + + } + else if (selectedButton == decimal){ + return LatLong.FORMAT_DECIMAL; + } + return -1; + } + @Override public void setParams(LatLong input) { this.latLong=input; diff --git a/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java b/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java index 2e80e8f0..915de2d9 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java +++ b/src/pamViewFX/fxNodes/utilityPanes/LatLongStrip.java @@ -12,7 +12,7 @@ import pamViewFX.fxNodes.PamHBox; /** * A pane which shows a latitude or longitude. Allows users to enter data as decimal or minutes/seconds. - * (copied ot FX from Doug Gillespie's LatLongDialogStrip) + * (copied to FX from Doug Gillespie's LatLongDialogStrip) * @author Jamie Macaulay * */ @@ -39,6 +39,7 @@ public class LatLongStrip extends PamBorderPane { * The format type e.g. LatLong.FORMAT_DECIMALMINUTES. */ private int formatType = LatLong.FORMAT_DECIMALMINUTES; + private Label titleLabel; @@ -152,7 +153,7 @@ public class LatLongStrip extends PamBorderPane { this.setCenter(holder); this.setBottom(formattedText); - showControls(formatType); + showControls(formatType, true); } private void newTypedValues(KeyEvent e) { @@ -170,10 +171,25 @@ public class LatLongStrip extends PamBorderPane { // and say the formated version sayFormattedValue(v); } - + + + /** + * Change the current controls for to show to show the current format of Latitude or Longitude. + * @param formatStyle - the style of Latitude or longitude e.g. LatLong.FORMAT_DECIMALMINUTES; + */ public void showControls(int formatStyle) { - if (formatType==formatStyle) { + showControls(formatStyle, false); + } + + /** + * Change the current controls for to show to show the current format of Latitude or Longitude. + * @param force - force a reset of controls even if the format style is the same as the current style. + * @param formatStyle - the style of Latitude or longitude e.g. LatLong.FORMAT_DECIMALMINUTES; + */ + private void showControls(int formatStyle, boolean force) { + + if (formatType==formatStyle && !force) { return; } @@ -223,7 +239,7 @@ public class LatLongStrip extends PamBorderPane { public void setValue(double value, boolean hiddenOnly) { - System.out.println("Set value: " + value); +// System.out.println("Set value: " + value); if (value >= 0) { nsew.getSelectionModel().select(0); } diff --git a/src/pamViewFX/fxNodes/utilityPanes/SettingsDialog.java b/src/pamViewFX/fxNodes/utilityPanes/SettingsDialog.java index 7b8b88ca..96c4fe20 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/SettingsDialog.java +++ b/src/pamViewFX/fxNodes/utilityPanes/SettingsDialog.java @@ -2,8 +2,9 @@ package pamViewFX.fxNodes.utilityPanes; import javafx.stage.Stage; import javafx.stage.StageStyle; +import pamViewFX.PamGuiManagerFX; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; -import PamController.PamController; +import pamViewFX.fxStyles.PamStylesManagerFX; import PamController.SettingsPane; /** @@ -19,24 +20,23 @@ public class SettingsDialog extends PamDialogFX{ private SettingsPane settingsPane; public SettingsDialog(SettingsPane settingsPane){ - super(null, settingsPane.getName(), StageStyle.DECORATED); + super(PamGuiManagerFX.getInstance().getPrimaryStage() /*TODO - add stage*/, settingsPane.getName(), StageStyle.DECORATED); this.setResizable(true); this.settingsPane=settingsPane; + + this.setContent(settingsPane.getContentNode()); -// this.getDialogPane().getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamSettingsCSS()); - this.getDialogPane().getStylesheets().add(PamController.getInstance().getGuiManagerFX().getPamDialogCSS()); + Stage stage = (Stage) this.getDialogPane().getScene().getWindow(); stage.toFront(); -// //set results converter -// this.setResultConverter(dialogButton -> { -// if (dialogButton == ButtonType.OK) { -// T params = getParams(); -// if (params!=null) pamControlledUnit.setParams(params); -// } -// return null; -// }); + this.getDialogPane().getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); + +// System.out.println("SettingsPane: DIALOG " + this.getDialogPane().getStylesheets().size()); +// for (int i=0;i{ freqPane.setSampleRate(sampleRate); } + public void addValueChangeListener(Object object) { + // TODO Auto-generated method stub + + } + } diff --git a/src/pamViewFX/fxNodes/utilityPanes/SourcePaneFX.java b/src/pamViewFX/fxNodes/utilityPanes/SourcePaneFX.java index edfece24..f421cc34 100644 --- a/src/pamViewFX/fxNodes/utilityPanes/SourcePaneFX.java +++ b/src/pamViewFX/fxNodes/utilityPanes/SourcePaneFX.java @@ -15,15 +15,15 @@ import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.text.Font; import javafx.stage.Window; -import pamViewFX.PamGuiManagerFX; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamVBox; import pamViewFX.fxNodes.pamDialogFX.PamDialogFX2AWT; +import pamViewFX.validator.PamValidator; import PamController.PamController; +import PamController.PamGUIManager; import PamDetection.LocalisationInfo; import PamUtils.PamUtils; import PamView.dialog.SourcePanelMonitor; @@ -42,7 +42,6 @@ import PamguardMVC.PamDataBlock; @SuppressWarnings("rawtypes") public class SourcePaneFX extends PamBorderPane { - private PamBorderPane panel; private ArrayList sourceType = new ArrayList<>(); private boolean hasChannels; private String borderTitle; @@ -84,6 +83,12 @@ public class SourcePaneFX extends PamBorderPane { */ private Label titleLabel; + /** + * Validator for channels + */ + protected PamValidator channelValidator; + private Label channelLabel; + /** * Construct a panel with a titles border * @param borderTitle Title to go in border @@ -95,6 +100,7 @@ public class SourcePaneFX extends PamBorderPane { if (sourceType != null) { this.sourceType.add(new SourceSelection(sourceType, includeSubClasses)); } + channelValidator = new PamValidator(); this.setHasChannels(hasChannels); this.setBorderTitle(borderTitle); createPanel(); @@ -109,6 +115,7 @@ public class SourcePaneFX extends PamBorderPane { */ public SourcePaneFX(Class sourceType, boolean hasChannels, boolean includeSubClasses) { this.sourceType.add(new SourceSelection(sourceType, includeSubClasses)); + channelValidator = new PamValidator(); this.setHasChannels(hasChannels); createPanel(); setSourceList(); @@ -147,6 +154,7 @@ public class SourcePaneFX extends PamBorderPane { * Add a listener to the data source drop down list * @param listener listener */ + @SuppressWarnings("unchecked") public void addSelectionListener(ChangeListener listener) { sourceList.valueProperty().addListener(listener); } @@ -158,7 +166,7 @@ public class SourcePaneFX extends PamBorderPane { protected void createPanel() { - + PamVBox comboBoxPane=new PamVBox(); comboBoxPane.setSpacing(5); @@ -183,7 +191,7 @@ public class SourcePaneFX extends PamBorderPane { channelPanel.setSpacing(5); if (isHasChannels()) { - Label channelLabel = new Label("Channel"); + channelLabel = new Label("ChannelS"); //PamGuiManagerFX.titleFont2style(channelLabel); // channelLabel.setFont(PamGuiManagerFX.titleFontSize2); comboBoxPane.getChildren().add(channelLabel); @@ -191,8 +199,11 @@ public class SourcePaneFX extends PamBorderPane { channelPanel.getChildren().add(selectAll =new CheckBox("All")); channelBoxes =new CheckBox[PamConstants.MAX_CHANNELS]; selectAll.setOnAction((action)->{ + System.out.println("Stylesheets: 0: " + getStylesheets().size()); if (selectAll.isSelected()) selectAllChannels(); else selectNoChannels(); + channelValidator.validate(); //makes sure any error signs are removed. + System.out.println("Stylesheets: 1: " + getStylesheets().size()); }); } @@ -204,10 +215,21 @@ public class SourcePaneFX extends PamBorderPane { if (isHasChannels()){ for (int i = 0; i < PamConstants.MAX_CHANNELS; i++){ channelBoxes[i] = new CheckBox("Channel " + i); + channelValidator.createCheck() + .dependsOn(("channel_" + i + "_" + this), channelBoxes[i].selectedProperty()) + .withMethod(c -> { + if (!isAChannelSelected() ) { + c.error("At least one channel needs to be selected for the module to work"); + } + }) + .decorates(channelBoxes[i]) + .immediate(); + //channelPanel.getChildren().add(channelBoxes[i]); final int n=i; channelBoxes[i].setOnAction((action)->{ selectionChanged(n); + channelValidator.validate(); //makes sure any error signs are removed. }); //System.out.println("SourcePanel.java creatPanel"+i); } @@ -220,6 +242,31 @@ public class SourcePaneFX extends PamBorderPane { } + + public Label getChannelLabel() { + return channelLabel; + } + + /** + * Check if a single channel is selected. + * @return true if at least one channel is selected. + */ + public boolean isAChannelSelected() { + int channels = 0; + PamDataBlock sb = getSource(); + if (sb != null) { + // channels = sb.getChannelMap(); + channels = sb.getSequenceMap(); + } + int n=0; + //remove all channels from vertical box pane. + for (int i = 0; i < Math.min(PamConstants.MAX_CHANNELS, channelBoxes.length); i++) { + if ((channels & 1< getDataBlockBox() { return this.sourceList; - + + } + + + /** + * Get the channel validator for the source pane. + * @return the channel validator + */ + public PamValidator getChannelValidator() { + return channelValidator; } } diff --git a/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java b/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java index cf76d5a8..4aaf312e 100644 --- a/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java +++ b/src/pamViewFX/fxPlotPanes/PamHiddenSidePane.java @@ -186,36 +186,35 @@ public class PamHiddenSidePane extends PamStackPane { public HidingPane createHidingPane(Region displayPane, Node icon, Side side){ //create the hiding pane HidingPane hidingPane=new HidingPane(side, displayPane, this, true); - hidingPane.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + hidingPane.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); hidingPane.getStyleClass().add("pane-trans"); //the stack pane holds all the different settings panes this.getChildren().add(hidingPane); PamButton showButton=hidingPane.getShowButton(); - showButton.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + showButton.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + showButton.setPrefHeight(60); + //style show button showButton.setGraphic(icon); switch(side){ case LEFT: StackPane.setAlignment(hidingPane, Pos.TOP_LEFT); StackPane.setAlignment(showButton, Pos.TOP_LEFT); - showButton.getStyleClass().add("close-button-right-trans"); + showButton.getStyleClass().add("close-button-right"); break; case RIGHT: StackPane.setAlignment(hidingPane, Pos.TOP_RIGHT); StackPane.setAlignment(showButton, Pos.TOP_RIGHT); - showButton.getStyleClass().add("close-button-left-trans"); + showButton.getStyleClass().add("close-button-left"); break; default: 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. - showButton.translateYProperty().setValue(yPos); - showButton.prefHeightProperty().bind(hidingPane.getHideButton().heightProperty()); + //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/pamViewFX/fxPlotPanes/PlotPane.java b/src/pamViewFX/fxPlotPanes/PlotPane.java index da03a046..f85fc79b 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,65 +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(); - - 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"); + //allow hiding panes to be added + hiddenSidePane=new PamHiddenSidePane(); + hiddenSidePane.getChildren().add(canvas); + hiddenSidePane.toFront(); - //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); - setAxisVisible(true, true, true, true); + canvasHolder.setCenter(hiddenSidePane); + canvasHolder.setMinHeight(10); + canvasHolder.setMinWidth(10); + //canvasHolder.getStyleClass().add("pane-plot"); - -// 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 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); + + 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 @@ -229,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) @@ -256,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()); @@ -265,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); //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. @@ -292,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. @@ -312,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. @@ -335,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. @@ -343,7 +341,7 @@ public class PlotPane extends PamBorderPane { public PamAxisFX[] getAllAxis() { return axisArray; } - + /** * Get an axis pane * @param side the axis to get. @@ -351,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 @@ -368,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); @@ -379,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. @@ -454,12 +452,12 @@ public class PlotPane extends PamBorderPane { public PamAxisFX getyAxisRight() { return yAxisRight; } - - - - - - - + + + + + + + } diff --git a/src/pamViewFX/fxSettingsPanes/SettingsFileDialogFX.java b/src/pamViewFX/fxSettingsPanes/SettingsFileDialogFX.java index fba85899..03ed28b8 100644 --- a/src/pamViewFX/fxSettingsPanes/SettingsFileDialogFX.java +++ b/src/pamViewFX/fxSettingsPanes/SettingsFileDialogFX.java @@ -39,21 +39,27 @@ public class SettingsFileDialogFX { singleInstance = new SettingsFileDialogFX(); singleInstance.settingsDialog=new PamSettingsDialogFX(singleInstance.settingsFilePane); // singleInstance.settingsDialog.getDialogPane().getStylesheets().add(singleInstance.getClass().getResource("/Resources/css/pamSettingsCSS.css").toExternalForm()); - singleInstance.settingsDialog.getDialogPane().getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); + singleInstance.settingsDialog.getDialogPane().getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); } singleInstance.settingsFilePane.setParams(settingsFileData); singleInstance.settingsDialog.setOnShown((value)->{ + +// singleInstance.settingsFilePane.getProgressBar().setVisible(startup); +// singleInstance.settingsFilePane.getProgressBar().setProgress(-1);; + singleInstance.settingsFilePane.paneInitialized(); //fix to make sure the dialog appearsa in pre PG GUI FX insitialisation i.e. when selecting viewer database //on PG start up/. ((Stage) singleInstance.settingsDialog.getDialogPane().getScene().getWindow()).setAlwaysOnTop(true); }); + + Optional result=singleInstance.settingsDialog.showAndWait(); - System.out.println("Hello FX result: "); + //System.out.println("Hello FX result: "); if (result==null || !result.isPresent()) { if (startup) { @@ -62,6 +68,7 @@ public class SettingsFileDialogFX { } else return null; } + SettingsFileData settings=(SettingsFileData) result.get(); return settings; diff --git a/src/pamViewFX/fxSettingsPanes/SettingsFilePane.java b/src/pamViewFX/fxSettingsPanes/SettingsFilePane.java index c9c0d10b..5cd38990 100644 --- a/src/pamViewFX/fxSettingsPanes/SettingsFilePane.java +++ b/src/pamViewFX/fxSettingsPanes/SettingsFilePane.java @@ -6,6 +6,7 @@ import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; import javafx.scene.paint.Color; import javafx.stage.FileChooser; import javafx.stage.FileChooser.ExtensionFilter; @@ -43,6 +44,8 @@ public class SettingsFilePane extends SettingsPane { private PamBorderPane mainPane = new PamBorderPane(); + private ProgressBar progressBar; + public SettingsFilePane(){ super(null); createSettingsLoadPane(); @@ -99,7 +102,21 @@ public class SettingsFilePane extends SettingsPane { // vBox.getChildren().add(alwaysShow); mainPane.setCenter(vBox); + + progressBar = new ProgressBar(); + progressBar.setVisible(false); + + mainPane.setBottom(progressBar); + + } + + /** + * Get the progress bar. This is default set to not visible and can be used to show loading settings. + * @return the progress bar. + */ + public ProgressBar getProgressBar() { + return progressBar; } /** diff --git a/src/pamViewFX/fxStyles/PamAtlantaStyle.java b/src/pamViewFX/fxStyles/PamAtlantaStyle.java index d0c7182a..892558d9 100644 --- a/src/pamViewFX/fxStyles/PamAtlantaStyle.java +++ b/src/pamViewFX/fxStyles/PamAtlantaStyle.java @@ -1,5 +1,12 @@ package pamViewFX.fxStyles; +import java.util.ArrayList; + +import PamView.ColourScheme; +import PamView.PamColors; +import atlantafx.base.theme.PrimerDark; +import atlantafx.base.theme.PrimerLight; + /* * PAMGUARD - Passive Acoustic Monitoring GUARDianship. * To assist in the Detection Classification and Localisation @@ -23,46 +30,95 @@ package pamViewFX.fxStyles; */ /** - * Class defining the default CSS Style sheets to use for JavaFX displays. This class can be extended and one or more methods overridden to - * specify new CSS styles. Style sheets can be specified for 3 different categories: sliding dialogs, regular dialogs, and all other components - * (incl. displays, etc). In addition, each category can have a style sheet to use for daytime mode and one to use for nighttime mode. The - * relative URI paths to the individual style sheets are specified as private fields, and accessed through public methods. The day/night switch - * is based on the name of the current PamColors colour scheme being used. + * Class defining the default CSS Style sheets to use for JavaFX displays. This + * class can be extended and one or more methods overridden to specify new CSS + * styles. Style sheets can be specified for 3 different categories: sliding + * dialogs, regular dialogs, and all other components (incl. displays, etc). In + * addition, each category can have a style sheet to use for daytime mode and + * one to use for nighttime mode. The relative URI paths to the individual style + * sheets are specified as private fields, and accessed through public methods. + * The day/night switch is based on the name of the current PamColors colour + * scheme being used. * * @author Jamie Macaulay * */ public class PamAtlantaStyle extends PamDefaultStyle { - + /** - * Relative location of the CSS style sheet to be used for the Pamguard GUI (but not dialogs) + * Relative location of the CSS style sheet to be used for the Pamguard GUI (but + * not dialogs) */ - //private String guiCSS = "/Resources/css/pamCSS.css"; - //private String guiCSS = new NordDark().getUserAgentStylesheet(); - protected String primerGuiCSS = "/Resources/css/primer-light.css"; - - + // private String guiCSS = "/Resources/css/pamCSS.css"; + // private String guiCSS = new NordDark().getUserAgentStylesheet(); +// protected String primerlight = "/Resources/css/primer-light.css"; + protected String primerlight = new PrimerLight().getUserAgentStylesheet(); + /** - * Relative location of the CSS style sheet to be used for the Pamguard standard dialogs + * Relative location of the CSS style sheet to be used for the Pamguard standard + * dialogs */ - //private String dialogCSS = "/Resources/css/pamSettingsCSS.css"; - //private String dialogCSS = new PrimerDark().getUserAgentStylesheet(); - protected String primerDialogCSS = "/Resources/css/primer-dark.css"; + // private String dialogCSS = "/Resources/css/pamSettingsCSS.css"; + // private String dialogCSS = new PrimerDark().getUserAgentStylesheet(); +// protected String primerdark = "/Resources/css/primer-dark.css"; + protected String primerdark = new PrimerDark().getUserAgentStylesheet(); /** - * Relative location of the CSS style sheet to be used for the Pamguard sliding dialogs + * PAMGuard specific additions to the Primer CSS style including hifing pane, pop over, etc. */ - //private String slidingDialogCSS = "/Resources/css/pamCSS.css"; - //private String slidingDialogCSS = new PrimerDark().getUserAgentStylesheet(); - protected String primerSlidingDialogCSS = "/Resources/css/primer-pamguard.css"; + protected String primerPAMGuard = "/Resources/css/primer-pamguard.css"; + /** + * Changes the colours in primerPAMGuard to dark style + */ + protected String primerPAMGuardDark = "/Resources/css/primer-pamguard-dark.css"; + public PamAtlantaStyle() { - super.guiCSS = primerGuiCSS; - super.dialogCSS = primerDialogCSS; - super.slidingDialogCSS = primerSlidingDialogCSS; - + super.guiCSS = primerlight; + super.dialogCSS = primerdark; + super.slidingDialogCSS = primerPAMGuard; } + @Override + public ArrayList getGUICSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && guiCSSNightMode != null) { + cssStyles.add(getClass().getResource(primerdark).toExternalForm()); + } else { + cssStyles.add(getClass().getResource(primerlight).toExternalForm()); + } + cssStyles.add(getClass().getResource(primerPAMGuard).toExternalForm()); + return cssStyles; + } + + + @Override + public ArrayList getDialogCSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && dialogCSSNightMode != null) { + cssStyles.add(getClass().getResource(primerdark).toExternalForm()); + } else { + cssStyles.add(getClass().getResource(primerdark).toExternalForm()); + } + cssStyles.add(getClass().getResource(primerPAMGuard).toExternalForm()); + cssStyles.add(getClass().getResource(primerPAMGuardDark).toExternalForm()); + return cssStyles; + } + + @Override + public ArrayList getSlidingDialogCSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && slidingDialogCSSNightMode != null) { + cssStyles.add(getClass().getResource(primerPAMGuardDark).toExternalForm()); + } else { + cssStyles.add(getClass().getResource(primerPAMGuardDark).toExternalForm()); + } + return cssStyles; + } + } diff --git a/src/pamViewFX/fxStyles/PamDefaultStyle.java b/src/pamViewFX/fxStyles/PamDefaultStyle.java index d05fe023..279a870f 100644 --- a/src/pamViewFX/fxStyles/PamDefaultStyle.java +++ b/src/pamViewFX/fxStyles/PamDefaultStyle.java @@ -20,101 +20,137 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - - package pamViewFX.fxStyles; +import java.util.ArrayList; + import PamView.ColourScheme; import PamView.PamColors; /** - * Class defining the default CSS Style sheets to use for JavaFX displays. This class can be extended and one or more methods overridden to - * specify new CSS styles. Style sheets can be specified for 3 different categories: sliding dialogs, regular dialogs, and all other components - * (incl. displays, etc). In addition, each category can have a style sheet to use for daytime mode and one to use for nighttime mode. The - * relative URI paths to the individual style sheets are specified as private fields, and accessed through public methods. The day/night switch - * is based on the name of the current PamColors colour scheme being used. + * Class defining the default CSS Style sheets to use for JavaFX displays. This + * class can be extended and one or more methods overridden to specify new CSS + * styles. Style sheets can be specified for 3 different categories: sliding + * dialogs, regular dialogs, and all other components (incl. displays, etc). In + * addition, each category can have a style sheet to use for daytime mode and + * one to use for nighttime mode. The relative URI paths to the individual style + * sheets are specified as private fields, and accessed through public methods. + * The day/night switch is based on the name of the current PamColors colour + * scheme being used. * * @author mo55 + * @author Jamie Macaulay * */ public class PamDefaultStyle { - + /** - * Relative location of the CSS style sheet to be used for the Pamguard GUI (but not dialogs) + * Relative location of the CSS style sheet to be used for the Pamguard GUI (but + * not dialogs) */ protected String guiCSS = "/Resources/css/pamCSS.css"; - + /** - * Relative location of the CSS style sheet to be used for the Pamguard GUI when in night mode. If there - * is not a style sheet specific to night mode, set it to null or point back to the guiCSS field. + * Relative location of the CSS style sheet to be used for the Pamguard GUI when + * in night mode. If there is not a style sheet specific to night mode, set it + * to null or point back to the guiCSS field. */ protected String guiCSSNightMode = guiCSS; - + /** - * Relative location of the CSS style sheet to be used for the Pamguard standard dialogs + * Relative location of the CSS style sheet to be used for the Pamguard standard + * dialogs */ protected String dialogCSS = "/Resources/css/pamDefaultDialogCSS.css"; - + /** - * Relative location of the CSS style sheet to be used for the Pamguard std dialogs when in night mode. If there - * is not a style sheet specific to night mode, set it to null or point back to the dialogCSS field. + * Relative location of the CSS style sheet to be used for the Pamguard std + * dialogs when in night mode. If there is not a style sheet specific to night + * mode, set it to null or point back to the dialogCSS field. */ protected String dialogCSSNightMode = dialogCSS; - + /** - * Relative location of the CSS style sheet to be used for the Pamguard sliding dialogs + * Relative location of the CSS style sheet to be used for the Pamguard sliding + * dialogs */ protected String slidingDialogCSS = "/Resources/css/pamSettingsCSS.css"; - + /** - * Relative location of the CSS style sheet to be used for the Pamguard sliding dialogs when in night mode. If there - * is not a style sheet specific to night mode, set it to null or point back to the slidingDialogCSS field. + * Relative location of the CSS style sheet to be used for the Pamguard sliding + * dialogs when in night mode. If there is not a style sheet specific to night + * mode, set it to null or point back to the slidingDialogCSS field. */ protected String slidingDialogCSSNightMode = slidingDialogCSS; - + /** - *

Return the CSS Style sheet to be used for the Pamguard GUI (displays and such) but not the dialogs.

- *

If overriding this method, do not simply return a URI String. In order for the String to be in the - * proper format, the getClass.getResource... method should be used. + *

+ * Return the CSS Style sheet to be used for the Pamguard GUI (displays and + * such) but not the dialogs. + *

+ *

+ * If overriding this method, do not simply return a URI String. In order for + * the String to be in the proper format, the getClass.getResource... method + * should be used. * - * @return a String of the class containing the URI of the CSS style sheet to use + * @return an array of Strings of the class containing the URI of the CSS style + * sheet to use */ - public String getGUICSS() { - if (PamColors.getInstance().getColourScheme().getName()==ColourScheme.NIGHTSCHEME && guiCSSNightMode!=null) { - return getClass().getResource(guiCSSNightMode).toExternalForm(); + public ArrayList getGUICSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && guiCSSNightMode != null) { + cssStyles.add(getClass().getResource(guiCSSNightMode).toExternalForm()); } else { - return getClass().getResource(guiCSS).toExternalForm(); + cssStyles.add(getClass().getResource(guiCSS).toExternalForm()); } + return cssStyles; } - + /** - *

Return the CSS Style sheet to be used for the Pamguard settings dialogs.

- *

If overriding this method, do not simply return a URI String. In order for the String to be in the - * proper format, the getClass.getResource... method should be used. + *

+ * Return the CSS Style sheet to be used for the Pamguard settings dialogs. + *

+ *

+ * If overriding this method, do not simply return a URI String. In order for + * the String to be in the proper format, the getClass.getResource... method + * should be used. * - * @return a String of the class containing the URI of the CSS style sheet to use + * @return an array of Strings of the class containing the URI of the CSS style + * sheet to use */ - public String getDialogCSS() { - if (PamColors.getInstance().getColourScheme().getName()==ColourScheme.NIGHTSCHEME && dialogCSSNightMode!=null) { - return getClass().getResource(dialogCSSNightMode).toExternalForm(); + public ArrayList getDialogCSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && dialogCSSNightMode != null) { + cssStyles.add(getClass().getResource(dialogCSSNightMode).toExternalForm()); } else { - return getClass().getResource(dialogCSS).toExternalForm(); + cssStyles.add(getClass().getResource(dialogCSS).toExternalForm()); } + return cssStyles; } - + /** - *

Return the CSS Style sheet to be used for the Pamguard sliding dialogs.

- *

If overriding this method, do not simply return a URI String. In order for the String to be in the - * proper format, the getClass.getResource... method should be used. + *

+ * Return the CSS Style sheet to be used for the Pamguard sliding dialogs. + *

+ *

+ * If overriding this method, do not simply return a URI String. In order for + * the String to be in the proper format, the getClass.getResource... method + * should be used. * - * @return a String of the class containing the URI of the CSS style sheet to use + * @return an array of Strings of the class containing the URI of the CSS style + * sheet to use */ - public String getSlidingDialogCSS() { - if (PamColors.getInstance().getColourScheme().getName()==ColourScheme.NIGHTSCHEME && slidingDialogCSSNightMode!=null) { - return getClass().getResource(slidingDialogCSSNightMode).toExternalForm(); + public ArrayList getSlidingDialogCSS() { + ArrayList cssStyles = new ArrayList(); + if (PamColors.getInstance().getColourScheme().getName() == ColourScheme.NIGHTSCHEME + && slidingDialogCSSNightMode != null) { + cssStyles.add(getClass().getResource(slidingDialogCSSNightMode).toExternalForm()); } else { - return getClass().getResource(slidingDialogCSS).toExternalForm(); + cssStyles.add(getClass().getResource(slidingDialogCSS).toExternalForm()); } + return cssStyles; } } diff --git a/src/pamViewFX/pamTask/PamLoadingPane.java b/src/pamViewFX/pamTask/PamLoadingPane.java index 40860869..7b4badc2 100644 --- a/src/pamViewFX/pamTask/PamLoadingPane.java +++ b/src/pamViewFX/pamTask/PamLoadingPane.java @@ -51,7 +51,7 @@ public class PamLoadingPane extends PamVBox { /** * Time for loading before load pane showsn. */ - private long waitBeforeShiow=3000; // wait three seconds before a load + private long waitBeforeShiow=1000; // wait one seconds before a load public PamLoadingPane(PamGuiManagerFX pamGuiManager){ @@ -66,7 +66,7 @@ public class PamLoadingPane extends PamVBox { */ public void updateLoadPane(PamTaskUpdate taskUpdate){ -// System.out.println("New PAMTask Update: " + taskUpdate.getName() + " Progress: " + taskUpdate.getProgress() + " Status: "+ taskUpdate.getStatus()); +// System.out.println("New PAMTask Update: " + taskUpdate.getName() + " Progress: " + taskUpdate.getProgress() + " Status: "+ taskUpdate.getStatus() + " Progress: " + taskUpdate.getProgressString()); //first, figure out if there there is a PamTaskPane for the update; PamTaskPane pamTaskPane = null; 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); + + } + +} diff --git a/src/pamguard/PamguardFX.java b/src/pamguard/PamguardFX.java index a2df26bb..4ae7dbe5 100644 --- a/src/pamguard/PamguardFX.java +++ b/src/pamguard/PamguardFX.java @@ -61,11 +61,14 @@ public class PamguardFX extends Application { + private static int runMode; + @Override public void start(Stage primaryStage) throws Exception { //this is called from launch(args) try { - PamController.create(PamController.RUN_NORMAL, primaryStage); + System.out.println("javafx.runtime.version: " + System.getProperty("javafx.runtime.version")); + PamController.create(runMode, primaryStage); } catch (Exception e) { e.printStackTrace(); @@ -89,7 +92,7 @@ public class PamguardFX extends Application { Debug.setPrintDebug(false); // make sure the class instantiates static members. - int runMode = PamController.RUN_NORMAL; + runMode = PamController.RUN_NORMAL; String InputPsf = "NULL"; @@ -273,6 +276,7 @@ public class PamguardFX extends Application { //that kicked off from with the EDT CJB 2009-06-16 PamGUIManager.setType(PamGUIManager.FX); + launch(args); } diff --git a/src/rawDeepLearningClassifier/DLControl.java b/src/rawDeepLearningClassifier/DLControl.java index 94bb056c..4b7d3713 100644 --- a/src/rawDeepLearningClassifier/DLControl.java +++ b/src/rawDeepLearningClassifier/DLControl.java @@ -27,17 +27,22 @@ import pamViewFX.fxNodes.pamDialogFX.PamDialogFX2AWT; import rawDeepLearningClassifier.dataPlotFX.DLDetectionPlotProvider; import rawDeepLearningClassifier.dataPlotFX.DLPredictionProvider; import rawDeepLearningClassifier.ddPlotFX.RawDLDDPlotProvider; +import rawDeepLearningClassifier.defaultModels.DLDefaultModelManager; import rawDeepLearningClassifier.dlClassification.DLClassName; import rawDeepLearningClassifier.dlClassification.DLClassNameManager; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.DLClassifierChooser; import rawDeepLearningClassifier.dlClassification.DLClassifyProcess; import rawDeepLearningClassifier.dlClassification.animalSpot.SoundSpotClassifier; +import rawDeepLearningClassifier.dlClassification.archiveModel.PamZipModelClassifier; +import rawDeepLearningClassifier.dlClassification.delphinID.DelphinIDClassifier; import rawDeepLearningClassifier.dlClassification.genericModel.GenericDLClassifier; -import rawDeepLearningClassifier.dlClassification.ketos.KetosClassifier; +import rawDeepLearningClassifier.dlClassification.ketos.KetosClassifier2; +import rawDeepLearningClassifier.dlClassification.koogu.KooguClassifier; import rawDeepLearningClassifier.layoutFX.DLSidePanelSwing; import rawDeepLearningClassifier.layoutFX.DLSymbolManager; import rawDeepLearningClassifier.layoutFX.PredictionSymbolManager; -import rawDeepLearningClassifier.layoutFX.RawDLSettingsPane; +import rawDeepLearningClassifier.layoutFX.DLSettingsPane; import rawDeepLearningClassifier.logging.DLAnnotationType; import rawDeepLearningClassifier.logging.DLDataUnitDatagram; import rawDeepLearningClassifier.logging.DLDetectionBinarySource; @@ -76,7 +81,7 @@ import rawDeepLearningClassifier.segmenter.SegmenterProcess; * AnimalSpot is a framework for training acoustic deep learning * models using Pytorch. Users can load a .py model which contains embedded * metadata so that PMAGuard knows the exact transforms required for the model - * input. This makes deployin models in PAMGuard very easy - users require little + * input. This makes deploying models in PAMGuard very easy - users require little * or no experience to get this working. *

*

  • Ketos
  • @@ -117,11 +122,12 @@ public class DLControl extends PamControlledUnit implements PamSettings { * List of different deep learning models that are available. */ private ArrayList dlModels = new ArrayList(); + /** * The settings pane. */ - private RawDLSettingsPane settingsPane; + private DLSettingsPane settingsPane; /** * The settings dialog @@ -179,7 +185,24 @@ public class DLControl extends PamControlledUnit implements PamSettings { * The current data selector. */ private DataSelector dataSelector; + + /** + * Figure out which model type has been imported. + */ + private DLClassifierChooser dlClassifierChooser; + /** + * Handles downloading models from the internet + */ + private DLDownloadManager modelDownloadManager; + + + /** + * Handles downloading models from the internet + */ + private DLDefaultModelManager defaultModelManager; + + /** * Constructor for the DL Control. * @@ -191,6 +214,7 @@ public class DLControl extends PamControlledUnit implements PamSettings { PamRawDataBlock rawDataBlock = PamController.getInstance() .getRawDataBlock(rawDLParmas.groupedSourceParams.getDataSource()); + /** * In the latest release of djl (0.11.0) there is a bug with the dll's of tensorflow and * pytorch. If tensorflow is loaded before pytorch there is a conglict in dll's and @@ -204,8 +228,17 @@ 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); + + //manages default models + defaultModelManager = new DLDefaultModelManager(this); + + //manages downloading models + modelDownloadManager = new DLDownloadManager(); + // add storage options etc. dlBinaryDataSource = new DLResultBinarySource(dlClassifyProcess); @@ -223,9 +256,16 @@ public class DLControl extends PamControlledUnit implements PamSettings { /***** Add new deep learning models here ****/ - dlModels.add(new GenericDLClassifier(this)); dlModels.add(new SoundSpotClassifier(this)); - dlModels.add(new KetosClassifier(this)); + dlModels.add(new KetosClassifier2(this)); + dlModels.add(new KooguClassifier(this)); + dlModels.add(new PamZipModelClassifier(this)); + dlModels.add(new DelphinIDClassifier(this)); + + //it is important the Generic Model is last because we need to check + //for PG metadata in all other models before resorting to manually + //setting up a model. + dlModels.add(new GenericDLClassifier(this)); // dlModels.add(new DummyClassifier()); // dlModels.add(new OrcaSpotClassifier(this)); //removed soon. @@ -239,7 +279,7 @@ public class DLControl extends PamControlledUnit implements PamSettings { TDDataProviderRegisterFX.getInstance() .registerDataInfo(new DLDetectionPlotProvider(this, dlClassifyProcess.getDLDetectionDatablock())); TDDataProviderRegisterFX.getInstance() - .registerDataInfo(new DLPredictionProvider(this, dlClassifyProcess.getDLDetectionDatablock())); + .registerDataInfo(new DLPredictionProvider(this, dlClassifyProcess.getDLPredictionDataBlock())); // register the DD display DDPlotRegister.getInstance() @@ -251,7 +291,10 @@ public class DLControl extends PamControlledUnit implements PamSettings { // serialized if (rawDLParmas.classNameMap == null) rawDLParmas.classNameMap = new ArrayList(); - + + //create the classiifer chooser. + dlClassifierChooser = new DLClassifierChooser(this); + // ensure everything is updated. updateParams(rawDLParmas); } @@ -264,6 +307,22 @@ public class DLControl extends PamControlledUnit implements PamSettings { public ArrayList getDLModels() { return dlModels; } + + + /** + * Get a model by it's name. + * @param the name the model. + * @return the corresponding model object or null if no model with the name exists. + */ + public DLClassiferModel getDLModel(String string) { + for (int i=0; i< this.dlModels.size(); i++) { + if (dlModels.get(i).getName().equals(string)) { + return dlModels.get(i); + } + } + return null; + } + /** * Get the current deep learning model. @@ -271,7 +330,12 @@ public class DLControl extends PamControlledUnit implements PamSettings { * @return the current deep learning model. */ public DLClassiferModel getDLModel() { - return dlModels.get(rawDLParmas.modelSelection); + if (this.rawDLParmas.modelSelection<0 || this.rawDLParmas.modelSelection>=dlModels.size()) { + return null; + } + else { + return dlModels.get(this.rawDLParmas.modelSelection); + } } /** @@ -284,6 +348,7 @@ public class DLControl extends PamControlledUnit implements PamSettings { this.segmenterProcess.setupSegmenter(); this.dlClassifyProcess.setupProcess(); + this.checkModelParams(); // this is a bit of a hack. Annotations are added to data units but the // datablock knows nothing about them @@ -342,11 +407,12 @@ public class DLControl extends PamControlledUnit implements PamSettings { * * @return the settings pane. */ - public RawDLSettingsPane getSettingsPane() { + public DLSettingsPane getSettingsPane() { if (this.settingsPane == null) { - settingsPane = new RawDLSettingsPane(this); + settingsPane = new DLSettingsPane(this); } + return settingsPane; } @@ -449,6 +515,34 @@ public class DLControl extends PamControlledUnit implements PamSettings { public void setParams(RawDLParams newParams) { this.rawDLParmas = newParams; + checkModelParams(); + } + + /** + * Called when setParams is called, which should have new model params + * after the dialog was closed. Puts these into dlParams so they get serialised + * with rest of XML. + */ + public void checkModelParams() { + RawDLParams dlParams = getDLParams(); + DLClassiferModel model = getDLModel(); + Serializable modelParams = null; + if (model != null) { + modelParams = model.getDLModelSettings(); + } + dlParams.setModelParameters(modelParams); + + // see what else we can find in the model in terms of metadata. +// if (model == null) { +// return; +// } +// try { +// String modelName = model.getName(); +// System.out.println("Model name: " + modelName); +// } +// catch (Exception e) { +// e.printStackTrace(); +// } } /** @@ -526,6 +620,31 @@ public class DLControl extends PamControlledUnit implements PamSettings { } } } + + /** + * Get the classifier chooser. + * @return the classifier chooser.Take it + */ + public DLClassifierChooser getDlClassifierChooser() { + return dlClassifierChooser; + } + + /** + * Get the download manager for downloading models offline. + * @return the download manager. + */ + public DLDownloadManager getDownloadManager() { + return modelDownloadManager; + } + + /** + * Get the default model manager. This handles the default models that can be downloaded. + * @return the default model manager. + */ + public DLDefaultModelManager getDefaultModelManager() { + return this.defaultModelManager; + } + } diff --git a/src/rawDeepLearningClassifier/DLControlGUI.java b/src/rawDeepLearningClassifier/DLControlGUI.java index 4b4f3145..fdb36c58 100644 --- a/src/rawDeepLearningClassifier/DLControlGUI.java +++ b/src/rawDeepLearningClassifier/DLControlGUI.java @@ -12,6 +12,8 @@ import pamViewFX.PamControlledGUIFX; */ public class DLControlGUI extends PamControlledGUIFX { + + /** * The dl control. */ diff --git a/src/rawDeepLearningClassifier/DLDownloadListener.java b/src/rawDeepLearningClassifier/DLDownloadListener.java new file mode 100644 index 00000000..3f3ffdf1 --- /dev/null +++ b/src/rawDeepLearningClassifier/DLDownloadListener.java @@ -0,0 +1,15 @@ +package rawDeepLearningClassifier; + +/** + * Listener for downloading files. + */ +public interface DLDownloadListener { + + + /** + * Updates the number of bytes downloaded + * @param bytesDownlaoded - the number of bytes downloaded so far. + */ + public void update(DLStatus status, long bytesDownlaoded); + +} diff --git a/src/rawDeepLearningClassifier/DLDownloadManager.java b/src/rawDeepLearningClassifier/DLDownloadManager.java new file mode 100644 index 00000000..507fd8f4 --- /dev/null +++ b/src/rawDeepLearningClassifier/DLDownloadManager.java @@ -0,0 +1,434 @@ +package rawDeepLearningClassifier; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.io.FilenameUtils; + +import ai.djl.util.ZipUtils; +import rawDeepLearningClassifier.defaultModels.RightWhaleModel1; + +/** + * Manages the downloading and unzipping of models. + */ +public class DLDownloadManager { + + public static String[] defaultModels = new String[] {"saved_model.pb"}; + + /** + * Listens for downlaods. + */ + private ArrayList downLoadListeners = new ArrayList(); + + + + + public DLDownloadManager() { + + } + + /** + * Notify the download listeners of a change. + * @param status - notify the listeners. + * @param downloadedBytes - the number of downloaded bytes. + */ + private void notifyDownLoadListeners(DLStatus status, long downloadedBytes) { + for (DLDownloadListener listener : downLoadListeners) { + listener.update(status, downloadedBytes); + } + } + + /** + * Check whether has been downloaded already to a default local folder? + * @param modelURI - the URL of the model + * @return + * @return true if the model has been downloaded. + */ + public boolean isModelDownloaded(URL modelURL) { + return isModelDownloaded(modelURL, getModelName(modelURL)); + } + + + /** + * Check whether has been downloaded already? + * @param modelURI - the URL of the model + * @param modelName - the model name that has been used to name the temporary folder. + * @return true if the model has been downloaded. + */ + public boolean isModelDownloaded(URL modelURL, String modelName) { + return getModelDownloadedFile( modelURL, modelName).exists(); + } + + /** + * Get the path to the model if it has been downloaded. + * @param modelURL - the URL to the model + * @param modelName - the model name. Use getModelName(modelURL) to use the default model name. + * @return a file object - may not exist. + */ + public File getModelDownloadedFile(URL modelURL, String modelName) { + //get the name of the file + String fileName = FilenameUtils.getName(modelURL.toString()); + + return new File(getModelFolder(modelName) + File.separator + fileName); + } + + + /** + * Get the path to a model. If the URI is a URL then the model is download to a local folder and the path to + * the local folder is returned. + * @param model - the model to load. + * @param modelName - the name of the model - this is used to create the local folder name if the model is downloaded. + * @return the path to the model The model might be a zip file, py file, koogu file. + */ + public URI downloadModel(URI modelURI) { + String modelName = getModelName(modelURI); + return downloadModel( modelURI, modelName); + } + + /** + * Get a model name based on it's filename + * @param modelURI - URI to the model + * @return the model name. + */ + private String getModelName(URI modelURI) { + return FilenameUtils.getBaseName(modelURI.getPath()); + } + + /** + * Get a model name based on it's filename + * @param modelURL - URL to the model + * @return the model name. + */ + private String getModelName(URL modelURI) { + return FilenameUtils.getBaseName(modelURI.getPath()); + } + + + /** + * Get the path to a model. If the URI is a URL then the model is download to a local folder and the path to + * the local folder is returned. + * @param model - the model to load. + * @param modelName - the name of the model - this is used to create the local folder name if the model is downloaded. + * @return the path to the model The model might be a zip file, py file, .kgu file. + */ + public URI downloadModel(URI modelURI, String modelName) { + + if ("file".equalsIgnoreCase(modelURI.getScheme())) { + // It's a file path + return modelURI; //the model is already a path. + + } else { + // It's a URL + try { + System.out.println("Download: " + modelURI.toURL().getPath()); + String folder = getModelFolder(modelName); + if (folder==null) { + System.err.println("DLDefaultModelManager.loadModel: Unable to make model folder: "); + } + try { + + HttpURLConnection huc = (HttpURLConnection) modelURI.toURL().openConnection(); + + int responseCode = huc.getResponseCode(); + + if (HttpURLConnection.HTTP_OK == responseCode) { + notifyDownLoadListeners(DLStatus.CONNECTION_TO_URL, -1); + File file = downLoadModel(modelURI.toURL(), getModelFolder(modelName) ); + + System.out.println("DOWNLOADED MODEL: " + file.getPath() ); + + notifyDownLoadListeners(DLStatus.DECOMPRESSING_MODEL, -1); + + file = decompressFile(file); + + if (file.isDirectory()) { + //the file has been decompressed and need to search for model path within this... + file = findModelFile(file, defaultModels); + + System.out.println("DECOMPRESSED MODEL: " + file ); + + if (file == null) return null; + } + + System.out.println("FINAL MODEL: " + file ); + + + return file.toURI(); + } + else { + notifyDownLoadListeners(DLStatus.NO_CONNECTION_TO_URL, -1); + return null; + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + + } + + private static boolean isArchive(File f) { + int fileSignature = 0; + try (RandomAccessFile raf = new RandomAccessFile(f, "r")) { + fileSignature = raf.readInt(); + } catch (IOException e) { + // handle if you like + } + return fileSignature == 0x504B0304 || fileSignature == 0x504B0506 || fileSignature == 0x504B0708; + } + + + /** + * Find the model file within + * @param folder + * @return + */ + private File findModelFile(File folder, String[] defaultModelNames) { + + + Path startDir = folder.toPath(); // replace with your directory path + + try { + List filesList = Files.walk(startDir) + .filter(Files::isRegularFile) + .collect(Collectors.toList()); + + filesList.forEach(System.out::println); + + for (String modelName:defaultModelNames) { + String name; + //now search through the list + for (Path modelFile:filesList) { + name = modelFile.getFileName().toString(); + if (name.equals(modelName)) { + return modelFile.toFile(); + } + + } + } + return null; + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + + } + + /** + * Once a file has been downloaded, it may need to be decompressed. + * @param file - the file. + * @return the path to the decompressed folder or the path to the original file if not a zip file. + */ + private File decompressFile(File file) { + + if (FilenameUtils.getExtension(file.getPath()).equals("zip")) { + + FileInputStream fileInputStream; + try { + fileInputStream = new FileInputStream(file); + + String fileNameWithOutExt = FilenameUtils.removeExtension(file.getPath()); + + File zipFolder = new File(fileNameWithOutExt); + + //Creating the directory + boolean bool = zipFolder.mkdir(); + + //unzip the model into the temporary directory.... + + ZipUtils.unzip(fileInputStream, Paths.get(zipFolder.toURI())); + + //now that file has been unzipped search for a valid model file. + + return zipFolder; + + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + } + else { + return file; + } + + } + + + private static boolean isRedirected( Map> header ) { + for( String hv : header.get( null )) { + if( hv.contains( " 301 " ) + || hv.contains( " 302 " )) return true; + } + return false; + } + + + /** + * Get the model folder - create it if it doesn't exist. + * @return file object for new folder + */ + static public String getModelFolder() { + String settingsFolder = System.getProperty("user.home"); + settingsFolder += File.separator + "Pamguard_deep_learning"; + // now check that folder exists + File folder = makeFolder( settingsFolder); + if (folder==null) return null; + return folder.getPath(); + } + + /** + * Get the model folder - create it if it doesn't exist. + * @param modelname - the name of the model - this is used to create the local folder name. + * @return file object for new folder + */ + static public String getModelFolder(String modelname) { + String folder = getModelFolder(); + if (folder == null) return null; + // now check that folder exists + File dlFolder = makeFolder( folder + File.separator + modelname); + if (dlFolder==null) return null; + return dlFolder.getPath(); + } + + /** + * Make a settings folder. + * @param settingsFolder + * @return + */ + private static File makeFolder(String settingsFolder) { + File folder = new File(settingsFolder); + if (folder.exists() == false) { + folder.mkdirs(); + if (folder.exists() == false) { + return null; + } + } + return folder; + } + + + + + /** + * Download a model to the default file location. + * @param link - the link to download from + * @param outFolder - the folder in which to save the file. + * @return the model file output. + * @throws IOException - exception if something goes wrong. + */ + private File downLoadModel(URL url, String outFolder) throws IOException { + + String link = url.toString(); + + //get the name of the file + String fileName = FilenameUtils.getName(link); + + File outFile = new File( outFolder + File.separator + fileName); + + HttpURLConnection http = (HttpURLConnection)url.openConnection(); + Map< String, List< String >> header = http.getHeaderFields(); + + //handle any redorects - e.g. from GitHub + while( isRedirected( header )) { + link = header.get( "Location" ).get( 0 ); + url = new URL( link ); + http = (HttpURLConnection)url.openConnection(); + header = http.getHeaderFields(); + } + + + notifyDownLoadListeners(DLStatus.DOWNLOAD_STARTING, 0); + + InputStream input = http.getInputStream(); + byte[] buffer = new byte[512]; //download in 4kB chunks + int n = -1; + OutputStream output = new FileOutputStream(outFile); + long count = 0; + + + //Files.copy(input, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + while ((n = input.read(buffer)) != -1) { +// System.out.println("Chunk: " + count); + count=count+n; //total bytes + notifyDownLoadListeners(DLStatus.DOWNLOADING, count); + output.write( buffer, 0, n ); + } + +// System.out.println("Chunk: " + n); + + output.close(); + + notifyDownLoadListeners(DLStatus.DOWNLOAD_FINISHED, count); + + + return outFile; + } + + /** + * Get the number of download listeners. + * @return the number of download listeners. + */ + public int getNumDownloadListeners() { + return downLoadListeners.size(); + } + + /** + * Add a download listener to the array manager. + * @param e - the listener to add. + * @return true if the listener was added successfully. + */ + public boolean addDownloadListener(DLDownloadListener e) { + return downLoadListeners.add(e); + } + + /** + * Remove a download listener to the array manager. + * @param e - the listener to remove. + * @return true if the listener was removed successfully. + */ + public boolean removeDownladListener(Object o) { + return downLoadListeners.remove(o); + } + + + public static void main(String[] args) { + DLDownloadManager dlDefaultModelManager = new DLDownloadManager(); + System.out.println("Test downloading a model: "); + + URI path = dlDefaultModelManager.downloadModel(new RightWhaleModel1().getModelURI(), new RightWhaleModel1().getModelName()); + } + + public void clearDownloadListeners() { + this.downLoadListeners.clear(); + + } + +} diff --git a/src/rawDeepLearningClassifier/DLStatus.java b/src/rawDeepLearningClassifier/DLStatus.java new file mode 100644 index 00000000..4ab61429 --- /dev/null +++ b/src/rawDeepLearningClassifier/DLStatus.java @@ -0,0 +1,119 @@ +package rawDeepLearningClassifier; + +/** + * Status reporting for the deep learning module. + */ +public enum DLStatus { + + FILE_NULL("The input file is null", + "The loaded file was null. If the file was download it may not have downloaded properly.", ErrorLevel.ERROR), + + MODEL_LOAD_FAILED("The model failed to load", + " The model failed to load - this could be because it is incompatible with PAMGuard or an uknown file format.", + ErrorLevel.ERROR), + + MODEL_LOAD_SUCCESS("The model loaded", " The model successfully loaded", ErrorLevel.NO_ERROR), + + DOWNLOAD_STARTING("Download starting", "The model is downloading", ErrorLevel.NO_ERROR), + + DOWNLOAD_FINISHED("Download finished", " The model successfully downloaded", ErrorLevel.NO_ERROR), + + DOWNLOADING("Downloading", "The model is currently downloading", ErrorLevel.NO_ERROR), + + NO_CONNECTION_TO_URL("Could not connect to the URL", + " Could connect to the URL - the URL may be wrong or there may be no internet connection", ErrorLevel.ERROR), + + CONNECTION_TO_URL("Connected to URL", "The connection to the URL was successful", ErrorLevel.NO_ERROR), + + MODEL_DOWNLOAD_FAILED("The model was not downloaded", + " The model download failed. Check internet connection and try again", ErrorLevel.ERROR), + + NO_MODEL_LOADED("There is no loaded model", + "There is no loaded model. A model my have failed to load or the path/URL to the model is incorrect", ErrorLevel.WARNING), + + //this is not a show stopper because predictions are saved there's just no binary classification. + NO_BINARY_CLASSIFICATION("No binary classification", + " There are no prediction classes selected for classification. Predicitons for each segment will be saved but there will be no detections generated", + ErrorLevel.WARNING), + + DECOMPRESSING_MODEL("Decompressing model", "Decompressing the model file", ErrorLevel.NO_ERROR), + + INCOMPATIBLE_ZIP("Incorrect Zip format", "The zip format is incorrect. The zip file should have a *.pgdl file in the parent directory along with either a Tensorflow or PyTorch model.", ErrorLevel.ERROR), ; + + + /** + * True of the message is an error message. + */ + private int isError = ErrorLevel.NO_ERROR; + + /** + * Check whether the message is an error message. + * + * @return true if an error message. + */ + public boolean isError() { + return isError==ErrorLevel.ERROR; + } + + /** + * The name of the status + */ + private String string; + + /** + * Description of the status + */ + private String expanded; + + /** + * Status report for the leep learning module. + * + * @param string - status + * @param expanded - description of the status. + */ + DLStatus(String string, String expande, int isError) { + this.string = string; + this.expanded = expande; + this.isError = isError; + } + + /** + * Get a brief description of the status. + * + * @return a brief description of the status. + */ + public String getName() { + return string; + } + + /** + * Longer description of the status. + * + * @return longer descrption of the status. + */ + public String getDescription() { + return expanded; + } + + private static class ErrorLevel { + public static final int NO_ERROR = 0; + + public static final int WARNING = 1; + + public static final int ERROR = 2; + } + + /** + * Get the error flag. e.g. ErrorLevel.NO_ERROR. The error flag describes + * whether the status is an error, warning or not an error. + * @return the error flag. + */ + int getErrorFlag() { + return isError; + } + + public boolean isWarning() { + return isError==ErrorLevel.WARNING; + } + +} diff --git a/src/rawDeepLearningClassifier/DLZipUtils.java b/src/rawDeepLearningClassifier/DLZipUtils.java new file mode 100644 index 00000000..808ab663 --- /dev/null +++ b/src/rawDeepLearningClassifier/DLZipUtils.java @@ -0,0 +1,106 @@ +package rawDeepLearningClassifier; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + + +public class DLZipUtils { + + /** + * Find the first file within a zip folder that matches a pattern. This peaks into the zip file instead of decompressing it. + * @param zipUri - uri to the zip file + * @param filePattern - the file pattern to match - the file must contain this string. + * @return null if no file found and the file pqth if the file is founf + * @throws ZipException + * @throws IOException + */ + public static String getZipFilePath(URI zipUri, String filePattern) throws ZipException, IOException { + return getZipFilePath(new File(zipUri), filePattern); + } + + + /** + * Extract a single file from a zip folder. + * @param zipPackage - URI to the the zip file + * @param fileToBeExtracted - the name of the file to be extracted + * @param - the folder path to extract the file to. + * @return - return the File that has been extracted. + * @throws IOException + * @throws URISyntaxException + */ + public static File extractFile(String zipPackage, String fileToBeExtracted, String outFolder) throws IOException, URISyntaxException { + return extractFile( new URI(zipPackage), fileToBeExtracted, outFolder) ; + } + + + /** + * Extract a single file from a zip folder. + * @param zipPackage - path the the zip file + * @param fileToBeExtracted - the name of the file to be extracted + * @param - the folder path to extract the file to. + * @return - return the File that has been extracted. + * @throws IOException + */ + public static File extractFile(URI zipPackage, String fileToBeExtracted, String outFolder) throws IOException { + File fileOut = new File(outFolder, fileToBeExtracted); + OutputStream out = new FileOutputStream(fileOut); + FileInputStream fileInputStream = new FileInputStream(new File(zipPackage)); + BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream ); + ZipInputStream zin = new ZipInputStream(bufferedInputStream); + ZipEntry ze = null; + while ((ze = zin.getNextEntry()) != null) { + if (ze.getName().equals(fileToBeExtracted)) { + byte[] buffer = new byte[9000]; + int len; + while ((len = zin.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.close(); + break; + } + } + zin.close(); + + return fileOut; + } + + + /** + * Find the first file within a zip folder that matches a pattern. + * @param zipFile - uri to the zip file + * @param filePattern - the file pattern to match - the file must contain this string. + * @return null if no file found and the file pqth if the file is founf + * @throws ZipException + * @throws IOException + */ + public static String getZipFilePath(File zipFileIn, String filePattern) throws ZipException, IOException { + + try (ZipFile zipFile = new ZipFile(zipFileIn)) { + Enumeration entries = zipFile.entries(); + //this iterates through all files, including in sub folders. + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + // Check if entry is a directory + if (!entry.isDirectory()) { + //System.out.println(entry); + if (entry.getName().contains(filePattern)) { + return entry.getName(); + } + } + } + } + return null; + } + +} diff --git a/src/rawDeepLearningClassifier/RawDLParams.java b/src/rawDeepLearningClassifier/RawDLParams.java index 4f84ad52..abb21daf 100644 --- a/src/rawDeepLearningClassifier/RawDLParams.java +++ b/src/rawDeepLearningClassifier/RawDLParams.java @@ -1,6 +1,7 @@ package rawDeepLearningClassifier; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; import PamView.GroupedSourceParameters; @@ -21,8 +22,16 @@ public class RawDLParams implements Serializable, Cloneable { /** * The currently selected Deep Learning model. + * (Models are now automatically selected). + * -1 means no model selected. */ - public int modelSelection = 0; + public int modelSelection = -1; + + /** + * The current model URI. The deep learning model must have some sort of external file to run. + * This might be a model, a .exe file etc. + */ + public URI modelURI; /** * Holds channel and grouping information @@ -76,7 +85,7 @@ public class RawDLParams implements Serializable, Cloneable { * different class names. If we change model then the class names may change. * Previously annotated data will then be messed up. But, in a giant dataset * that may be an issue. Perhaps users wish to run a new model on some chunk of - * data without messing up all the other classified detection which have used + * data without messing up all the other classified detectionS which have used * that module. So store the data in binary files? That is super inefficient as * the same string is stored many times. So instead store a short which * identifies the string that sits in this table. Everytime a new model is added @@ -91,6 +100,8 @@ public class RawDLParams implements Serializable, Cloneable { */ public short classNameIndex = 0; + private Serializable modelParameters; + @Override public RawDLParams clone() { RawDLParams newParams = null; @@ -109,4 +120,22 @@ public class RawDLParams implements Serializable, Cloneable { return newParams; } + /** + * Set the model parameters. These aren't really needed in here are aren't really + * used except when the parameters are serialized to XML for book keeping. + * @param modelParameters + */ + public Serializable getModelParameters() { + return modelParameters; + } + + /** + * Set the model parameters. These aren't really needed in here are aren't really + * used except when the parameters are serialized to XML for book keeping. + * @param modelParameters the modelParameters to set + */ + public void setModelParameters(Serializable modelParameters) { + this.modelParameters = modelParameters; + } + } diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLDetectionPlotInfoFX.java b/src/rawDeepLearningClassifier/dataPlotFX/DLDetectionPlotInfoFX.java index b1fef763..a2282812 100644 --- a/src/rawDeepLearningClassifier/dataPlotFX/DLDetectionPlotInfoFX.java +++ b/src/rawDeepLearningClassifier/dataPlotFX/DLDetectionPlotInfoFX.java @@ -47,7 +47,7 @@ public class DLDetectionPlotInfoFX extends RawClipDataInfo { try { PamSVGIcon iconMaker= new PamSVGIcon(); //PamSVGIcon svgsprite = iconMaker.create(new File(getClass().getResource(resourcePath).toURI()), Color.WHITE); - PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), Color.WHITE); + PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), Color.WHITE,1); // svgsprite.getSpriteNode().setStyle("-fx-text-color: white"); // svgsprite.getSpriteNode().setStyle("-fx-fill: white"); diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPane.java index 41d26118..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); } @@ -184,7 +186,7 @@ public class DLPredictionPane extends PamBorderPane implements TDSettingsPane { try { PamSVGIcon iconMaker= new PamSVGIcon(); //PamSVGIcon svgsprite = iconMaker.create(new File(getClass().getResource(resourcePath).toURI()), Color.WHITE); - PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), Color.DODGERBLUE); + PamSVGIcon svgsprite = iconMaker.create(getClass().getResource(resourcePath).toURI().toURL(), Color.DODGERBLUE, 1); // svgsprite.getSpriteNode().setStyle("-fx-text-color: white"); // svgsprite.getSpriteNode().setStyle("-fx-fill: white"); diff --git a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java index 945561b5..d5ab0f7f 100644 --- a/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java +++ b/src/rawDeepLearningClassifier/dataPlotFX/DLPredictionPlotInfoFX.java @@ -3,12 +3,14 @@ package rawDeepLearningClassifier.dataPlotFX; import java.io.Serializable; +import PamController.PamController; import PamUtils.PamArrayUtils; import PamView.GeneralProjector; import PamView.GeneralProjector.ParameterType; import PamView.GeneralProjector.ParameterUnits; import PamView.symbol.PamSymbolChooser; import PamView.symbol.PamSymbolManager; +import PamguardMVC.PamConstants; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import dataPlotsFX.TDManagedSymbolChooserFX; @@ -18,6 +20,7 @@ import dataPlotsFX.data.generic.GenericLinePlotInfo; import dataPlotsFX.data.generic.GenericScaleInfo; import dataPlotsFX.layout.TDGraphFX; import dataPlotsFX.projector.TDProjectorFX; +import javafx.geometry.Point2D; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; @@ -81,19 +84,31 @@ public class DLPredictionPlotInfoFX extends GenericLinePlotInfo { frequencyInfo = new GenericScaleInfo(0, 1, ParameterType.FREQUENCY, ParameterUnits.HZ); - DLClassName[] classNames = getDlControl().getDLModel().getClassNames(); - - //make sure this is initialised otherwise the plot won't work when first created. - if (dlPredParams.lineInfos==null ) dlPredParams.lineInfos = new LineInfo[classNames.length]; - for (int i=0; i - +

    _A diagram of how the deep learning module works in PAMGuard. An input waveform is segmented into chunks. A series of transforms are applied to each chunk creating the input for the deep learning model. The transformed chunks are sent to the model. The results from the model are saved and can be viewed in real time (e.g. mitigation) or in post processing (e.g. data from SoundTraps)._ ### Generic Model + A generic model allows a user to load any model compatible with the [djl](https://djl.ai/) (PyTorch (JIT), Tenserflow, ONXX) library and then manually set up a series of transforms using PAMGuard's transform library. It is recommended that users use an existing framework instead of a generic model as these models will automatically generate the required transforms. ### AnimalSpot + [ANIMAL-SPOT](https://github.com/ChristianBergler/ANIMAL-SPOT) is a deep learning based framework which was initially designed for [killer whale sound detection]((https://github.com/ChristianBergler/ORCA-SPOT)) in noise heavy underwater recordings (see [Bergler et al. (2019)](https://www.nature.com/articles/s41598-019-47335-w)). It has now been expanded to a be species independent framework for training acoustic deep learning models using [PyTorch]( https://pytorch.org/) and Python. Imported AnimalSpot models will automatically set up their own data transforms and output classes. ### Ketos -[Ketos](https://meridian.cs.dal.ca/2015/04/12/ketos/) is an acoustic deep learning framework based on Tensorflow and developed by [MERIDIAN]( https://meridian.cs.dal.ca/). It has excellent resources and tutorials and Python libraries can be installed easily via pip. Imported Ketos model will automatically set up their own data transforms and output classes. + +[Ketos](https://meridian.cs.dal.ca/2015/04/12/ketos/) is an acoustic deep learning framework based on Tensorflow and developed by [MERIDIAN]( https://meridian.cs.dal.ca/). It has excellent resources and tutorials and Python libraries can be installed easily via pip. Imported Ketos (.ktpb) models will automatically set up their own data transforms and output classes. + +### Koogu + +[Koogu ](https://shyamblast.github.io/Koogu/en/stable/) is a Python package which allows users to train a deep learning model. Koogu helps users by integrating with some frequency used annotation programs and provides tools to train and test classifiers. Imported Koogu models (.kgu) will automatically set up their own data transforms and output classes. + +### PAMGuardZip + +PAMGuard zip models consist of a deep learning model (either a Tensorflow saved_model.pb or PyTorch **.py model) alongside a PAMGuard metdata file (*.pdtf*) within a zip archive. The metadata file contains all the information needed for PAMGaurd to set up the model. PAMGuard will import the zip file, decompress it and search for the relevent deep learning model and metadata file then set up all settings accordingly. This framework allows users to easily share pre-tested PAMGuard compatible models. ## Creating an instance of the module + The module can be added from the _File> Add modules > Classifier > Raw deep learning classifier_ menu or by right clicking in the data model. More than one instance of the module can be added if multiple deep learning models are required. ## Module settings + The module settings are opened by selecting the _Settings > Raw deep learning classifier_ menu. The main settings pane is shown below and is split into three sections, _Raw Sound Data_, _Segmentation_ and _Deep Learning Model_

    @@ -38,7 +52,7 @@ _The main settings pane for the deep learning module with descriptions_ ### Raw Sound Data -The deep learning module accepts any raw data source i.e., any data source that contains raw waveform data. +The deep learning module accepts any raw data source i.e., any data source that contains raw waveform data e.g. clicks, clips and Ishmael detections. Note that the module accepts whislte and moan detections but only if a delphinID classifier has been loaded. If the data is continuous, e.g. from the Sound Acquisition module then deep learning detections are saved to PAMGuard's data management system if they pass a user defined prediction threshold. The raw waveform data for segments which pass prediction threshold is saved and the detection is annotated with the deep prediction results. @@ -50,7 +64,7 @@ Channel grouping controls are used to arrange channels into groups. Channels in The segmentation section defines how the raw data is segmented. Some deep learning models require a specific segment size and others can be run with different segment sizes. The _Window Length_ is the size of the segment in samples. The _Hop Length_ is the overlap (from the start of the segment) in samples. A _Hop Length_ which is the same as the segment length means no overlap. If a prediction passes threshold, then the raw data from segments is saved to PAMGuard binary files. If concurrent segments pass a prediction threshold, then they are saved as one data unit. The _Max. re-merge_ is the maximum number of segments that can form a single data unit before a new data unit is automatically created. -### Deep Learning Model +### Deep Learning Model The deep learning model section is used to select the deep learning model. The drop down menu is used to select the framework the model is from e.g. Generic model. Note that each model type has a unique user interface which appears just below the drop down menu - currently these all look fairly similar. @@ -82,27 +96,42 @@ The _Model Settings_ tab allows the model input shape and output shape/classes t The import and export buttons on the bottom of the advanced settings pane can be used to export and import settings for the generic model. This means that users do not have to manually set up transforms and input and output data whenever settings up a new PAMGuard data model and allow easier sharing of classifiers amongst researchers. -#### AnimalSpot and Ketos models +#### Koogu, Ketos, AnimalSpot, PAMGuard zip and delphinID models -If using an AnimalSpot or Ketos model then all transforms are automatically set up. The transforms can be viewed and altered via the Advanced menu button but in the majority of cases these settings should not be used. It is advisable to select "Use default segment length" to change the _Window length_ to the default for the selected model. Note that this is often necessary for Ketos models but usually not a requirement for AnimalSpot models. +If using a deep learning model from a supported framework then all transforms are automatically set up. The transforms can be viewed and altered via the Advanced menu button but in the majority of cases these settings should not be used. For some models, it is advisable to select "Use default segment length" to change the _Window length_ to the default for the selected model.

    -_An AnimalSpot or Ketos model will automatically create a list of transforms with the appropriate settings. These is no need to use the advanced pane but it is there in case users wish to change transform settings for some reason_ +_An AnimalSpot, Ketos or other supported deep learning model will automatically create a list of transforms with the appropriate settings. These is no need to use the advanced pane but it is there in case users wish to change transform settings for some reason_ + +### Default Models + +Default models are selectable from the menu button in the Deep Learning Pane. Default models are deep learning classifiers which are open source, known to be effective and have have been published in open access academic litrature; they are downloaded directly from a GitHub repository and then all associated settings are automtically applied. The default model selection pane also contains hyperlinks to the papers descirbing each model which will take users directly to the relvent website. + +

    + +

    + +_Default models can be downloaded. Default models are models which are published, open and have been known to work well amongst the bioacoustics community. More will be added to PAMGaurd over time. If you you would like your model to become a defualt model then drop PAMGuard support an email._ ## Running + ### Real time + In real time, the deep learning model runs automatically when processing starts. A warning will appear if there are issues with the model and/or it cannot cope with real time speeds. ### Viewer Mode + The deep learning module can be re-run on _detector_ data (e.g. click or clip detections) in PAMGuard _viewer_ mode. Detections can be reclassified by selecting the _Settings > Raw Deep Learning Classifier > Reclassify detections_. Select the data range in the reprocessing dialog e.g. Loaded Data and select _Start_. Detections without a deep learning annotation will have one added and detections with an existing annotation will have it overwritten. ## Viewing and exporting results + Output from the deep learning module can be viewed in PAMGuard viewer mode, or extracted from binary files using MATLAB or R. ### PAMGuard viewer mode + Detections form continuous raw data are shown in the datagram in the same way as all data streams in PAMGuard. The Time base display FX is best way to view detailed data outputs from the deep learning algorithm. The time base display can display almost all data types in PAMGuard on a large variety of different data axis. For example, click detections can be displayed on an amplitude, bearing, ICI, waveform and/or frequency axis. Deep learning detections (i.e. data units which have been saved from raw data using the deep learning detector) can be displayed on the time base display in the same way as many other detections and in addition, there is a symbol manager options which allows the deep learning detections or other detections which have been classified by the deep learning module to be coloured by prediction class. This means that a manual analyst can quickly navigate to detections with high prediction values for a certain class. Hovering over or right clicking on a data unit in the time display and selecting the information button, will show the data unit’s metadata, including the prediction values for all output classes from the deep learning model. @@ -115,7 +144,8 @@ _An example click detection module output coloured by deep learning annotations. Other displays also show outputs from the deep learning module. Hovering over data units in the click display will, for example, show deep learning prediction values. The spectrogram will also show deep learning detections as translucent blue boxes (these must be selected in the right click menu). -### MATLAB +### MATLAB + The easiest way to export to MATLAB is to select the desired units in the time base display, right click and select the MATLAB icon. Data units will be exported to a .mat file as list of structures which is then saved to the clipboard. This file can be saved and then dragged into MATLAB to open. Where it is necessary to further analyse large datasets produced by PAMGuard, there is a MATLAB-PAMGuard library which can directly import the binary files which store PAMGaurd detection data. The library is simple to use with the primary function being ```loadPAMGuardBinaryFile.m. ``` This will load any binary file type (e.g. clicks, whistles, deep learning detections) and return a list of data structures with the detection data. The structures include annotations where deep learning predictions are stored. @@ -125,53 +155,52 @@ Here is a simple example loading up all the deep learning detections for a right ```matlab % the folder containing PAMGuard binary files folder = '/Users/me/right_whale_project_1/PAMBinary/'; - + %load all the detections in the folder dldetections = loadPamguardBinaryFolder(folder, 'Deep_Learning_Classifier_Raw_Deep_Learning_Classifier_DL_detection_*.pgdf') ``` + The predicitons for each class (in this case the classes are noise and right whale) are easily accessed in the structure via; ```matlab %% access the prediciton form the first detection predicitons = dldetections(1).annotations.dlclassification(j).predictions; - ``` The loaded detections can then be plotted by accessing the waveform data in each structure; ```matlab - % plot all the spectrograms. clf tiledlayout(5,5) for i=1:length(dldetections) - + nexttile - + % generate the data for a spectrgram [s, w, t] = spectrogram(dldetections(i).wave,512, 384,[],sR,'yaxis'); - + % create the time and frequency matrices required to plot a surface [X, Y] = meshgrid(t,w); % plot the surface (divide and multiply by 1000 to show milliseconds and kHz respectively) surf(X*1000, Y/1000, 20*log10(abs(s))-140, 'EdgeColor', 'None') view([0,90]) - + caxis([70, 140]-140) ylim([0,0.5]); xlabel('') ylabel('') - + if (mod(i,5)==0) c = colorbar; c.Label.String = 'Amplitude (dB)'; end - + %x axis only on bottom plots if (i>=20) xlabel('Time (ms)') end - + %y axis only on left most plots if (mod(i-1,5)==0) ylabel('Frequency (kHz)') @@ -185,11 +214,13 @@ for i=1:length(dldetections) _Right whale detections from a deep learning model imported and then plotted in MATLAB_ ### R + In the same way as MATLAB export, the PAMGuard time base display and export selected data units directly to an R struct which can be imported easily into R.. R also has a well supported PAMGuard library with like for like functions compared to the MATLAB library. The PAMBinaries R library can be found [here](https://github.com/TaikiSan21/PamBinaries). ## Common bugs and mistakes + The first time you use the module and/or load a different type of model e.g. a tensorflow or pytorch model, you must be connected to the internet. You must install the correct version of CUDA for hardware acceleration using an Nvidea GPU. See the currently supported CUDA versions on the Pytorch and Tensorflow websites. @@ -199,6 +230,3 @@ You should always have deep learning models in their own folder. Do not have any Pytorch models must be saved using jit to be compatible with PAMGuard. Tensorflow models must be saved as .pb files to be opened in PAMGuard. - - - diff --git a/src/rawDeepLearningClassifier/defaultModels/DLDefaultModelManager.java b/src/rawDeepLearningClassifier/defaultModels/DLDefaultModelManager.java new file mode 100644 index 00000000..6437cada --- /dev/null +++ b/src/rawDeepLearningClassifier/defaultModels/DLDefaultModelManager.java @@ -0,0 +1,50 @@ +package rawDeepLearningClassifier.defaultModels; + +import java.util.ArrayList; +import rawDeepLearningClassifier.DLControl; + +/** + * Manages default models. + */ +public class DLDefaultModelManager { + + /** + * A list of the default models + */ + private ArrayList defaultModels = new ArrayList(); + + /** + * Reference ot the DL control. + */ + private DLControl dlControl; + + /** + * Constructor for the Defulat Model Manager. + * @param dlControl - reference to the controller for this model manager. + */ + public DLDefaultModelManager(DLControl dlControl) { + this.dlControl = dlControl; + defaultModels.add(new RightWhaleModel1()); + defaultModels.add(new HumpbackWhaleGoogle()); + defaultModels.add(new HumpbackWhaleAtlantic()); + } + + + /** + * Get a default model at index i + * @param i - the index of the default model + */ + public DLModel getDefaultModel(int i) { + return defaultModels.get(i); + } + + + /** + * Get the number of default models + * @return the number of default models. + */ + public int getNumDefaultModels() { + return defaultModels.size(); + } + +} diff --git a/src/rawDeepLearningClassifier/defaultModels/DLModel.java b/src/rawDeepLearningClassifier/defaultModels/DLModel.java new file mode 100644 index 00000000..f3aad6d9 --- /dev/null +++ b/src/rawDeepLearningClassifier/defaultModels/DLModel.java @@ -0,0 +1,59 @@ +package rawDeepLearningClassifier.defaultModels; + +import java.io.Serializable; +import java.net.URI; + +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; + +/** + * A default model that can be loaded from a file or a URL. + */ +public interface DLModel { + + /** + * Get a brief description of the model. + * @return a brief description of the model. + */ + public String getDescription(); + + /** + * Get a name for the model + * @return + */ + public String getName(); + + /** + * Get the citation for the model + * @return - the citation. + */ + public String getCitation(); + + /** + * Get the URI to the model file + * @return the model URI. + */ + public URI getModelURI(); + + + /** + * The model name. This is used if, for example, a model is downloaded as a zip file and the model + * file is located somewhere within the saved folder. For Tensorflow models this will often be saved_model.pb + * @return the model name; + */ + public String getModelName(); + + /** + * Get the link to the paper for the model + * @return the citation link. + */ + public URI getCitationLink(); + + /** + * Set the model settings once it has loaded. + * @param dlModelSettings - the model settings. + */ + public void setParams(Serializable dlModelSettings); + + + +} diff --git a/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleAtlantic.java b/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleAtlantic.java new file mode 100644 index 00000000..4f7e7071 --- /dev/null +++ b/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleAtlantic.java @@ -0,0 +1,96 @@ +package rawDeepLearningClassifier.defaultModels; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; +import org.jamdev.jdl4pam.transforms.SimpleTransformParams; +import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; + +import rawDeepLearningClassifier.dlClassification.DLClassName; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.GenericModelParams; +import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory.ExampleSoundType; + +/** + * Atlantic version of Google's humpback whale network. + */ +public class HumpbackWhaleAtlantic implements DLModel { + + @Override + public String getDescription() { + return "Detects Atlantic Humpback whales"; + } + + @Override + public String getName() { + return "Humpback Whale Atlantic"; + } + + @Override + public String getCitation() { + return "Vincent Kather, Fabian Seipel, Benoit Berges, Genevieve Davis, Catherine Gibson, Matt Harvey, Lea-Anne Henry, Andrew Stevenson, Denise Risch; Development of a machine learning detector for North Atlantic humpback whale song. J. Acoust. Soc. Am. 1 March 2024; 155 (3): 2050–2064."; + } + + @Override + public URI getModelURI() { + try { + return new URI("https://github.com/PAMGuard/deeplearningmodels/raw/master/humpback_whale_2/humpback_whale_2.zip"); + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + @Override + public String getModelName() { + return "saved_model.pb"; + } + + @Override + public URI getCitationLink() { + try { + return new URI("https://doi.org/10.1121/10.0025275"); + } catch (URISyntaxException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void setParams(Serializable dlModelSettings) { + GenericModelParams genericModelParams = (GenericModelParams) dlModelSettings; + + //decimation value + float sr = 2000; + + //create the transforms. + ArrayList dlTransformParamsArr = new ArrayList(); + + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.DECIMATE_SCIPY, sr)); + + genericModelParams.dlTransfromParams = dlTransformParamsArr; + + genericModelParams.defaultSegmentLen = 3.8775*1000; + genericModelParams.binaryClassification = new boolean[] {true}; + genericModelParams.classNames= new DLClassName[] {new DLClassName("Humpback whale", (short) 1)}; + genericModelParams.numClasses = 1; + + genericModelParams.defaultShape= new Long[] {-1L,-1L,-1L,1L}; + genericModelParams.shape= new Long[] {-1L,-1L,-1L,1L}; + + //create the transforms. + genericModelParams.dlTransfroms = DLTransformsFactory.makeDLTransforms((ArrayList)genericModelParams.dlTransfromParams); + + genericModelParams.setExampleSound(ExampleSoundType.HUMPBACK_WHALE); + } + + + +} diff --git a/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleGoogle.java b/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleGoogle.java new file mode 100644 index 00000000..9a7ed64a --- /dev/null +++ b/src/rawDeepLearningClassifier/defaultModels/HumpbackWhaleGoogle.java @@ -0,0 +1,91 @@ +package rawDeepLearningClassifier.defaultModels; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; +import org.jamdev.jdl4pam.transforms.SimpleTransformParams; +import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; + +import rawDeepLearningClassifier.dlClassification.DLClassName; +import rawDeepLearningClassifier.dlClassification.genericModel.GenericModelParams; +import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory.ExampleSoundType; + +public class HumpbackWhaleGoogle implements DLModel { + + @Override + public String getDescription() { + return "A model developed by Google to detect Humpback whales (Megaptera novaeangliae) in the Pacific ocean"; + } + + @Override + public String getName() { + return "Humpback Whale Pacific"; + } + + @Override + public String getCitation() { + return "A. N. Allen et al., ‘A Convolutional Neural Network for Automated Detection of Humpback Whale Song in a Diverse, Long-Term Passive Acoustic Dataset’, Front. Mar. Sci., vol. 8, p. 607321, Mar. 2021"; + } + + @Override + public URI getModelURI() { + try { + return new URI("https://github.com/PAMGuard/deeplearningmodels/raw/master/humpback_whale_1/humpback_whale_1.zip"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public String getModelName() { + return "saved_model.pb"; + } + + @Override + public URI getCitationLink() { + try { + return new URI("https://doi.org/10.3389/fmars.2021.607321"); + } catch (URISyntaxException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void setParams(Serializable dlModelSettings) { + GenericModelParams genericModelParams = (GenericModelParams) dlModelSettings; + + //decimation value + float sr = 10000; + + //create the transforms. + ArrayList dlTransformParamsArr = new ArrayList(); + + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.DECIMATE_SCIPY, sr)); + + genericModelParams.dlTransfromParams = dlTransformParamsArr; + + genericModelParams.defaultSegmentLen = 3.92*1000; + genericModelParams.binaryClassification = new boolean[] {true}; + genericModelParams.classNames= new DLClassName[] {new DLClassName("Humpback whale", (short) 1)}; + genericModelParams.numClasses = 1; + + genericModelParams.defaultShape= new Long[] {-1L,-1L,-1L,1L}; + genericModelParams.shape= new Long[] {-1L,-1L,-1L,1L}; + + //create the transforms. + genericModelParams.dlTransfroms = DLTransformsFactory.makeDLTransforms((ArrayList)genericModelParams.dlTransfromParams); + + genericModelParams.setExampleSound(ExampleSoundType.HUMPBACK_WHALE); + } + + +} + diff --git a/src/rawDeepLearningClassifier/defaultModels/RightWhaleModel1.java b/src/rawDeepLearningClassifier/defaultModels/RightWhaleModel1.java new file mode 100644 index 00000000..eeb32c60 --- /dev/null +++ b/src/rawDeepLearningClassifier/defaultModels/RightWhaleModel1.java @@ -0,0 +1,115 @@ +package rawDeepLearningClassifier.defaultModels; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; +import org.jamdev.jdl4pam.transforms.SimpleTransformParams; + +import rawDeepLearningClassifier.dlClassification.DLClassName; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.GenericModelParams; +import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory.ExampleSoundType; + +/** + * Right whale model from Shiu et al. 2019 + *

    + * *

    + * Shiu, Y., Palmer, K.J., Roch, M.A., Fleishman, E., Liu, X., Nosal, E.-M., Helble, T., Cholewiak, D., Gillespie, D., Klinck, H., 2020. + * Deep neural networks for automated detection of marine mammal species. Scientific Reports 10, 607. https://doi.org/10.1038/s41598-020-57549-y + */ +public class RightWhaleModel1 implements DLModel { + + @Override + public String getDescription() { + return "Detects North Atlantic right whales (Eubalaena glacialis)."; + } + + @Override + public String getName() { + return "North Atlantic right whale"; + } + + @Override + public String getCitation() { + return "Shiu, Y., Palmer, K.J., Roch, M.A. et al. Deep neural networks for automated detection of marine mammal species. Sci Rep 10, 607 (2020)"; + } + + @Override + public URI getModelURI() { + try { + return new URI("https://github.com/PAMGuard/deeplearningmodels/raw/master/right_whale_1/model_lenet_dropout_input_conv_all.zip"); + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + @Override + /** + * Set the model settings once it has loaded. + * @param dlModelSettings - the model settings. + */ + public void setParams(Serializable dlModelSettings) { + + GenericModelParams genericModelParams = (GenericModelParams) dlModelSettings; + + //decimation value + float sr = 2000; + + //create the transforms. + ArrayList dlTransformParamsArr = new ArrayList(); + + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.DECIMATE, sr)); + // dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.PREEMPHSIS, preemphases)); + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.SPECTROGRAM, 256, 102)); + //in the python code they have an sfft of 129xN where N is the number of chunks. They then + //choose fft data between bin 5 and 45 in the FFT. This roughly between 40 and 350 Hz. + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.SPECCROPINTERP, 47.0, 357.0, 40)); + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.SPECNORMALISEROWSUM)); + + + genericModelParams.dlTransfromParams = dlTransformParamsArr; + + genericModelParams.defaultSegmentLen = 2.0*1000; + genericModelParams.binaryClassification = new boolean[] {false, true}; + + genericModelParams.defaultShape= new Long[] {-1L,40l,401L,1L}; + genericModelParams.shape= new Long[] {-1L,40L,40L,1L}; + + genericModelParams.classNames= new DLClassName[] {new DLClassName("Noise", (short) 0), new DLClassName("Right Whale", (short) 1)}; + genericModelParams.numClasses = 2; + + //create the transforms. + genericModelParams.dlTransfroms = DLTransformsFactory.makeDLTransforms((ArrayList)genericModelParams.dlTransfromParams); + + genericModelParams.setExampleSound(ExampleSoundType.RIGHT_WHALE); + + } + + @Override + public String getModelName() { + return "saved_model.pb"; + } + + @Override + public URI getCitationLink() { + try { + return new URI("https://doi.org/10.1038/s41598-020-57549-y"); + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/DLClassiferModel.java b/src/rawDeepLearningClassifier/dlClassification/DLClassiferModel.java index 465fb255..dd0e85b7 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLClassiferModel.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLClassiferModel.java @@ -1,11 +1,19 @@ package rawDeepLearningClassifier.dlClassification; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; +import org.jamdev.jdl4pam.transforms.DLTransform; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; +import org.jamdev.jdl4pam.transforms.SimpleTransform; +import org.jamdev.jdl4pam.transforms.SimpleTransformParams; + +import PamguardMVC.PamDataUnit; +import javafx.scene.Node; import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.DLStatus; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; import warnings.PamWarning; /** @@ -25,12 +33,26 @@ public interface DLClassiferModel { * * @return the deep learning model. */ - public ArrayList runModel(ArrayList rawDataUnit); + public ArrayList runModel(ArrayList rawDataUnit); /** * Prepare the model. This is called on PAMGuard start up. + * @param file */ public void prepModel(); + + /** + * Called whenever PAMGuard stops. + * @return + */ + public DLStatus setModel(URI model); + + /** + * Check whether a URI is compatible with a classification framework + * @param model - the URI to the model + * @return true if the model is compatible. + */ + public boolean isModelType(URI model); /** * Called whenever PAMGuard stops. @@ -81,14 +103,32 @@ public interface DLClassiferModel { /** * Check whether a model has been selected and can be loaded successfully. */ - public boolean checkModelOK(); + public DLStatus getModelStatus(); + +// /** +// * Get warnings for the classifier model. This is called when the user confirms settings and +// * used to return a warning dialog. +// * @return a list of warnings. If the list is null or size() is zero then settings are OK. +// */ +// public ArrayList checkSettingsOK(); + /** - * Get warnings for the classifier model. This is called when the user confirms settings and - * used to return a warning dialog. - * @return a list of warnings. If the list is null or size() is zero then settings are OK. + * Get the parameters which can be serialized from transforms. + * @param dlTransfroms- the dl transforms. */ - public ArrayList checkSettingsOK(); + public static ArrayList getDLTransformParams(ArrayList dlTransfroms) { + ArrayList dlTransformParams = new ArrayList(); + + if (dlTransfroms==null) return null; + //need to set the generic model params. + for (int i=0; i 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. @@ -83,8 +81,8 @@ public class DLClassifyProcess extends PamInstantProcess { public DLClassifyProcess(DLControl dlControl, SegmenterDataBlock parentDataBlock) { super(dlControl); - - + + this.setParentDataBlock(parentDataBlock); // this.setParentDataBlock(parentDataBlock); @@ -118,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"); @@ -141,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()); @@ -167,7 +165,7 @@ public class DLClassifyProcess extends PamInstantProcess { public void prepareProcess() { setupClassifierProcess(); } - + /** * called for every process once the system model has been created. @@ -195,36 +193,72 @@ 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 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 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 @@ -236,18 +270,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; @@ -255,16 +289,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); @@ -495,6 +536,39 @@ public class DLClassifyProcess extends PamInstantProcess { } + /** + * Get the result with the highest score. + * @return model result with the highest score. + */ + public PredictionResult getBestModelResult(DLDetection dlDetection) { + ArrayList results = dlDetection.getModelResults(); + if (results == null || results.size() == 0) { + return null; + } + /* + * probably need to improve this function to only look at results + * that are in a list of used results or something crazy ? + */ + +// dlControl.getDLModel().; + PredictionResult bestResult = null; + float bestScore = 0; + for (PredictionResult pred : results) { + float[] scores = pred.getPrediction(); + if (scores == null) { + continue; + } + for (int i = 0; i < scores.length; i++) { + if (scores[i] > bestScore) { + bestScore = scores[i]; + bestResult = pred; + } + } + } + + return bestResult; + } + /** * Clear the data unit buffer. */ @@ -533,14 +607,22 @@ public class DLClassifyProcess extends PamInstantProcess { @Override public void pamStart() { - // TODO Auto-generated method stub +// 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(); } @@ -596,4 +678,8 @@ public class DLClassifyProcess extends PamInstantProcess { return this.dlControl.getDLParams(); } + public DLControl getDLControl() { + return dlControl; + } + } 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/DLDetection.java b/src/rawDeepLearningClassifier/dlClassification/DLDetection.java index 6c1903a7..6f5bb842 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLDetection.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLDetection.java @@ -132,7 +132,6 @@ public class DLDetection extends PamDataUnit implements PamDetection, RawDataHol if (annotation!=null) return annotation.getModelResults(); else return null; } - /** diff --git a/src/rawDeepLearningClassifier/dlClassification/DLDetectionDataBlock.java b/src/rawDeepLearningClassifier/dlClassification/DLDetectionDataBlock.java index 07d78166..490925cf 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLDetectionDataBlock.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLDetectionDataBlock.java @@ -3,6 +3,12 @@ package rawDeepLearningClassifier.dlClassification; import PamView.GroupedDataSource; import PamView.GroupedSourceParameters; import PamguardMVC.AcousticDataBlock; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.tethys.DLSpeciesManager; +import rawDeepLearningClassifier.tethys.DLTethysDataProvider; +import tethys.TethysControl; +import tethys.pamdata.TethysDataProvider; +import tethys.species.DataBlockSpeciesManager; /** * Holds classified data units from deep learning model. @@ -13,10 +19,14 @@ import PamguardMVC.AcousticDataBlock; public class DLDetectionDataBlock extends AcousticDataBlock implements GroupedDataSource { private DLClassifyProcess dlClassifyProcess; + private DLTethysDataProvider dlTethysDataProvider; + private DLSpeciesManager dlSpeciesManager; + private DLControl dlControl; public DLDetectionDataBlock(String dataName, DLClassifyProcess parentProcess, int channelMap) { super(DLDetection.class, dataName, parentProcess, channelMap); this.dlClassifyProcess = parentProcess; + dlControl = dlClassifyProcess.getDLControl(); } @Override @@ -24,5 +34,21 @@ public class DLDetectionDataBlock extends AcousticDataBlock impleme return dlClassifyProcess.getDLParams().groupedSourceParams; } + @Override + public TethysDataProvider getTethysDataProvider(TethysControl tethysControl) { + if (dlTethysDataProvider == null) { + dlTethysDataProvider = new DLTethysDataProvider(tethysControl, dlControl, this); + } + return dlTethysDataProvider; + } + + @Override + public DataBlockSpeciesManager getDatablockSpeciesManager() { + if (dlSpeciesManager == null) { + dlSpeciesManager = new DLSpeciesManager(dlClassifyProcess.getDLControl(), this); + } + return dlSpeciesManager; + } + } diff --git a/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java b/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java index 3e730769..c1d77c41 100644 --- a/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java +++ b/src/rawDeepLearningClassifier/dlClassification/DLTaskThread.java @@ -5,14 +5,14 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import rawDeepLearningClassifier.dlClassification.animalSpot.SoundSpotResult; +import PamguardMVC.PamDataUnit; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; +import rawDeepLearningClassifier.segmenter.GroupedRawData; /** * Creates a que for grouped data units for classiifcation. - * @author au671271 + * @author Jamie Macaulay * */ public abstract class DLTaskThread extends Thread { @@ -28,7 +28,7 @@ public abstract class DLTaskThread extends Thread { /** * Holds a list of segmented raw data units which need to be classified. */ - private List> queue = Collections.synchronizedList(new ArrayList>()); + private List> queue = Collections.synchronizedList(new ArrayList>()); public DLTaskThread(DLModelWorker soundSpotWorker) { @@ -38,9 +38,9 @@ public abstract class DLTaskThread extends Thread { public void stopTaskThread() { run.set(false); //Clean up daemon. - if (dlModelWorker!=null) { - dlModelWorker.closeModel(); - } +// if (dlModelWorker!=null) { +// dlModelWorker.closeModel(); +// } dlModelWorker = null; } @@ -50,9 +50,9 @@ public abstract class DLTaskThread extends Thread { try { if (queue.size()>0) { System.out.println("DL TASK THREAD: " + "The queue size is " + queue.size()); - ArrayList groupedRawData = queue.remove(0); + 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> getQueue() { + public List> getQueue() { return queue; } - public void setQueue(List> queue) { + public void setQueue(List> queue) { this.queue = queue; } diff --git a/src/rawDeepLearningClassifier/dlClassification/DefaultModels.java b/src/rawDeepLearningClassifier/dlClassification/DefaultModels.java new file mode 100644 index 00000000..dcbd3cf6 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/DefaultModels.java @@ -0,0 +1,70 @@ +package rawDeepLearningClassifier.dlClassification; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javafx.stage.FileChooser.ExtensionFilter; +import rawDeepLearningClassifier.DLControl; + +public class DefaultModels { + + /** + * The default model list. + */ + ArrayList defaultModels; + + /** + * The dlControl. + */ + private DLControl dlControl; + + public DefaultModels(DLControl dlControl) { + this.dlControl = dlControl; + defaultModels = new ArrayList(); + + /***/ + + defaultModels.add(new DefualtModel("Right Whale", null)); + + + } + + + public class DefualtModel { + + public DefualtModel(String name, URI filename) { + this.name = name; + this.filename = filename; + } + + /** + * The name of the model. + */ + public String name; + + /** + * The filename of the model + */ + public URI filename; + + /** + * Get the DLClassifier associated with the model. + * Technically this could be overridden to provide a different DLClassifierModel. + * @return the default model classifier. + */ + public DLClassiferModel getDLClassifier() { + return dlControl.getDlClassifierChooser().selectClassiferModel(filename); + } + + } + + /** + * Get the default models. + * @return the default models. + */ + public List getDefaultModels() { + return defaultModels; + } + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java b/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java new file mode 100644 index 00000000..bc11e883 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/StandardClassifierModel.java @@ -0,0 +1,365 @@ +package rawDeepLearningClassifier.dlClassification; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.plexus.util.FileUtils; + +import PamController.PamSettings; +import PamDetection.RawDataUnit; +import PamUtils.PamArrayUtils; +import PamUtils.PamCalendar; +import PamguardMVC.PamDataUnit; +import javafx.stage.FileChooser.ExtensionFilter; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.DLStatus; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.GenericDLClassifier; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; +import rawDeepLearningClassifier.layoutFX.DLSettingsPane; +import rawDeepLearningClassifier.segmenter.GroupedRawData; +import warnings.PamWarning; +import warnings.WarningSystem; + +/** + * A useful abstract class for standard models which are a file or URL that is loaded, have a UI and + * utilise PAMSettings to save settings state. These models only accept raw sound data segments. + */ +public abstract class StandardClassifierModel implements DLClassiferModel, PamSettings { + + + protected DLControl dlControl; + + /** + * True to force the classifier to use a queue - used for simulating real time operation. + */ + private boolean forceQueue = false; + + /** + * The worker thread has a buffer so that Standard models can be run + * in real time without slowing down the rest of PAMGaurd. + */ + private TaskThread workerThread; + + + public StandardClassifierModel(DLControl dlControl) { + this.dlControl=dlControl; + } + + /** + * Sound spot warning. + */ + PamWarning dlClassifierWarning = new PamWarning(getName(), "",2); + + + @Override + @SuppressWarnings("rawtypes") + public ArrayList runModel( ArrayList groupedRawData) { + if (getDLWorker().isModelNull()) return null; + + // System.out.println("SoundSpotClassifier: PamCalendar.isSoundFile(): " + // + PamCalendar.isSoundFile() + " " + (PamCalendar.isSoundFile() && !forceQueue)); + + + + /** + * If a sound file is being analysed then Ketos can go as slow as it wants. if used in real time + * then there is a buffer with a maximum queue size. + */ + if ((PamCalendar.isSoundFile() && !forceQueue) || dlControl.isViewer()) { + //run the model + @SuppressWarnings("unchecked") + ArrayList modelResult = (ArrayList) getDLWorker().runModel(groupedRawData, + groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); + + if (modelResult==null) { + dlClassifierWarning.setWarningMessage(getName() + " deep learning model returned null"); + WarningSystem.getWarningSystem().addWarning(dlClassifierWarning); + return null; + } + + for (int i =0; iDLModelWorker.MAX_QUEUE_SIZE) { + //we are not doing well - clear the buffer + workerThread.getQueue().clear(); + } + workerThread.getQueue().add(groupedRawData); + } + return null; + } + + + @Override + public void prepModel() { + System.out.println("STANDARD CLASSIFIER MODEL PREP MODEL! !!!: " + getDLParams().modelPath); +// StandardModelParams oldParams = getDLParams().clone(); + + getDLWorker().prepModel(getDLParams(), dlControl); + + + if (getDLWorker().isModelNull()) { + dlClassifierWarning.setWarningMessage("There is no loaded " + getName() + " classifier model. " + getName() + " disabled."); + WarningSystem.getWarningSystem().addWarning(dlClassifierWarning); + return; + } + + + if ((!PamCalendar.isSoundFile() || forceQueue) && !dlControl.isViewer()) { + //for real time only + if (workerThread!=null) { + workerThread.stopTaskThread(); + } + + workerThread = new TaskThread(getDLWorker()); + workerThread.setPriority(Thread.MAX_PRIORITY); + workerThread.start(); + } + } + + + /** + * Get the sound spot worker. + * @return the sound spot worker. + */ + public abstract DLModelWorker getDLWorker(); + + /** + * Get the sound spot worker. + * @return the sound spot worker. + */ + public abstract StandardModelParams getDLParams(); + + + public DLControl getDLControl() { + return dlControl; + } + + + @Override + public int getNumClasses() { + return this.getDLParams().numClasses; + } + + @Override + public DLClassName[] getClassNames() { + return getDLParams().classNames; + } + + @Override + public DLStatus getModelStatus() { + if (getDLWorker().isModelNull()) { + return DLStatus.MODEL_LOAD_FAILED; + } + + File file = new File(getDLParams().modelPath); + if (getDLParams().modelPath == null || !file.isFile()) { + return DLStatus.NO_MODEL_LOADED; + } + + // if continuous data is selected and all classes are false then this is a + // potential mistake... + if (dlControl.getSettingsPane().getSelectedParentDataBlock().getUnitClass() == RawDataUnit.class + && (getDLParams().binaryClassification==null || PamArrayUtils.isAllFalse(getDLParams().binaryClassification))){ + return DLStatus.NO_BINARY_CLASSIFICATION; +// warnings.add(new PamWarning("Generic classifier", +// "There are no prediction classes selected for classification. " +// + "Predicitons for each segment will be saved but there will be no detections generated", +// 1)); + } + return DLStatus.MODEL_LOAD_SUCCESS; + } + + @Override + public DLStatus setModel(URI uri) { + //will change the params if we do not clone. + StandardModelParams.setModel(uri, this.getDLParams()); + this.getDLWorker().prepModel(getDLParams(), dlControl); + return getModelStatus(); + } + + + + /** + * The task thread. + * @author Jamie Macaulay + * + */ + public class TaskThread extends DLTaskThread { + + public TaskThread(DLModelWorker soundSpotWorker) { + super(soundSpotWorker); + } + + @Override + 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() { + getDLWorker().closeModel(); + } + + + /** + * Send a new result form the thread queue to the process. + * @param modelResult - the model result; + * @param groupedRawData - the grouped raw data. + */ + protected void newResult(StandardPrediction modelResult, PamDataUnit groupedRawData) { + if (groupedRawData instanceof GroupedRawData) { + this.dlControl.getDLClassifyProcess().newRawModelResult(modelResult, (GroupedRawData) groupedRawData); + } + } +// +// @Override +// public ArrayList checkSettingsOK() { +// return checkSettingsOK(getDLParams(), dlControl); +// } + + + /** + * Get the number of samples for microseconds. Based on the sample rate of the parent data block. + */ + public double millis2Samples(double millis) { + //System.out.println("Samplerate: " + this.dlControl.getSegmenter().getSampleRate() ); + return millis*this.dlControl.getSegmenter().getSampleRate()/1000.0; + } + + + /** + * Get raw settings pane + * @return the setting pane. + */ + public DLSettingsPane getRawSettingsPane() { + return this.dlControl.getSettingsPane(); + } + + + public boolean isModelExtensions(URI uri){ +// System.out.println("Check: " + getName() + " extensions"); + String url; + try { + url = uri.toURL().getPath(); + + if (url.contains(".")) { + String extension = FileUtils.getExtension(url); +// System.out.println("Check: " + getName() + " file extension " + extension); + + List pExtensions = this.getModelUI().getModelFileExtensions(); + for (ExtensionFilter extF : pExtensions) { + for (String ext : extF.getExtensions()) { +// System.out.println(getName() + " Extensions: " + ext + " " + extension + " " + ext.equals(extension.substring(2).trim()) + " " + extension.substring(2).trim()); + if (extension.equals(ext.substring(2))) { + return true; + } + } + } + + } + return false; + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return false; + } + + + @Deprecated + public static ArrayList checkSettingsOK(StandardModelParams genericModelParams, DLControl dlControl) { + + // TODO - check if model is null. + // check that classifier is selected if continous files. + // + ArrayList warnings = new ArrayList(); + + File file = new File(genericModelParams.modelPath); + if (genericModelParams.modelPath == null || !file.isFile()) { + warnings.add( + new PamWarning("Generic classifier", "There is no model loaded - the classifier will not run", 2)); + // if no model then this is ionly message needed for the generic classifier. + return warnings; + } + + // if continous data is selected and all classes are false then this is a + // potential mistake... + if (dlControl.getSettingsPane().getSelectedParentDataBlock().getUnitClass() == RawDataUnit.class + && PamArrayUtils.isAllFalse(genericModelParams.binaryClassification)) { + warnings.add(new PamWarning("Generic classifier", + "There are no prediction classes selected for classification. " + + "Predicitons for each segment will be saved but there will be no detections generated", + 1)); + } + + return warnings; + + } + + + + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java index 17d3d58e..d9374432 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotClassifier.java @@ -1,25 +1,22 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; import java.io.Serializable; +import java.net.URI; + import java.util.ArrayList; -import java.util.Collections; -import java.util.List; + +import org.jamdev.jdl4pam.animalSpot.AnimalSpotModel; +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; + import PamController.PamControlledUnitSettings; import PamController.PamSettingManager; -import PamController.PamSettings; -import PamUtils.PamCalendar; import rawDeepLearningClassifier.DLControl; -import rawDeepLearningClassifier.dlClassification.DLClassName; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; -import rawDeepLearningClassifier.dlClassification.DLTaskThread; +import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericDLClassifier; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; -import rawDeepLearningClassifier.layoutFX.RawDLSettingsPane; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; -import warnings.PamWarning; -import warnings.WarningSystem; /** * A deep learning classifier running models wihc have been created using the @@ -40,12 +37,7 @@ import warnings.WarningSystem; * @author JamieMacaulay * */ -public class SoundSpotClassifier implements DLClassiferModel, PamSettings { - - /** - * Reference to the control. - */ - private DLControl dlControl; +public class SoundSpotClassifier extends StandardClassifierModel { /** * The user interface for sound spot. @@ -61,87 +53,125 @@ public class SoundSpotClassifier implements DLClassiferModel, PamSettings { /** * The deep learning model worker. */ - private SoundSpotWorker soundSpotWorker; + private DLModelWorker soundSpotWorker; - /** - * True to force the classifier to use a queue - used for simulating real time operation. - */ - private boolean forceQueue = false; - - /** - * Sound spot warning. - */ - PamWarning soundSpotWarning = new PamWarning("AnimalSpot_Classifier", "",2); - - /** - * Runs the deep leanring on a different thread with a data buffer. For real time only. - */ - private DLTaskThread workerThread; public SoundSpotClassifier(DLControl dlControl) { - this.dlControl=dlControl; + super(dlControl); this.soundSpotParmas = new StandardModelParams(); this.soundSpotUI= new SoundSpotUI(this); //load the previous settings PamSettingManager.getInstance().registerSettings(this); - - System.out.println("LOADED CLASS NAMES: currParams.classNames: " + soundSpotParmas.classNames); +// System.out.println("LOADED CLASS NAMES: currParams.classNames: " + soundSpotParmas.classNames); + } + + + @Override + public String getName() { + return "AnimalSpot"; + } + + @Override + public DLCLassiferModelUI getModelUI() { + return soundSpotUI; + } + + @Override + public Serializable getDLModelSettings() { + return soundSpotParmas; + } + + @Override + public String getUnitName() { + return dlControl.getUnitName()+"_SoundSpot"; + } + + @Override + public String getUnitType() { + return dlControl.getUnitType()+"_SoundSpot"; + } + + @Override + public Serializable getSettingsReference() { + if (soundSpotParmas==null) { + soundSpotParmas = new StandardModelParams(); + } + + ArrayList dlTransformParams = DLClassiferModel.getDLTransformParams(soundSpotParmas.dlTransfroms); + + soundSpotParmas.dlTransfromParams=dlTransformParams; + + + //System.out.println("SoundSpot have been saved. : " + soundSpotParmas.classNames); + return soundSpotParmas; } @Override - public ArrayList runModel(ArrayList groupedRawData) { + public long getSettingsVersion() { + return StandardModelParams.serialVersionUID; + } - // System.out.println("SoundSpotClassifier: PamCalendar.isSoundFile(): " - // + PamCalendar.isSoundFile() + " " + (PamCalendar.isSoundFile() && !forceQueue)); - /** - * If a sound file is being analysed then SoundSpot can go as slow as it wants. if used in real time - * then there is a buffer with a maximum queue size. - */ - if ((PamCalendar.isSoundFile() && !forceQueue) || dlControl.isViewer()) { - //run the model - ArrayList modelResult = getSoundSpotWorker().runModel(groupedRawData, - groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); - for (int i =0; i) soundSpotParmas.dlTransfromParams); } - - return modelResult; //returns to the classifier. } - else { - //add to a buffer if in real time. - if (workerThread.getQueue().size()>DLModelWorker.MAX_QUEUE_SIZE) { - //we are not doing well - clear the buffer - workerThread.getQueue().clear(); - } - workerThread.getQueue().add(groupedRawData); - } - return null; + else soundSpotParmas = new StandardModelParams(); + return true; } /** - * Check whether a model passes a binary test... - * @param modelResult - the model results - * @return the model results. + * Get the sound spot parameters. + * @return sound spot parameters. */ - private boolean isBinaryResult(GenericPrediction modelResult) { - for (int i=0; isoundSpotParmas.threshold && soundSpotParmas.binaryClassification[i]) { - //System.out.println("SoundSpotClassifier: prediciton: " + i + " passed threshold with val: " + modelResult.getPrediction()[i]); - return true; - } - } - return false; + public StandardModelParams getSoundSpotParams() { + return soundSpotParmas; } /** - * Get the sound spot worker. - * @return the sound spot worker. + * Set the sound spot parameters. + * @param the params to set */ - SoundSpotWorker getSoundSpotWorker() { + public void setSoundSpotParams(StandardModelParams soundSpotParmas) { + this.soundSpotParmas=soundSpotParmas; + } + + + + @Override + public boolean isModelType(URI uri) { + //TODO need to be more sophisticated here. + if (super.isModelExtensions(uri)) { + //we have a PyTorch model but is it animal spot. + + try { + AnimalSpotModel soundSpotModel = new AnimalSpotModel(uri.getPath()); + if (soundSpotModel!=null && soundSpotModel.getExtraFiles()!=null) { + return true; + } + return false; + } + + catch (Exception e) { + return false; + } + + } + + return false; + } + + + @Override + public DLModelWorker getDLWorker() { if (soundSpotWorker==null) { soundSpotWorker = new SoundSpotWorker(); } @@ -149,27 +179,33 @@ public class SoundSpotClassifier implements DLClassiferModel, PamSettings { } - /** - * The task thread. - * @author Jamie Macaulay - * - */ - public class TaskThread extends DLTaskThread { - - TaskThread(DLModelWorker soundSpotWorker) { - super(soundSpotWorker); - } - - @Override - public void newDLResult(GenericPrediction soundSpotResult, GroupedRawData groupedRawData) { - soundSpotResult.setClassNameID(getClassNameIDs()); - soundSpotResult.setBinaryClassification(isBinaryResult(soundSpotResult)); - newResult(soundSpotResult, groupedRawData); - } + @Override + public StandardModelParams getDLParams() { + return soundSpotParmas; } + +// /** +// * Check whether a model passes a binary test... +// * @param modelResult - the model results +// * @return the model results. +// */ +// private boolean isBinaryResult(GenericPrediction modelResult) { +// for (int i=0; isoundSpotParmas.threshold && soundSpotParmas.binaryClassification[i]) { +// //System.out.println("SoundSpotClassifier: prediciton: " + i + " passed threshold with val: " + modelResult.getPrediction()[i]); +// return true; +// } +// } +// return false; +// } + + + + + // public class TaskThread extends Thread { // // private AtomicBoolean run = new AtomicBoolean(true); @@ -223,171 +259,19 @@ public class SoundSpotClassifier implements DLClassiferModel, PamSettings { // // } - /** - * Get the class name IDs - * @return an array of class name IDs - */ - private short[] getClassNameIDs() { - if (soundSpotParmas.classNames==null || soundSpotParmas.classNames.length<=0) return null; - short[] nameIDs = new short[soundSpotParmas.classNames.length]; - for (int i = 0 ; i checkSettingsOK() { - return GenericDLClassifier.checkSettingsOK(soundSpotParmas, dlControl); - } } diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotModelPane.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotModelPane.java index ba61ffea..457a9b0a 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotModelPane.java @@ -11,20 +11,14 @@ public class SoundSpotModelPane extends StandardModelPane { * Reference to the currently selected sound spot classifier. */ private SoundSpotClassifier soundSpotClassifier; - - /** - * The extension filter for sound spot models. - */ - private ArrayList extensionFilters; + public SoundSpotModelPane(SoundSpotClassifier soundSpotClassifier) { super(soundSpotClassifier); // TODO Auto-generated constructor stub this.soundSpotClassifier=soundSpotClassifier; - - extensionFilters = new ArrayList (); - extensionFilters.add(new ExtensionFilter("Pytorch Model", "*.pk")); + } @@ -35,7 +29,7 @@ public class SoundSpotModelPane extends StandardModelPane { @Override public void newModelSelected(File file) { - System.out.println("New file model selected:"); + //System.out.println("New file model selected:"); this.setCurrentSelectedFile(file); @@ -46,20 +40,31 @@ public class SoundSpotModelPane extends StandardModelPane { //prep the model with current parameters; StandardModelParams params = getParams(getParamsClone()); - soundSpotClassifier.getSoundSpotWorker().prepModel(params, soundSpotClassifier.getDLControl()); + +// if (params.dlTransfromParams!=null) { +// System.out.println("AnimalSpot: Decimator: " + params.dlTransfromParams.get(0).params[0]); +// } +// else { +// System.out.println("AnimalSpot: params is null" + getParamsClone().dlTransfromParams); +// } + + /** + * Note that the model prep will determine whether new transforms need to be loaded from the + * model or to use the existing transforms in the settings. + */ + soundSpotClassifier.getDLWorker().prepModel(params, soundSpotClassifier.getDLControl()); //get the model tansforms calculated from the model by SoundSpoyWorker and apply them to our temporary params clone. - getParamsClone().dlTransfroms = this.soundSpotClassifier.getSoundSpotWorker().getModelTransforms(); + getParamsClone().dlTransfroms = this.soundSpotClassifier.getDLWorker().getModelTransforms(); +// if (getParamsClone().defaultSegmentLen!=null) { +// usedefaultSeg.setSelected(true); +// } + ///set the advanced pane parameters. getAdvSettingsPane().setParams(getParamsClone()); } - @Override - public ArrayList getExtensionFilters() { - return extensionFilters; - } - } 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/SoundSpotUI.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotUI.java index 87ecf231..b13b8feb 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotUI.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotUI.java @@ -1,8 +1,13 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; +import java.util.ArrayList; +import java.util.List; + import javax.swing.JPanel; import PamController.SettingsPane; +import javafx.scene.Node; +import javafx.stage.FileChooser.ExtensionFilter; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; @@ -24,12 +29,21 @@ public class SoundSpotUI implements DLCLassiferModelUI { */ private SoundSpotClassifier soundSpotClassifier; + + /** + * The extension filter for sound spot models. + */ + private ArrayList extensionFilters; + /** * SondSpot classifier. * @param soundSpotClassifier */ public SoundSpotUI(SoundSpotClassifier soundSpotClassifier) { this.soundSpotClassifier=soundSpotClassifier; + + extensionFilters = new ArrayList (); + extensionFilters.add(new ExtensionFilter("Pytorch Model", "*.pk")); } @Override @@ -59,4 +73,21 @@ public class SoundSpotUI implements DLCLassiferModelUI { return null; } + /** + * Get a list of extension fitlers for the file dialog. + * e.g. + * new ExtensionFilter("Pytorch Model", "*.pk") + * @return a list of extension fitlers for the file dialog. + */ + @Override + public List getModelFileExtensions() { + return extensionFilters; + } + + @Override + public Node getIcon() { + // TODO Auto-generated method stub + return null; + } + } diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java index 095fa2d5..735db07c 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/SoundSpotWorker.java @@ -1,12 +1,13 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; +import java.nio.file.Paths; + import org.jamdev.jdl4pam.animalSpot.AnimalSpotModel; import org.jamdev.jdl4pam.animalSpot.AnimalSpotParams; -import PamModel.PamModel; -import PamModel.PamModel.PluginClassloader; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** @@ -18,13 +19,16 @@ import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; * @author Jamie Macaulay * */ -public class SoundSpotWorker extends DLModelWorker { +public class SoundSpotWorker extends DLModelWorker { /** * Sound spot model. */ - private AnimalSpotModel soundSpotModel; + private AnimalSpotModel soundSpotModel; + + + private String currentPath; /** @@ -39,8 +43,8 @@ public class SoundSpotWorker extends DLModelWorker { */ public void prepModel(StandardModelParams soundSpotParams, DLControl dlControl) { //ClassLoader origCL = Thread.currentThread().getContextClassLoader(); - - System.out.println("prepModel: " + soundSpotParams.useDefaultTransfroms); + + //System.out.println("prepModel: " + soundSpotParams.useDefaultTransfroms); try { @@ -52,9 +56,13 @@ public class SoundSpotWorker extends DLModelWorker { // PluginClassloader newCL = PamModel.getPamModel().getClassLoader(); // Thread.currentThread().setContextClassLoader(newCL); // } + if (soundSpotModel==null || currentPath ==null || !Paths.get(currentPath).equals(Paths.get(soundSpotParams.modelPath))) { + //System.out.println("Sound spot path: " + soundSpotParams.modelPath); + //first open the model and get the correct parameters. + soundSpotModel = new AnimalSpotModel(soundSpotParams.modelPath); + this.currentPath = soundSpotParams.modelPath; - //first open the model and get the correct parameters. - soundSpotModel = new AnimalSpotModel(soundSpotParams.modelPath); + } } catch (Exception e) { e.printStackTrace(); @@ -66,7 +74,7 @@ public class SoundSpotWorker extends DLModelWorker { AnimalSpotParams dlParams = new AnimalSpotParams(soundSpotModel.getTransformsString()); //only load new transforms if defaults are selected - if (getModelTransforms()==null || soundSpotParams.useDefaultTransfroms) { + if (getModelTransforms()==null || soundSpotParams.dlTransfroms==null || soundSpotParams.useDefaultTransfroms) { //only set the transforms if they are null - otherwise handled elsewhere. setModelTransforms(model2DLTransforms(dlParams)); soundSpotParams.useDefaultTransfroms = true; @@ -75,7 +83,7 @@ public class SoundSpotWorker extends DLModelWorker { //use the old transforms. setModelTransforms(soundSpotParams.dlTransfroms); } - + soundSpotParams.defaultSegmentLen = dlParams.seglen; //the segment length in microseconds. soundSpotParams.numClasses = dlParams.classNames.length; @@ -132,7 +140,8 @@ public class SoundSpotWorker extends DLModelWorker { * Destroy the model. */ public void closeModel() { - //TODO + //TODO - need to be able to access model in JPAM API. + this.currentPath = null; } @@ -145,4 +154,10 @@ public class SoundSpotWorker extends DLModelWorker { } + @Override + public boolean isModelNull() { + return soundSpotModel==null; + } + + } diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardAdvModelPane.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardAdvModelPane.java index 8684bfcb..1842dada 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardAdvModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardAdvModelPane.java @@ -1,6 +1,10 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; +import java.util.ArrayList; + import org.controlsfx.control.ToggleSwitch; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; + import PamController.SettingsPane; import javafx.geometry.Insets; import javafx.scene.Node; @@ -106,13 +110,18 @@ public class StandardAdvModelPane extends SettingsPane { @Override public StandardModelParams getParams(StandardModelParams currParams) { currParams.dlTransfroms = transfromPane.getDLTransforms(); + + currParams.dlTransfromParams = new ArrayList(); +// for (int i=0; i { BorderPane.setMargin(defaultTogglePane, new Insets(5)); transfromPane.setDisable(params.useDefaultTransfroms); toggleSwitch.setSelected(params.useDefaultTransfroms); + transfromPane.setTransforms(params.dlTransfroms); //set the image mainPane.setBottom(dlImage); diff --git a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java index ce694ccf..7dda72ad 100644 --- a/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/animalSpot/StandardModelPane.java @@ -1,35 +1,27 @@ package rawDeepLearningClassifier.dlClassification.animalSpot; import java.io.File; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.util.ArrayList; - import org.controlsfx.control.CheckComboBox; +import org.controlsfx.control.IndexedCheckModel; import org.controlsfx.control.PopOver; import org.controlsfx.control.ToggleSwitch; import PamController.SettingsPane; +import PamUtils.PamArrayUtils; import PamView.dialog.warn.WarnOnce; -import ai.djl.Device; -import javafx.application.Platform; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener.Change; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.control.Labeled; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.Spinner; import javafx.scene.control.Tooltip; import javafx.scene.control.Alert.AlertType; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; -import javafx.stage.FileChooser; -import javafx.stage.FileChooser.ExtensionFilter; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; @@ -38,8 +30,9 @@ import pamViewFX.fxNodes.PamGridPane; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.PamSpinner; import pamViewFX.fxNodes.PamVBox; -import pamViewFX.fxNodes.flipPane.FlipPane; +import pamViewFX.validator.PamValidator; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; /** * Settings pane for SoundSpot @@ -54,20 +47,12 @@ public abstract class StandardModelPane extends SettingsPane{ - - fileChooser.getExtensionFilters().clear(); - fileChooser.getExtensionFilters().addAll(getExtensionFilters()); - - - Path path = currentSelectedFile.toPath(); - if(path!=null && Files.exists(path, LinkOption.NOFOLLOW_LINKS)) { - fileChooser.setInitialDirectory(new File(currentSelectedFile.getParent())); - } - else { - fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); - } - - File file = fileChooser.showOpenDialog(null); - - if (file==null) { - return; - } - - modelLoadIndicator.setVisible(true); - - pathLabel.setText("Loading model..."); - - //whenever a new model is selected then the the paramters should be set to use defualt transforms again - this ensures - //that the new transforms are loaded up - advSettingsPane.getParams(paramsClone); - paramsClone.useDefaultTransfroms=true; - advSettingsPane.setParams(paramsClone); - - - // separate non-FX thread - load the model - //on a separate thread so we can show a moving load - //bar on the FX thread. Otherwise the GUI locks up - //whilst stuff is loaded. - new Thread() { - // runnable for that thread - public void run() { - try { - newModelSelected(file); - Thread.sleep(5000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - Platform.runLater(new Runnable() { - public void run() { - modelLoadIndicator.setVisible(false); - updatePathLabel(); - setClassNames(paramsClone); - } - }); - } - }.start(); - - - - }); - - PamHBox hBox = new PamHBox(); - hBox.setSpacing(5); - hBox.getChildren().addAll(modelLoadIndicator, pathLabel, pamButton); - hBox.setAlignment(Pos.CENTER_RIGHT); // PamButton advButton = new PamButton("", PamGlyphDude.createPamGlyph(MaterialDesignIcon.SETTINGS, PamGuiManagerFX.iconSize)); PamButton advButton = new PamButton("", PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); @@ -240,6 +162,10 @@ public abstract class StandardModelPane extends SettingsPane{ defaultSegmentLenChanged(); + //only set the hop if the user physically changes the toggle switch. This is not included in defaultSegmentLenChanged + //becuase defaultSegmentLenChanged can be called from elsewhere + int defaultsamples = getDefaultSamples(dlClassifierModel, paramsClone); + dlClassifierModel.getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) defaultsamples/2); }); usedefaultSeg.setPadding(new Insets(0,0,0,0)); //there is an issue with the toggle switch which means that it has dead space to the left if @@ -262,7 +188,9 @@ public abstract class StandardModelPane extends SettingsPane(0.0, 1.0, 0.9, 0.1), 1, 0); - detectionSpinner.setPrefWidth(80); + detectionSpinner.setPrefWidth(70); detectionSpinner.setEditable(true); detectionSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + detectionSpinner.setTooltip(new Tooltip("Set the minimum prediciton value for selected classes. If a prediction exceeds this value " + + "a detection will be saved.")); + gridPane.add(new Label(""), 2, 0); speciesIDBox = new CheckComboBox(); gridPane.add(speciesIDBox, 3, 0); - speciesIDBox.setMaxWidth(100); - speciesIDBox.setPrefWidth(100); + //speciesIDBox.setMaxWidth(100); +// speciesIDBox.setPrefWidth(100); speciesIDBox.prefHeightProperty().bind(detectionSpinner.heightProperty()); + speciesIDBox.setMaxWidth(150); //otherwise expands too much if multiple classes selected + speciesIDBox.setTooltip(new Tooltip("Select output classes to use for binary classification. " + + "If the prediction value of a selected class exceeds the minimum prediction threshold then a detection is saved. ")); + validator = new PamValidator(); + + final SimpleIntegerProperty checkItemsCount = new SimpleIntegerProperty(); + + speciesIDBox.getCheckModel().getCheckedItems().addListener((Change c)->{ + checkItemsCount.set(speciesIDBox.getCheckModel().getCheckedItems().size()); + }); + + + validator.createCheck() + .dependsOn("species_box",checkItemsCount) + .withMethod(c -> { + int nChecked = c.get("species_box"); + + if (nChecked==speciesIDBox.getItems().size() && speciesIDBox.getItems().size()>1) { + c.warn("All output class are checked. If one of these classes is noise then PAMGuard will continually detect all sound data..."); + } + + if (nChecked==0) { + c.warn("No output classes are checked for binary classification. PAMGuard will save all prediction values but no detections will be generated"); + } + }) + .decorates(speciesIDBox) + .immediate(); + ; vBoxHolder = new PamVBox(); vBoxHolder.setSpacing(5); - vBoxHolder.getChildren().addAll(classiferInfoLabel, hBox, advSettings, classiferInfoLabel2, gridPane); + vBoxHolder.getChildren().addAll(classifierIcon, advSettings, classiferInfoLabel2, gridPane); mainPane.setCenter(vBoxHolder); @@ -297,14 +256,6 @@ public abstract class StandardModelPane extends SettingsPane getExtensionFilters(); - /** * The default segment len changed. */ @@ -317,13 +268,13 @@ public abstract class StandardModelPane extends SettingsPanei && currParams.classNames[i]!=null) { @@ -508,7 +446,9 @@ public abstract class StandardModelPane extends SettingsPane dlTransfromParams = null; + /** * The DL custom transforms if the default transforms for the model are not being used. */ @@ -97,11 +101,39 @@ public class StandardModelParams implements Serializable, Cloneable { */ public int exampleSoundIndex = 0; + + /** + * Set the example sound that should be associated with parameters. + * @param soundType - the example sound type. + */ + public void setExampleSound(ExampleSoundType soundType) { + ExampleSoundType[] values = ExampleSoundType.values(); + for (int i=0; i getDLWorker() { + return getModelWorker(); + } + + + @Override + public StandardModelParams getDLParams() { + return standardDLParams; + } + + /** + * Get the parameters for the Ketos classifier. + * @param standardDLParams - the Ketos parameters. + */ + public StandardModelParams getKetosParams() { + return standardDLParams; + } + + + @Override + public Serializable getDLModelSettings() { + return standardDLParams; + } + + /** + * Get the KetosWorker. this handles loading and running the Ketos model. + * @return the Ketos worker. + */ + public ArchiveModelWorker getModelWorker() { + if (archiveModelWorker==null) { + archiveModelWorker= new ArchiveModelWorker(); + } + return archiveModelWorker; + } + + + @Override + public String getUnitName() { + return dlControl.getUnitName()+"_" + getName(); + } + + + @Override + public String getUnitType() { + return dlControl.getUnitType()+"_" + getName(); + } + + + @Override + public Serializable getSettingsReference() { + if (standardDLParams==null) { + standardDLParams = makeParams(); + } + + ArrayList dlTransformParams = DLClassiferModel.getDLTransformParams(standardDLParams.dlTransfroms); + + standardDLParams.dlTransfromParams=dlTransformParams; +// System.out.println("KetosParams have been saved. : " + standardDLParams.dlTransfromParams); + return standardDLParams; + } + + + @Override + public long getSettingsVersion() { + return StandardModelParams.serialVersionUID; + } + + + @Override + public boolean restoreSettings(PamControlledUnitSettings pamControlledUnitSettings) { + StandardModelParams newParameters = (StandardModelParams) pamControlledUnitSettings.getSettings(); + if (newParameters != null) { + standardDLParams = newParameters.clone(); + System.out.println("KetosParams have been restored. : " + standardDLParams.dlTransfromParams); + if (standardDLParams.dlTransfromParams != null) { + standardDLParams.dlTransfroms = DLTransformsFactory + .makeDLTransforms((ArrayList) standardDLParams.dlTransfromParams); + } + } + else { + standardDLParams = makeParams(); + } + return true; + } + + /** + *Create the parameters class for the model. This can be overridden for bespoke parameters. + *classes. + * @return a new parameters class object. + */ + public StandardModelParams makeParams() { + return new StandardModelParams(); + } + + @Override + public boolean isModelType(URI uri) { + return super.isModelExtensions(uri); + } + + + /** + * Get the file extensions for the model type. + * @return the file extension. + */ + public String[] getFileExtensions() { + return fileExtensions; + } + + + + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelPane.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelPane.java new file mode 100644 index 00000000..1f493e92 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelPane.java @@ -0,0 +1,62 @@ +package rawDeepLearningClassifier.dlClassification.archiveModel; + +import java.io.File; + +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.ketos.KetosDLParams; + +public class ArchiveModelPane extends StandardModelPane { + + private ArchiveModelClassifier archiveModelClassifier; + + public ArchiveModelPane(ArchiveModelClassifier archiveModelClassifier) { + super(archiveModelClassifier); + this.archiveModelClassifier = archiveModelClassifier; + + } + + @Override + public void newModelSelected(File file) { + + //the model has to set some of the parameters for the UI . + + //A ketos model contains information on the transforms, duration and the class names. + this.setCurrentSelectedFile(file); + + if (this.getParamsClone()==null) { + this.setParamsClone(new KetosDLParams()); + } + + + StandardModelParams params = getParamsClone(); + + +// if (params.dlTransfromParams!=null) { +// System.out.println("Ketos: Decimator: " + params.dlTransfromParams.get(0).params[0]); +// } +// else { +// System.out.println("Ketos:dltransform params is null" + getParamsClone().dlTransfromParams); +// } + + //prep the model with current parameters; + + /** + * Note that the model prep will determine whether new transforms need to be loaded from the + * model or to use the existing transforms in the settings. + */ + archiveModelClassifier.getDLWorker().prepModel(params, archiveModelClassifier.getDLControl()); + + //get the model transforms calculated from the model by the worker and apply them to our temporary params clone. + getParamsClone().dlTransfroms = this.archiveModelClassifier.getDLWorker().getModelTransforms(); + +// if (getParamsClone().defaultSegmentLen!=null) { +// usedefaultSeg.setSelected(true); +// } + + ///set the advanced pane parameters. + getAdvSettingsPane().setParams(getParamsClone()); + + } + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelUI.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelUI.java new file mode 100644 index 00000000..27fde898 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelUI.java @@ -0,0 +1,102 @@ +package rawDeepLearningClassifier.dlClassification.archiveModel; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; + +import PamController.SettingsPane; +import javafx.scene.Node; +import javafx.stage.FileChooser.ExtensionFilter; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; + +public class ArchiveModelUI implements DLCLassiferModelUI { + + /** + * Pane containing controls to set up the OrcaSPot classifier. + */ + private StandardModelPane standardSettingsPane; + + /** + * The sound spot classifier. + */ + private ArchiveModelClassifier archiveModel; + + /** + * The extension filter. + */ + private ArrayList extensionFilters; + + + + /** + * SondSpot classifier. + * @param soundSpotClassifier + */ + public ArchiveModelUI(ArchiveModelClassifier archiveClassifier) { + this.archiveModel=archiveClassifier; + +// extensionFilters = new ArrayList(); +// +// String[] fileExt = archiveModel.getFileExtensions(); +// +// extensionFilters.add(new ExtensionFilter(archiveClassifier.getName(), fileExt)); + + } + + @Override + public SettingsPane getSettingsPane() { + if (standardSettingsPane==null) { + standardSettingsPane = new ArchiveModelPane(archiveModel); + } + return standardSettingsPane; + } + + @Override + public void getParams() { + StandardModelParams params = getSettingsPane().getParams(archiveModel.getDLParams()); + archiveModel.setDLParams(params); + } + + + @Override + public void setParams() { + // System.out.println("Set model params: " + genericModelClassifier.getGenericDLParams().dlTransfromParams.size()); + getSettingsPane().setParams(archiveModel.getDLParams()); + } + + + @Override + public JPanel getSidePanel() { + //no side pane for Ketos just now. + return null; + } + + @Override + public List getModelFileExtensions() { + if (extensionFilters == null) { + extensionFilters = new ArrayList(); + extensionFilters.add(new ExtensionFilter(archiveModel.getName(), archiveModel.getFileExtensions())); + } + return extensionFilters; + } + + /** + * Set the extension filters. + * @param extensionFilters + */ + public void setExtensionFilters(ArrayList extensionFilters) { + this.extensionFilters = extensionFilters; + } + + @Override + public Node getIcon() { + // TODO Auto-generated method stub + return null; + } + + + +} \ No newline at end of file diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java new file mode 100644 index 00000000..d83086d0 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/ArchiveModelWorker.java @@ -0,0 +1,268 @@ +package rawDeepLearningClassifier.dlClassification.archiveModel; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import org.jamdev.jdl4pam.ArchiveModel; +import org.jamdev.jdl4pam.genericmodel.GenericModelParams; +import org.jamdev.jdl4pam.transforms.DLTransform; +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.FreqTransform; +import org.jamdev.jdl4pam.transforms.jsonfile.DLTransformParser2; +import org.jamdev.jdl4pam.transforms.jsonfile.DLTransformsParser; +import org.json.JSONObject; + +import PamUtils.PamArrayUtils; +import PamView.dialog.warn.WarnOnce; +import ai.djl.MalformedModelException; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.GenericModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; + +/** + * + * Runs a Ketos deep learning model and performs feature extraction. + *

    + * + * @author Jamie Macaulay + * + */ +public class ArchiveModelWorker extends GenericModelWorker { + + + /** + * The ketos model + */ + private ArchiveModel dlModel; + + /** + * Thelast loaded model path., + */ + private String currentPath; + + + /** + * SoundSpotWorker constructor. + */ + public ArchiveModelWorker() { + this.setEnableSoftMax(false); + } + + /** + * Prepare the model. + * Note it is important to put a synchonized here or the model loading can fail. + */ + @Override + public synchronized void prepModel(StandardModelParams dlParams, DLControl dlControl) { + //ClassLoader origCL = Thread.currentThread().getContextClassLoader(); + try { + + // get the plugin class loader and set it as the context class loader + // NOTE THAT THIS IS REQUIRED TO MAKE THIS MODULE RUN AS A PLUGIN WHEN THE CLASS FILES + // ARE BUNDLED INTO A FATJAR, HOWEVER THIS WILL STOP THE PLUGIN FROM RUNNING AS A SEPARATE + // PROJECT IN ECLIPSE. So while testing the code and debugging, make sure the + // if (DLControl.PLUGIN_BUILD) { + // PluginClassloader newCL = PamModel.getPamModel().getClassLoader(); + // Thread.currentThread().setContextClassLoader(newCL); + // } + //first open the model and get the correct parameters. + //21/11/2022 - Added a null and filename check here to stop the mdoel reloading everytime PAMGuard hits a new file or + //is stopped or started - this was causing a memory leak. + if (dlModel==null || currentPath ==null || !Paths.get(currentPath).equals(Paths.get(dlParams.modelPath))) { + + + //TODO + // if (ketosModel!=null && ketosModel.getModel()!=null) { + // ketosModel.getModel().close(); + // } + + //System.out.println(Paths.get(genericParams.modelPath)); + this.currentPath = dlParams.modelPath; + dlModel = loadModel(currentPath); + //System.out.println("LOAD A NEW MODEL: "); + //System.out.println(genericModel.getModel().getModelPath().getFileName()); + } + } + catch (Exception e) { + e.printStackTrace(); + //WarnOnce.showWarning(null, "Model Load Error", "There was an error loading the model file.", WarnOnce.OK_OPTION); + } + + try { + + //read the JSON string from the the file. + String jsonString = DLTransformsParser.readJSONString(new File(dlModel.getAudioReprFile())); + + + //convert the JSON string to a parameters object. + GenericModelParams modelParams = makeModelParams( jsonString); + + //important to add this for Ketos models because the JSON string does not necessarily contain and output shape. + //System.out.println("----Default output shape: " + ketosParams.defaultOutputShape + " " + ketosModel.getOutShape()); + if (modelParams.defaultOutputShape==null) { + modelParams.defaultOutputShape = dlModel.getOutShape(); + } + + //HACK there seems to be some sort of bug in ketos where the params input shape is correct but the model input shape is wrong. + if (dlModel.getInputShape()==null || !dlModel.getInputShape().equals(modelParams.defaultInputShape)) { + System.err.println("Model input shape does not match: params: " + modelParams.defaultInputShape + " model: " + dlModel.getInputShape()); + //WarnOnce.showWarning("Model shape", "The model shape does not match the model metadata. \n Metadata shape will be used used.", WarnOnce.OK_OPTION); + dlModel.setInputShape(modelParams.defaultInputShape); + } + + + // ///HACK here for now to fix an issue with dB and Ketos transforms having zero length somehow... + // 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... +// for (int i=0; i + * The model quickly pre checks zip files to make sure there is a settings file inside. + * This means that non compatible zip files are greyed out in the file importer. + *

    + * The model can accept PyTorch or Tensorflow models. + * + * @author Jamie Macaulay + * + */ +public class PamZipModelClassifier extends ArchiveModelClassifier { + + private static final String MODEL_NAME = "PAMGuard model"; + + /** + * The file extensions + */ + private String[] fileExtensions = new String[] {"*.zip"}; + + public PamZipModelClassifier(DLControl dlControl) { + super(dlControl); + } + + @Override + public String getName() { + return MODEL_NAME; + } + + + @Override + public DLStatus setModel(URI zipUri) { + //will change the params if we do not clone. + //first check whether the zip file has the correct model. +// ZipUtils. + + //check that we have some kind of model that we can load here. Do not attempt to load the model if not. + String model; + try { + model = getZipFilePath( zipUri, ".py"); + + if (model==null) model = getZipFilePath( zipUri, ".pb"); + if (model==null) return DLStatus.INCOMPATIBLE_ZIP; + + String settingsFile = getZipFilePath( zipUri, ".pdtf"); + if (settingsFile==null) return DLStatus.INCOMPATIBLE_ZIP; + } catch (ZipException e) { + e.printStackTrace(); + return DLStatus.INCOMPATIBLE_ZIP; + } catch (IOException e) { + e.printStackTrace(); + return DLStatus.INCOMPATIBLE_ZIP; + } + + return super.setModel(zipUri); + } + + /** + * Find the first file within a zip folder that matches a pattern. This peaks into the zip file instead of decompressing it. + * @param zipUri - uri to the zip file + * @param filePattern - the file pattern to match - the file must contain this string. + * @return null if no file found and the file pqth if the file is founf + * @throws ZipException + * @throws IOException + */ + public static String getZipFilePath(URI zipUri, String filePattern) throws ZipException, IOException { + return DLZipUtils.getZipFilePath(new File(zipUri), filePattern); + } + + /** + * Zip test. + * @param args + */ + public static void main(String[] args) { + + String fileName = "D:/Dropbox/PAMGuard_dev/Deep_Learning/Gibbons/gibbon_model_shared/gibbon_model.zip"; + + URI zipUri = new File(fileName).toURI(); + + try { + String pbFile = getZipFilePath( zipUri, ".pb"); + String transformFile = getZipFilePath( zipUri, ".pdtf"); + + System.out.println("pbFile: " + pbFile + " transformFile: " + transformFile); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + + +} + + diff --git a/src/rawDeepLearningClassifier/dlClassification/archiveModel/SimpleArchiveModel.java b/src/rawDeepLearningClassifier/dlClassification/archiveModel/SimpleArchiveModel.java new file mode 100644 index 00000000..3ffebe01 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/archiveModel/SimpleArchiveModel.java @@ -0,0 +1,92 @@ +package rawDeepLearningClassifier.dlClassification.archiveModel; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jamdev.jdl4pam.ArchiveModel; + +import ai.djl.MalformedModelException; + +/** + * A Tensorflow model packaged with a jar file. + * @author Jamie Macaulay + * + */ +public class SimpleArchiveModel extends ArchiveModel { + + + public SimpleArchiveModel(File file) throws MalformedModelException, IOException { + super(file); + } + + @Override + public String getAudioReprRelPath(String zipFolder) { + try { + //System.out.println("SETTINGS PATH: " + getRelFilePath(zipFolder, ".pdtf")); + return getRelFilePath(zipFolder, ".pdtf"); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getModelRelPath(String zipFolder) { + try { + String model = null; + model = getRelFilePath(zipFolder, ".pb"); + if (model==null) model = getRelFilePath(zipFolder, ".py"); + //System.out.println("MODEL PATH: " +model); + return model; + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getModelFolderName() { + return "zip_model"; + } + + /** + * Gte the relative path of file within a zip folder. + * @param zipFolder + * @param fileEnd + * @return + * @throws IOException + */ + private static String getRelFilePath(String zipFolder, String fileEnd) throws IOException { + try (Stream walk = Files.walk(Paths.get(zipFolder))) { + List result = walk + .filter(p -> !Files.isDirectory(p)) // not a directory + .map(p -> p.toString()) // convert path to string + .filter(f -> f.endsWith(fileEnd)) // check end with + .collect(Collectors.toList()); // collect all matched to a List + + if (result.size()>0) { + String firstFile = result.get(0); + + //System.out.println("First file: " +firstFile); + + String relative = firstFile.replace(zipFolder, ""); + + return relative; + } + else return null; + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java new file mode 100644 index 00000000..29b7f66b --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDClassifier.java @@ -0,0 +1,145 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + +import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; + +import PamController.PamControlledUnitSettings; +import PamController.PamSettingManager; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; +import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; + +/** + * A classifier based on the delphinID method which uses whistle contours to predict + * dolphin species. + * + * @author Jamie Macaulay + * + */ +public class DelphinIDClassifier extends StandardClassifierModel { + + + private DelphinIDParams delphinIDParams = new DelphinIDParams(); + + + private DelphinUI delphinUI; + + + private DelphinIDWorker delphinIDWorker; + + + public DelphinIDClassifier(DLControl dlControl) { + super(dlControl); + + //load the previous settings + PamSettingManager.getInstance().registerSettings(this); + } + + @Override + public boolean isModelType(URI model) { + return false; + } + + @Override + public String getName() { + return "delphinID"; + } + + @Override + public DLCLassiferModelUI getModelUI() { + if (delphinUI==null) { + delphinUI = new DelphinUI(this); + } + return delphinUI; + } + + @Override + public Serializable getDLModelSettings() { + return delphinIDParams; + } + + @Override + public String getUnitName() { + return dlControl.getUnitName()+"_" + getName(); + } + + @Override + public String getUnitType() { + return dlControl.getUnitType()+"_" + getName(); + } + + @Override + public Serializable getSettingsReference() { + if (delphinIDParams==null) { + delphinIDParams = new DelphinIDParams(); + } + + ArrayList dlTransformParams = DLClassiferModel.getDLTransformParams(delphinIDParams.dlTransfroms); + + delphinIDParams.dlTransfromParams=dlTransformParams; + + //System.out.println("SoundSpot have been saved. : " + soundSpotParmas.classNames); + 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 threshold. + return false; + } + + + + @Override + public long getSettingsVersion() { + return StandardModelParams.serialVersionUID; + } + + + @Override + public boolean restoreSettings(PamControlledUnitSettings pamControlledUnitSettings) { + + DelphinIDParams newParameters = (DelphinIDParams) pamControlledUnitSettings.getSettings(); + if (newParameters!=null) { + delphinIDParams = (DelphinIDParams) newParameters.clone(); +// System.out.println("DELPHINID have been restored. : " + delphinIDParams.modelPath); + if (delphinIDParams.dlTransfromParams!=null) { + delphinIDParams.dlTransfroms = DLTransformsFactory.makeDLTransforms((ArrayList) delphinIDParams.dlTransfromParams); + } + } + else delphinIDParams = new DelphinIDParams(); + return true; + } + + + + @Override + public DLModelWorker getDLWorker() { + if (delphinIDWorker==null) { + delphinIDWorker = new DelphinIDWorker(); + } + return delphinIDWorker; + } + + @Override + public DelphinIDParams getDLParams() { + return delphinIDParams; + } + + public void setDLParams(DelphinIDParams params) { + this.delphinIDParams=params; + + } + +} \ No newline at end of file diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPane.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPane.java new file mode 100644 index 00000000..7a74c94b --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPane.java @@ -0,0 +1,182 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + + +import java.io.File; + +import PamController.SettingsPane; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ContentDisplay; +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 javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxGlyphs.PamGlyphDude; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamSpinner; +import pamViewFX.fxNodes.PamVBox; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; + +/** + * Settings pane for delphin ID. + * + * @author Jamie Macaulay + * + */ +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); + + 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); + + 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) { + currParams.threshold = decisionSlider.getValue(); + currParams.minDetectionDensity = detectionDensitySpinner.getValue(); + return currParams; + } + + @Override + public void setParams(DelphinIDParams input) { + 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"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @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 new file mode 100644 index 00000000..e655270c --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDParams.java @@ -0,0 +1,18 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; + +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/DelphinIDPrediction.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java new file mode 100644 index 00000000..158f013a --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDPrediction.java @@ -0,0 +1,12 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; + +public class DelphinIDPrediction extends StandardPrediction{ + + public DelphinIDPrediction(float[] prob) { + super(prob); + // TODO Auto-generated constructor stub + } + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java new file mode 100644 index 00000000..eb8e72d6 --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDTest.java @@ -0,0 +1,138 @@ +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 whistlesAndMoans.AbstractWhistleDataUnit; + + +/** + * A delphinID test suite. + * + * @author Jamie Macaulay + * + */ +public class DelphinIDTest { + + + public static class DelphinIDWorkerTest extends DelphinIDWorker { + + private float[][][] lastModelInput; + + + public float[][][] dataUnits2ModelInput(ArrayList 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. + * @param args - the arguments + */ + public static void main(String args[]) { + + double segLen = 4000.; + double segHop = 1000.0; + float sampleRate =96000; + //unix time from sound file + long dataStartMillis = 1340212413000L; + + //path to the .mat containing whistle contours. + 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_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"; + + //create MatFile for saving the image data to. + MatFile matFile = Mat5.newMatFile(); + + //get the whislte contours form a .mat file. + ArrayList whistleContours = DelphinIDUtils.getWhistleContoursMAT(whistleContourPath); + + //segment the whistle detections + ArrayList segments = DelphinIDUtils.segmentWhsitleData(whistleContours, (long) (dataStartMillis+(9.565*1000.)), + 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(); + + System.out.println(); + System.out.print("Segment: " + i + " " + (aSegment.get(0).getSegmentStartMillis()-dataStartMillis)/1000. + "s "); + for (int j=0; j 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; + 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 csvFiles = filelist.getFileList(listOfFiles[i].getAbsolutePath(), ".mat" , true); + + System.out.println("Directory " + listOfFiles[i].getName()); + + + + try { + + File file = new File(listOfFiles[i].getPath() + File.separator + "whistles.mat"); + + if (!file.exists()) { + System.out.println("No whistles.mat for " + listOfFiles[i].getName()); + continue; + } + + 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(); + + 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() + " " + 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); + } + } + + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + } + } + } + + 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 new file mode 100644 index 00000000..bb3ef58a --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/DelphinIDWorker.java @@ -0,0 +1,209 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.DLTransform; +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.jamdev.jpamutils.JamArr; +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.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; + +/** + * + * + * @author Jamie Macaulay + * + */ +public class DelphinIDWorker extends ArchiveModelWorker { + + /** + * Parameters for the whistle to image transform. + */ + private Whistle2ImageParams whistleImageParams; + + /** + * Get the whislte to image parameters. + * + * @return + */ + public Whistle2ImageParams getWhistleImageParams() { + return whistleImageParams; + } + + + @Override + public void prepModel(StandardModelParams dlParams, DLControl dlControl) { + //most of the model prep is done in the perent class. + super.prepModel(dlParams, dlControl); + + //now have to read the whsitle2image transform to get correct parameters for that. + String jsonString = DLTransformsParser.readJSONString(new File(this.getModel().getAudioReprFile())); + whistleImageParams = readWhistleImageTransform(new JSONObject(jsonString)) ; + if (whistleImageParams==null) { + 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.numClasses]; + 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; + + //the number of chunks. + int numChunks = whistleGroups.size(); + + //data input into the model - a stack of spectrogram images. + float[][][] transformedDataStack = new float[numChunks][][]; + + double[][] transformedData2; //spectrogram data + + + for (int j=0; j extensionFilters; + + public DelphinUI(DelphinIDClassifier delphinUIClassifier) { + this.delphinUIClassifier=delphinUIClassifier; + extensionFilters = new ArrayList(); + //import the settings holder + extensionFilters.add(new ExtensionFilter("Zip Model", "*.zip")); + } + + @Override + public SettingsPane getSettingsPane() { + if (delphinIDSettings==null) { + delphinIDSettings = new DelphinIDPane(delphinUIClassifier); + } + return delphinIDSettings; + } + + @Override + public void getParams() { + DelphinIDParams params = getSettingsPane().getParams(delphinUIClassifier.getDLParams()); + delphinUIClassifier.setDLParams(params); + } + + + @Override + public void setParams() { + // System.out.println("Set model params: " + genericModelClassifier.getGenericDLParams().dlTransfromParams.size()); + getSettingsPane().setParams(delphinUIClassifier.getDLParams()); + } + + @Override + public List getModelFileExtensions() { + //this is a bit redundant because zip files + return extensionFilters; + } + + @Override + public JPanel getSidePanel() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Node getIcon() { +// if (icon==null) { +// PamSVGIcon iconMaker= new PamSVGIcon(); +// PamSVGIcon svgsprite; +// try { +// svgsprite = iconMaker.create(getClass().getResource("/Resources/delphinid_logo01.svg").toURI().toURL()); +// icon = svgsprite.getSpriteNode(); +// } catch (MalformedURLException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (URISyntaxException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (Exception e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// return icon; + return null; + } + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java b/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java new file mode 100644 index 00000000..539792da --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/delphinID/Whistles2Image.java @@ -0,0 +1,227 @@ +package rawDeepLearningClassifier.dlClassification.delphinID; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.util.ArrayList; + +import org.jamdev.jdl4pam.transforms.FreqTransform; +import org.jamdev.jpamutils.spectrogram.SpecTransform; + +import PamUtils.PamArrayUtils; +import rawDeepLearningClassifier.segmenter.SegmenterDetectionGroup; +import whistlesAndMoans.AbstractWhistleDataUnit; + +/** + * Transform whistles to an image. + * + * Here we extend the FreqTransform class because it contains lots of image + * transforms that are useful once the whistles have been converted into an + * image. + * + * @author Jamie Macaulay + * + */ +public class Whistles2Image extends FreqTransform { + + /** + * Create an image transform from a whistleGroup. + * @param whistleGroup - the whistle group + * @param params - the paramters for whsilte image - min. freq., max. freq., width in pixels and height in pixels. + */ + public Whistles2Image(SegmenterDetectionGroup whistleGroup, Whistle2ImageParams params) { + super(null, null); + // 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, params.lineWidth); + + this.setSpecTransfrom(specTransform); + this.setFreqlims(params.freqLimits); + + } + + + /** + * Convert a group of whistles + * @param whistleGroup - the whistle groups + * @param freqLimits - the frequency limits + * @return the spectrogram transform. + */ + private SpecTransform whistleGroupToImage(SegmenterDetectionGroup whistleGroup, double[] freqLimits, double[] size, double lineWidth) { + + SpecTransform specTransform = new SpecTransform(); + + /* + * All time-frequency points are saved as a scatterplot with x-axis spanning 0-4 + * seconds in time and y-axis spanning 0-20 kHz in frequency. - Matplotlib was + * used to produce plot (matplotlib.pyplot.scatter) (Point size set at 5 and all + * other values kept at default, including figure size which is saved as 6.4 x + * 4.8 inches as default, axes removed before saving using plt.axes(‘off’)) + **/ + + 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, lineWidth); + + double[][] imaged = new double[(int) size[0]][(int) size[1]]; + + float[] color = new float[3]; + Raster raster = canvas.getData(); + for (int i=0; i whistContours2Points(SegmenterDetectionGroup whistleGroup) { + + ArrayList contours = new ArrayList(); + + AbstractWhistleDataUnit whistleContour; + + long segStart = whistleGroup.getSegmentStartMillis(); + long segEnd = (long) (whistleGroup.getSegmentStartMillis() + whistleGroup.getSegmentDuration()); + + +// for (int i=0; i=segStart && whistleStart=segStart && whistleEnd 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) { + + BufferedImage canvas = new BufferedImage((int) size[0], (int) size[1], BufferedImage.TYPE_INT_RGB); + + double x, y; + for (int j=0; j runModel(ArrayList rawDataUnit) { + public ArrayList runModel(ArrayList rawDataUnit) { ArrayList modelResults = new ArrayList(); for (int i=0; i checkSettingsOK() { + public DLStatus setModel(URI model) { // TODO Auto-generated method stub return null; } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/DLModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/DLModelWorker.java index d8816239..22c87512 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/DLModelWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/DLModelWorker.java @@ -11,17 +11,16 @@ import org.jamdev.jdl4pam.transforms.DLTransformsFactory; import org.jamdev.jdl4pam.utils.DLUtils; import org.jamdev.jpamutils.wavFiles.AudioData; -import PamUtils.PamArrayUtils; +import PamguardMVC.PamDataUnit; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; +import rawDeepLearningClassifier.segmenter.GroupedRawData; /** * * Runs the deep learning model and performs feature extraction. *

    - * * * @author Jamie Macaulay * @@ -42,77 +41,89 @@ public abstract class DLModelWorker { * True to enable normalisation of results using softmax; */ private boolean enableSoftMax = true; + + + /** + * Convert a list of data units to a stack if images. + * @param dataUnits - the data units. + * @param sampleRate - the sample rate + * @param iChan - the channels + * @return a stack of images for input into a deep learning model. + */ + public float[][][] dataUnits2ModelInput(ArrayList dataUnits, float sampleRate, int iChan){ + + @SuppressWarnings("unchecked") + ArrayList rawDataUnits = ( ArrayList) dataUnits; + + //the number of chunks. + int numChunks = rawDataUnits.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 runModel(ArrayList rawDataUnits, float sampleRate, int iChan) { + public synchronized ArrayList runModel(ArrayList dataUnits, float sampleRate, int iChan) { try { - //the number of chunks. - int numChunks = rawDataUnits.size(); - //PamCalendar.isSoundFile(); //create an audio data object from the raw data chunk long timeStart = System.nanoTime(); - - //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 { float[] classOut; for (int i=0; i { } public abstract float[] runModel(float[][][] transformedDataStack); + + /** + * Check whether a model is null or not. + * @return true of the model is null. + */ + public abstract boolean isModelNull(); + public abstract T makeModelResult(float[] prob, double time); diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericAdvPane.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericAdvPane.java index 9b56c877..3d695502 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericAdvPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericAdvPane.java @@ -1,6 +1,7 @@ package rawDeepLearningClassifier.dlClassification.genericModel; import PamController.SettingsPane; +import PamUtils.PamArrayUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -51,10 +52,10 @@ public class GenericAdvPane extends SettingsPane { */ private GridPane classNameHolder; -// /** -// * The text field names. -// */ -// private TextField[] classNameFields; + // /** + // * The text field names. + // */ + // private TextField[] classNameFields; /** * The DL transform pane. @@ -182,14 +183,34 @@ public class GenericAdvPane extends SettingsPane { defualtShapeSwitch = new PamToggleSwitch("Use model default shape"); defualtShapeSwitch.selectedProperty().addListener((obsval, oldval, newval)->{ + + PamArrayUtils.printArray( currentInput.defaultShape); + + if (newval) { //set the correct model shape. if (currentInput!=null && currentInput.defaultShape!=null) { - for (int i=0; i { outShapeSpinners= new Spinner[2]; //set at for now but could be different in future? for (int i=0; i(-1, Integer.MAX_VALUE, 10, 1); outShapeSpinners[i] .setPrefWidth(80); outShapeSpinners[i] .getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); @@ -211,25 +232,25 @@ public class GenericAdvPane extends SettingsPane { defualtOutSwitch = new PamToggleSwitch("Use model default out"); defualtOutSwitch.selectedProperty().addListener((obsval, oldval, newval)->{ - + if (!newval) { PamDialogFX.showWarning("This is generally a very bad idea. If the output shape is not that specified by the model then PAMGuard may through an index out of bounds exception"); } -// System.out.println("DEFAULT OUTPUT" + currentInput); + // System.out.println("DEFAULT OUTPUT" + currentInput); //set the correct model shape. if (currentInput!=null && currentInput.defualtOuput!=null) { - + ///just use the max number? for (int i=0; i { if (setParams) return; genericClassifier.getModelUI().getSettingsPane().setParams(this.getParams(currentInput)); } - + /** * Populate the class name fields. */ @@ -304,8 +325,8 @@ public class GenericAdvPane extends SettingsPane { else { textFields[i].setText(("Class " + i)); } - - + + classNameHolder.add(new Label(("Class " + i)), 0, i); classNameHolder.add(textFields[i], 1, i); } @@ -321,7 +342,7 @@ public class GenericAdvPane extends SettingsPane { @Override public GenericModelParams getParams(GenericModelParams currParams) { -// System.out.println("Generic Adv Pane GET PARAMS:"); + // System.out.println("Generic Adv Pane GET PARAMS:"); if (setParams) return null; @@ -336,11 +357,11 @@ public class GenericAdvPane extends SettingsPane { String[] classNames= new String[textFields.length]; boolean[] binaryClassification = new boolean[currParams.numClasses]; - -// if (currParams.binaryClassification!=null) { -// System.out.println("Current classification: " + currParams.binaryClassification.length -// + " new: " + binaryClassification.length) ; -// } + + // if (currParams.binaryClassification!=null) { + // System.out.println("Current classification: " + currParams.binaryClassification.length + // + " new: " + binaryClassification.length) ; + // } for (int i=0; i { } + int nSpinner=0; + for (int i =0; i { //System.out.println("Input shape: V " + shapeSpinners[i].getValue().intValue()); } - + currParams.outputShape = outShape; currParams.shape = inShape; currParams.classNames = this.getDLControl().getClassNameManager().makeClassNames(classNames); - -// System.out.println("GenericAdvPane: Class names"); -// for (int i=0; i { @Override public void setParams(GenericModelParams input) { - + setParams= true; this.currentInput = input.clone(); //System.out.println("Generic Adv Pane SET PARAMS: " +currentInput ); - -// System.out.println("GenericAdvPane: Class names " + currentInput.numClasses); -// for (int i=0; i { // classNumber.getValueFactory().setValue(currentInput.numClasses); - + //populate the class names field. populateClassNameFields(currentInput.numClasses, input); - + //set the model shape if (currentInput.shape!=null) { for (int i=0; i { if (currentInput.outputShape!=null) { for (int i=0; i { //System.out.println("Set transforms: " + currentInput.dlTransfroms.size()); transfromPane.setExampleSoundIndex(currentInput.exampleSoundIndex); transfromPane.setTransforms(currentInput.dlTransfroms); - -// for (int i=0; i)genericModelParams.dlTransfromParams); - } - - genericModelWorker.setModelTransforms(genericModelParams.dlTransfroms); - - - if (genericModelWorker.getModel()==null) { - genericModelWarning.setWarningMessage("There is no loaded deep learning model. Generic model classifier disabled."); - WarningSystem.getWarningSystem().addWarning(genericModelWarning); - } - - if ((!PamCalendar.isSoundFile() || forceQueue) && !dlControl.isViewer()) { - //for real time only - if (workerThread!=null) { - workerThread.stopTaskThread(); - } - - workerThread = new GenericTaskThread(genericModelWorker); - workerThread.setPriority(Thread.MAX_PRIORITY); - workerThread.start(); - } - } - - @Override - public ArrayList runModel(ArrayList groupedRawData) { - - if (genericModelWorker.getModel()==null) return null; - - // System.out.println("SoundSpotClassifier: PamCalendar.isSoundFile(): " - // + PamCalendar.isSoundFile() + " " + (PamCalendar.isSoundFile() && !forceQueue)); - - - /** - * If a sound file is being analysed then SoundSpot can go as slow as it wants. if used in real time - * then there is a buffer with a maximum queue size. - */ - if ((PamCalendar.isSoundFile() && !forceQueue) || dlControl.isViewer()) { - //run the model - - //System.out.println("GENERICDLCLASSIFIER: Run here!!!"); - ArrayList modelResult = getGenericDLWorker().runModel(groupedRawData, - groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); - //System.out.println("GENERICDLCLASSIFIER: RESULTS!!!: " + modelResult); - - if (modelResult==null) { - genericModelWarning.setWarningMessage("Generic deep learning model returned null"); - WarningSystem.getWarningSystem().addWarning(genericModelWarning); - return null; - } - - for (int i =0; iDLModelWorker.MAX_QUEUE_SIZE) { - //we are not doing well - clear the buffer - workerThread.getQueue().clear(); - } - workerThread.getQueue().add(groupedRawData); - } - return null; - } - - - @Override - public void closeModel() { - //very important to prevent memory leak for long term processing. - if (genericModelWorker.getModel()!=null && genericModelWorker.getModel().getModel()!=null) { - //System.out.println("CLOSE GENERNIC MODEL"); - genericModelWorker.getModel().getModel().close(); - } - } - @Override public int getNumClasses() { @@ -225,36 +117,20 @@ public class GenericDLClassifier implements DLClassiferModel, PamSettings { genericModelParams = new GenericModelParams(); } - ArrayList dlTransformParams = getDLTransformParams(genericModelParams.dlTransfroms); + ArrayList dlTransformParams = DLClassiferModel.getDLTransformParams(genericModelParams.dlTransfroms); genericModelParams.dlTransfromParams=dlTransformParams; - if (genericModelParams.dlTransfromParams!=null) { - System.out.println("Generic settings have been saved. : " + genericModelParams.dlTransfromParams.size()); - } - else { - System.out.println("Generic settings have been saved. : " + null); - - } +// if (genericModelParams.dlTransfromParams!=null) { +// System.out.println("Generic settings have been saved. : " + genericModelParams.dlTransfromParams.size()); +// } +// else { +// System.out.println("Generic settings have been saved. : " + null); +// } return genericModelParams; } - /** - * Get the parameters which can be serialized from transforms. - * @param dlTransfroms- the dl transforms. - */ - public ArrayList getDLTransformParams(ArrayList dlTransfroms) { - ArrayList dlTransformParams = new ArrayList(); - - if (genericModelParams.dlTransfroms==null) return null; - //need to set the generic model params. - for (int i=0; i checkSettingsOK() { +// return checkSettingsOK(genericModelParams, dlControl); +// } - /** - * 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 ArrayList checkSettingsOK() { - return checkSettingsOK(genericModelParams, dlControl); + public boolean isModelType(URI uri) { + return super.isModelExtensions(uri); } - - public static ArrayList checkSettingsOK(StandardModelParams genericModelParams, DLControl dlControl) { - - //TODO - check if model is null. - //check that classifier is selected if continous files. - // - ArrayList warnings = new ArrayList(); - - File file = new File(genericModelParams.modelPath); - if (genericModelParams.modelPath == null || !file.isFile()) { - warnings.add(new PamWarning("Generic classifier", "There is no model loaded - the classifier will not run", 2)); - //if no model then this is ionly message needed for the generic classifier. - return warnings; - } - - //if continous data is selected and all classes are false then this is a potential mistake... - if (dlControl.getSettingsPane().getSelectedParentDataBlock().getUnitClass() == RawDataUnit.class - && PamArrayUtils.isAllFalse(genericModelParams.binaryClassification)) { - warnings.add(new PamWarning("Generic classifier", "There are no prediction classes selected for classification. " - + "Predicitons for each segment will be saved but there will be no detections generated", 1)); - } - - return warnings; - + @Override + public DLModelWorker getDLWorker() { + return this.genericModelWorker; + } + + + + @Override + public StandardModelParams getDLParams() { + return this.genericModelParams; } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericImportExportPane.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericImportExportPane.java index 538dbbc6..4e2efb16 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericImportExportPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericImportExportPane.java @@ -76,9 +76,7 @@ public class GenericImportExportPane extends ImportExportPane { //set the default segment length if available. if (genericModelParams.defaultSegmentLen!=null) { - double sR = genericAdvPane.getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); - //automatically set the default segment length. - genericAdvPane.getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue((int) (sR*genericModelParams.defaultSegmentLen/1000.)); + genericAdvPane.getDLControl().getSettingsPane().setSegmentLength(genericModelParams.defaultSegmentLen); } if (genericModelParams==null) { diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelPane.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelPane.java index f26f1d72..70dc24e2 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelPane.java @@ -1,9 +1,6 @@ package rawDeepLearningClassifier.dlClassification.genericModel; import java.io.File; -import java.util.ArrayList; - -import javafx.stage.FileChooser.ExtensionFilter; import pamViewFX.fxNodes.PamButton; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; @@ -17,10 +14,6 @@ import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams */ public class GenericModelPane extends StandardModelPane { - /** - * The extension filter for sound spot models. - */ - private ArrayList extensionFilters; private GenericAdvPane advPane; @@ -35,13 +28,6 @@ public class GenericModelPane extends StandardModelPane { this.genericDLClassifier = genericDLClassifier; - //must add an additional import settings button. - extensionFilters = new ArrayList(); - - //import the settings holder - extensionFilters.add(new ExtensionFilter("TensorFlow Model", "*.pb")); - extensionFilters.add(new ExtensionFilter("Pytorch Model", "*.pk")); - //this.getVBoxHolder().getChildren().add(2, new Label("Classifier Settings")); usedefaultSeg.setDisable(true); defaultSegBox.setVisible(false); @@ -85,30 +71,36 @@ public class GenericModelPane extends StandardModelPane { @Override public void setParams(StandardModelParams currParams) { +// System.out.println("SET PARAMS GENERIC PANE: " + currParams); + super.setParams(currParams); } + + @Override + public StandardModelParams getParams(StandardModelParams currParams) { +// System.out.println("GET GENERIC PARAMS: " + currParams); + + return super.getParams(currParams); + } @Override public void newModelSelected(File file) { this.setCurrentSelectedFile(file); - this.genericDLClassifier.newModelSelected(file); - + //this.setParamsClone(new GenericModelParams()); + //prep the model with current parameters; - genericDLClassifier.getGenericDLWorker().prepModel(getParams(getParamsClone()), genericDLClassifier.getDLControl()); - //get the model transforms calculated from the model by SoundSpoyWorker and apply them to our temporary paramters clone. - //getParamsClone().dlTransfroms = this.genericDLClassifier.getGenericDLWorker().getModelTransforms(); - ///set the advanced pane parameters. + +// genericDLClassifier.getGenericDLWorker().prepModel(getParams(getParamsClone()), genericDLClassifier.getDLControl()); + //do not have getParam here as it resets some of the setting before set params has been called. + genericDLClassifier.getGenericDLWorker().prepModel(getParamsClone(), genericDLClassifier.getDLControl()); + //now new parameters have been set in the prepModel functions so need to set new params now. getAdvSettingsPane().setParams(getParamsClone()); } - @Override - public ArrayList getExtensionFilters() { - return extensionFilters; - } } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelParams.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelParams.java index ff15ebf0..11c80cdb 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelParams.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelParams.java @@ -5,7 +5,9 @@ import java.util.ArrayList; import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; import org.json.JSONArray; +import PamUtils.PamArrayUtils; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory.ExampleSoundType; import org.jamdev.jdl4pam.transforms.DLTransformsFactory; import org.jamdev.jdl4pam.transforms.DLTransfromParams; @@ -28,7 +30,7 @@ public class GenericModelParams extends StandardModelParams implements Cloneable ArrayList dlTransformParamsArr = new ArrayList(); //waveform transforms. - dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.DECIMATE, 96000.0)); + dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.DECIMATE, 96000.0)); // dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.PREEMPHSIS, preemphases)); dlTransformParamsArr.add(new SimpleTransformParams(DLTransformType.SPECTROGRAM, 256, 100)); //in the python code they have an sfft of 129xN where N is the number of chunks. They then @@ -82,16 +84,76 @@ public class GenericModelParams extends StandardModelParams implements Cloneable // } return newParams; } + + + @Override + public boolean equals(Object o) { + + if (o instanceof GenericModelParams == false) { + /* + * have to add this since the equals function is used in a list comparason + * in the XML output and that list contains objects of different types + * so need to get out HERE or get a classcastexception at the next line + */ + return false; + } + GenericModelParams params = (GenericModelParams) o; + + + //check the transforms are the same. + if (this.dlTransfromParams!=null && params.dlTransfromParams == null) { + return false; + } + if (params.dlTransfromParams!=null && this.dlTransfromParams == null) { + return false; + } + + //iterate through the transofrms and check each one is equal. + if (this.dlTransfromParams!=null) { + + if (this.dlTransfromParams.size()!=dlTransfromParams.size()) { + return false; + } + + for (int i=0; i) params.dlTransfromParams; + + if (params.shape!=null) { + jpamParams.defaultInputShape = new Shape(PamArrayUtils.object2Primitve(params.shape)); + } + if (params.outputShape!=null) { + jpamParams.defaultOutputShape = new Shape(PamArrayUtils.object2Primitve(params.outputShape)); + } + + jpamParams.segLen = params.defaultSegmentLen; + + return jpamParams; + } + + + private static String[] dlClassNames2String(DLClassName[] classNames) { + if (classNames==null) return null; + + String[] classNamesS = new String[classNames.length]; + + int n =0; + for (DLClassName dlName : classNames) { + classNamesS[n] = dlName.className; + n++; + } + + return classNamesS; + } + /** * Write a JSON string to a JSON file. * @param file - the file to write to. @@ -98,6 +186,7 @@ public class GenericModelParser { * @param params - the parameters object. * @return the JSONObject. */ + @Deprecated public static JSONObject getJSonParamsObject(GenericModelParams params) { return getJSonParamsObject( params, new JSONObject()); } @@ -111,6 +200,7 @@ public class GenericModelParser { * @param paramsobject - jsonObject to add params to. * @return the JSONObject. */ + @Deprecated public static JSONObject getJSonParamsObject(GenericModelParams params, JSONObject paramsObject) { //set the class names. JSONObject classInfo = new JSONObject(); @@ -175,18 +265,34 @@ public class GenericModelParser { fileReader.close(); System.out.println(jsonData); - - //DL transforms - ArrayList dlTransforms = DLTransformsParser.parseTransfromParams(jsonData); - //System.out.println("No. parsed transforms: " + dlTransforms.size()); - - //DL transforms - params.dlTransfromParams = dlTransforms; - - //parse the data - params = parseJSOString(jsonData, params, classNameManager); + + //Need to figure out if this is the new or old format. + JSONObject jsonObject = new JSONObject(jsonData); + + + boolean isParamsV2 =false; + + //does the object have a version + if (jsonObject.has("version_info") ) { + JSONObject versionObject = jsonObject.getJSONObject("version_info"); + + //lots of JSON metadata might have a version number so to be absolutely sure... + if (versionObject.has("version")) { + isParamsV2=true; + } + } + if (isParamsV2) { + params = parseJSONObject( jsonObject, params, + classNameManager); + } + else { + params = parseJSONObject_legacy( jsonObject, params, + classNameManager); + } + + return params; } catch (FileNotFoundException e) { @@ -211,6 +317,49 @@ public class GenericModelParser { return null; } + + + /** + * Parse legacy JSON format. + * @param jsonObject - the JSONObject holding data + * @param paramsClone - parameters object to set fields to to set. + * @param classNameManager - the class names manager associated with the parameters. + * @return paramsClone with new settings. + */ + public static GenericModelParams parseJSONObject_legacy(JSONObject jsonObject, GenericModelParams paramsClone, + DLClassNameManager classNameManager) { + + //DL transforms + ArrayList dlTransforms = DLTransformsParser.parseTransfromParams(jsonObject.toString()); + + + //DL transforms + paramsClone.dlTransfromParams = dlTransforms; + + parseJSONMetaData_legacy( jsonObject, paramsClone, classNameManager); + + //System.out.println("No. parsed transforms: " + dlTransforms.size()); + + return paramsClone; + } + + /** + * Parse JSON data containing transforms and model metadata. + * @param jsonObject - the JSONObject holding data + * @param paramsClone - parameters object to set fields to to set. + * @param classNameManager - the class names manager associated with the parameters. + * @return paramsClone with new settings. + */ + public static GenericModelParams parseJSONObject(JSONObject jsonObject, GenericModelParams paramsClone, + DLClassNameManager classNameManager) { + + org.jamdev.jdl4pam.genericmodel.GenericModelParams params = DLTransformParser2.readJSONParams(jsonObject); + + GenericModelParams pgParams = jpamParams2PGParmas(params, classNameManager); + + return pgParams; + + } /** @@ -219,13 +368,9 @@ public class GenericModelParser { * @param params - the parameters. * @return the parameters class with new settings set. */ - public static GenericModelParams parseJSOString(String jsonParamsString, GenericModelParams paramsClone, + public static GenericModelParams parseJSONMetaData_legacy(JSONObject jsonObject, GenericModelParams paramsClone, DLClassNameManager classNameManager) { - JSONObject jsonObject = new JSONObject(jsonParamsString); - - //the segment length information - System.out.println(jsonParamsString); //System.out.println("SEG SIZE: " + jsonObject.getString(SEG_SIZE_STRING) + " " + jsonObject.isNull("size_ms")); diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelUI.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelUI.java index b10e3592..c4f15e4f 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelUI.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelUI.java @@ -1,12 +1,17 @@ package rawDeepLearningClassifier.dlClassification.genericModel; +import java.util.ArrayList; +import java.util.List; + import javax.swing.JPanel; import PamController.SettingsPane; import PamView.dialog.warn.WarnOnce; import PamView.dialog.warn.WarnOnceDialog; import javafx.application.Platform; +import javafx.scene.Node; import javafx.scene.control.Alert.AlertType; +import javafx.stage.FileChooser.ExtensionFilter; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; @@ -28,6 +33,11 @@ public class GenericModelUI implements DLCLassiferModelUI { * The sound spot classifier. */ private GenericDLClassifier genericModelClassifier; + + /** + * The extension filter for sound spot models. + */ + private ArrayList extensionFilters; /** * SondSpot classifier. @@ -35,6 +45,13 @@ public class GenericModelUI implements DLCLassiferModelUI { */ public GenericModelUI(GenericDLClassifier soundSpotClassifier) { this.genericModelClassifier=soundSpotClassifier; + + //must add an additional import settings button. + extensionFilters = new ArrayList(); + + //import the settings holder + extensionFilters.add(new ExtensionFilter("TensorFlow Model", "*.pb")); + extensionFilters.add(new ExtensionFilter("Pytorch Model", "*.pk")); } @Override @@ -56,7 +73,7 @@ public class GenericModelUI implements DLCLassiferModelUI { @Override public void setParams() { - // System.out.println("Set model params: " + genericModelClassifier.getGenericDLParams().dlTransfromParams.size()); +// System.out.println("SE MODEL UI PARAMS: " + genericModelClassifier.getGenericDLParams().dlTransfromParams.size()); getSettingsPane().setParams(genericModelClassifier.getGenericDLParams()); } @@ -68,4 +85,14 @@ public class GenericModelUI implements DLCLassiferModelUI { } + @Override + public List getModelFileExtensions() { + return extensionFilters; + } + + @Override + public Node getIcon() { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java index 2742054c..45863142 100644 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericModelWorker.java @@ -9,6 +9,7 @@ import org.jamdev.jdl4pam.transforms.FreqTransform; import PamModel.PamModel; import PamModel.PamModel.PluginClassloader; +import PamUtils.PamArrayUtils; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; @@ -18,12 +19,12 @@ import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams * @author Jamie Macaulay * */ -public class GenericModelWorker extends DLModelWorker { +public class GenericModelWorker extends DLModelWorker { /** * The generic model */ - private PamGenericModel genericModel; + private GenericModel genericModel; /** * Frequency transform. @@ -32,26 +33,29 @@ public class GenericModelWorker extends DLModelWorker { @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[0][0][0]); +// 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) - results = genericModel.runModel2(transformedDataStack); + results = getModel().runModel(transformedDataStack); else { //run a model if it is waveform info. float[][] waveStack = new float[transformedDataStack.length][]; for (int i=0; i { // System.out.println(Paths.get(genericParams.modelPath)); // System.out.println(Paths.get(genericModel.getModel().getName()).equals(Paths.get(genericParams.modelPath))); // } - + //first open the model and get the correct parameters. - //21/11/2022 - Added a null and filename check here to stop the mdoel reloading everytime PAMGuard hits a new file or + //21/11/2022 - Added a null and filename check here to stop the model reloading everytime PAMGuard hits a new file or //is stopped or started - this was causing a memory leak. if (genericModel==null || !Paths.get(genericModel.getModel().getName()).equals(Paths.get(genericParams.modelPath))) { + + if (genericModel!=null && genericModel.getModel()!=null) { + genericModel.getModel().close(); + } //System.out.println(Paths.get(genericParams.modelPath)); - genericModel = new PamGenericModel(genericParams.modelPath); + + //create a new model. + genericModel = new GenericModel(genericParams.modelPath); //System.out.println("LOAD A NEW MODEL: "); //System.out.println(genericModel.getModel().getModelPath().getFileName()); } + setModelTransforms(genericParams.dlTransfroms); + //is this a waveform or a spectrogram model? - DLTransform transform = genericParams.dlTransfroms.get(genericParams.dlTransfroms.size()-1); - if (transform instanceof FreqTransform) { - freqTransform = true; - } - else { - freqTransform = false; - } + setWaveFreqModel(genericParams); //use softmax or not? String extension = FilenameUtils.getExtension(genericParams.modelPath); @@ -107,10 +113,10 @@ public class GenericModelWorker extends DLModelWorker { this.setEnableSoftMax(true); } - GenericModelParams genericModelParams = new GenericModelParams(); +// GenericModelParams genericModelParams = new GenericModelParams(); - genericModelParams.defaultShape = longArr2Long(genericModel.getInputShape().getShape()); - genericModelParams.defualtOuput = longArr2Long(genericModel.getOutShape().getShape()); + ((GenericModelParams) genericParams).defaultShape = longArr2Long(genericModel.getInputShape().getShape()); + ((GenericModelParams) genericParams).defualtOuput = longArr2Long(genericModel.getOutShape().getShape()); } @@ -123,6 +129,18 @@ public class GenericModelWorker extends DLModelWorker { //Thread.currentThread().setContextClassLoader(origCL); } + + protected void setWaveFreqModel(StandardModelParams genericParams) { + //is this a waveform or a spectrogram model? + DLTransform transform = genericParams.dlTransfroms.get(genericParams.dlTransfroms.size()-1); + if (transform instanceof FreqTransform) { + freqTransform = true; + } + else { + freqTransform = false; + } + } + /** * Convert a long[] array to a Long[] array. * @param inArr - the input long[] array. @@ -140,15 +158,23 @@ public class GenericModelWorker extends DLModelWorker { @Override public void closeModel() { - genericModel.getModel().close(); +// can be very important to prevent memory leak for long term processing. + if (genericModel!=null && genericModel.getModel()!=null) { + genericModel.getModel().close(); + } } /** * Generic model. * @return the generic model. */ - public PamGenericModel getModel() { + public GenericModel getModel() { return genericModel; } + @Override + public boolean isModelNull() { + return genericModel==null; + } + } diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/PamGenericModel.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/PamGenericModel.java deleted file mode 100644 index f81da14d..00000000 --- a/src/rawDeepLearningClassifier/dlClassification/genericModel/PamGenericModel.java +++ /dev/null @@ -1,216 +0,0 @@ -package rawDeepLearningClassifier.dlClassification.genericModel; - - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.apache.commons.io.FilenameUtils; -import org.jamdev.jdl4pam.genericmodel.SpectrogramTranslator; -import org.jamdev.jdl4pam.genericmodel.WaveformTranslator; - -import ai.djl.MalformedModelException; -import ai.djl.Model; -import ai.djl.engine.Engine; -import ai.djl.inference.Predictor; -import ai.djl.ndarray.types.Shape; -import ai.djl.translate.TranslateException; - - -/** - * The generic model. This is implemented in the JPAM library as Generic model - * but having it here gives a little more control without requiring constant changes - * the Maven dependencies. - * - * @author Jamie Macaulay - * - */ -public class PamGenericModel { - - - /** - * The currently loaded model - */ - private Model model; - - /** - * The predictor for the model. - */ - Predictor specPredictor; - - - /** - * Predictor for the model for waveforms. - */ - Predictor wavePredictor; - - - /** - * The input shape from the loaded model. - */ - private Shape inputShape = null; - - /** - * The output shape from the model. - */ - private Shape outShape = null; - - - private SpectrogramTranslator specTranslator; - - - private WaveformTranslator waveTranslator; - - - - public PamGenericModel(String modelPath) throws MalformedModelException, IOException{ - - //System.out.println("NEW GENERIC MODEL:"); - - File file = new File(modelPath); - - //String modelPath = "/Users/au671271/Google Drive/Aarhus_research/PAMGuard_bats_2020/deep_learning/BAT/models/bats_denmark/BAT_4ms_256ft_8hop_128_NOISEAUG_40000_100000_-100_0_256000_JAMIE.pk"; - - Path modelDir = Paths.get(file.getAbsoluteFile().getParent()); //the directory of the file (in case the file is local this should also return absolute directory) - String modelName = file.getName(); - - String extension = FilenameUtils.getExtension(file.getAbsolutePath()); - - System.out.println("Generic Model: Available engines: " + Engine.getAllEngines()); - - Model model; - switch (extension) { - case "pb": - model = Model.newInstance(modelPath, "TensorFlow"); - model.load(modelDir, modelName); - break; - case "py": - model = Model.newInstance(modelName); - model.load(modelDir, modelName); - break; - default: - //will try to load a model automatically - problematic but let's see. - model = Model.newInstance(modelPath); - break; - } - - if (model == null) { - System.err.println("Generic Model: Could not load model: " + modelPath); - } - else { - if (model!=null && model.describeInput()!=null) { - System.out.println("Generic Model: Input: " + model.describeInput().toString()); - inputShape = model.describeInput().get(0).getValue(); - } - if (model!=null && model.describeOutput()!=null) { - System.out.println("Generic Model: Output: " + model.describeOutput().toString()); - outShape = model.describeOutput().get(0).getValue(); - } - - this.model=model; - - specTranslator = new SpectrogramTranslator(inputShape); - - waveTranslator = new WaveformTranslator(model.describeInput()); - - //predictor for the model if using images as input - specPredictor = model.newPredictor(specTranslator); - - //predictor for the model if using - wavePredictor = model.newPredictor(waveTranslator); - } - - } - - /** - * Get the predictor for spectrogram images. - * @return - */ - public Predictor getSpecPredictor() { - return specPredictor; - } - - /*** - * Get the predictor for the waveform input. - * @return the predictor for waveforms. - */ - public Predictor getWavePredictor() { - return wavePredictor; - } - - /** - * Get the model shape for the input. - * @return the input shape. - */ - public Shape getInputShape() { - return inputShape; - } - - /** - * Set the input shape. - * @param inputShape - the input shape. - */ - public void setInputShape(Shape inputShape) { - this.inputShape = inputShape; - specTranslator.setShape(inputShape); - } - - /** - * Get the output shape. The shape is null if the model does not specify shape. - * @return the output shape. - */ - public Shape getOutShape() { - return outShape; - } - - - - /** - * Run the model on spectrogram images - * @param specImage - the spectrogram image [no. batches][image x][image y] - * @return the results - */ - public float[] runModel2(float[][][] specImage) { - try { - float[] results = specPredictor.predict(specImage); - //DLUtils.printArray(results); - return results; - } catch (TranslateException e) { - System.out.println("Error on model: "); - e.printStackTrace(); - } - return null; - } - - - /** - * Run the model on a raw waveform data - * @param specImage - waveform data [no. batches][samples] - * @return the results - */ - public float[] runModel1(float[][] waveform) { - try { - float[] results = wavePredictor.predict(waveform); - //DLUtils.printArray(results); - return results; - } catch (TranslateException e) { - System.out.println("Error on model: "); - e.printStackTrace(); - } - return null; - } - - - public Model getModel() { - return model; - } - - public void setModel(Model model) { - this.model = model; - } - - - - - -} diff --git a/src/rawDeepLearningClassifier/dlClassification/genericModel/GenericPrediction.java b/src/rawDeepLearningClassifier/dlClassification/genericModel/StandardPrediction.java similarity index 79% rename from src/rawDeepLearningClassifier/dlClassification/genericModel/GenericPrediction.java rename to src/rawDeepLearningClassifier/dlClassification/genericModel/StandardPrediction.java index 8e50f89e..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 { /** @@ -39,14 +39,20 @@ public class GenericPrediction implements PredictionResult { public double analysisTime=0; - public GenericPrediction(float[] prob, short[] classNameID, boolean isBinary) { + /** + * Constructor for a typical generic prediciton. + * @param prob - the probability for each class. + * @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 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); } @@ -54,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 49ed2260..d224a013 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier.java @@ -1,24 +1,21 @@ package rawDeepLearningClassifier.dlClassification.ketos; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; +import org.jamdev.jdl4pam.transforms.DLTransformsFactory; +import org.jamdev.jdl4pam.transforms.DLTransfromParams; + import PamController.PamControlledUnitSettings; import PamController.PamSettingManager; -import PamController.PamSettings; -import PamUtils.PamCalendar; import rawDeepLearningClassifier.DLControl; -import rawDeepLearningClassifier.dlClassification.DLClassName; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; -import rawDeepLearningClassifier.dlClassification.DLTaskThread; -import rawDeepLearningClassifier.dlClassification.PredictionResult; +import rawDeepLearningClassifier.dlClassification.StandardClassifierModel; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericDLClassifier; -import rawDeepLearningClassifier.dlClassification.genericModel.GenericPrediction; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; -import warnings.PamWarning; -import warnings.WarningSystem; /** * Classifier which uses deep learning models from Meridian's Ketos framework. @@ -32,12 +29,11 @@ import warnings.WarningSystem; * @author Jamie Macaulay * */ -public class KetosClassifier implements DLClassiferModel, PamSettings { +public class KetosClassifier extends StandardClassifierModel { + + public static String MODEL_NAME = "Ketos"; + - /** - * Reference to the DL contro.. - */ - private DLControl dlControl; /** * Paramters for a Ketos classifier. @@ -55,27 +51,12 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { */ private KetosWorker ketosWorker; - /** - * True to force the classifier to use a queue - can be used for simulating real time operation. - */ - private boolean forceQueue = false; - - /** - * Sound spot warning. - */ - PamWarning ketosWarning = new PamWarning("Ketos_Classifier", "",2); - - /** - * The Ketos worker thread has a buffer so that Ketos models can be run - * in real time without dslowing down the rest of PAMGaurd. - */ - private KetosThread workerThread; - /** * The ketos classifier. */ public KetosClassifier(DLControl dlControl) { + super(dlControl); this.dlControl=dlControl; this.ketosDLParams = new KetosDLParams(); this.ketosUI= new KetosUI(this); @@ -84,151 +65,28 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { } - @Override - public ArrayList runModel(ArrayList groupedRawData) { - if (getKetosWorker().getModel()==null) return null; - - // System.out.println("SoundSpotClassifier: PamCalendar.isSoundFile(): " - // + PamCalendar.isSoundFile() + " " + (PamCalendar.isSoundFile() && !forceQueue)); - - /** - * If a sound file is being analysed then Ketos can go as slow as it wants. if used in real time - * then there is a buffer with a maximum queue size. - */ - if ((PamCalendar.isSoundFile() && !forceQueue) || dlControl.isViewer()) { - //run the model - ArrayList modelResult = getKetosWorker().runModel(groupedRawData, - groupedRawData.get(0).getParentDataBlock().getSampleRate(), 0); - - if (modelResult==null) { - ketosWarning.setWarningMessage("Generic deep learning model returned null"); - WarningSystem.getWarningSystem().addWarning(ketosWarning); - return null; - } - - for (int i =0; iDLModelWorker.MAX_QUEUE_SIZE) { - //we are not doing well - clear the buffer - workerThread.getQueue().clear(); - } - workerThread.getQueue().add(groupedRawData); - } - return null; - } - - - @Override - public void prepModel() { - //System.out.println("PrepModel! !!!"); - getKetosWorker().prepModel(ketosDLParams, dlControl); - - if (!ketosDLParams.useDefaultTransfroms) { - //set custom transforms in the model. - getKetosWorker().setModelTransforms(ketosDLParams.dlTransfroms); - } - - if (getKetosWorker().getModel()==null) { - ketosWarning.setWarningMessage("There is no loaded classifier model. Ketos disabled."); - WarningSystem.getWarningSystem().addWarning(ketosWarning); - } - - - if ((!PamCalendar.isSoundFile() || forceQueue) && !dlControl.isViewer()) { - //for real time only - if (workerThread!=null) { - workerThread.stopTaskThread(); - } - - workerThread = new KetosThread(getKetosWorker()); - workerThread.setPriority(Thread.MAX_PRIORITY); - workerThread.start(); - } - - } - - /** - * The task thread to run Ketos classifier in real time. - * - * @author Jamie Macaulay - * - */ - public class KetosThread extends DLTaskThread { - - KetosThread(DLModelWorker soundSpotWorker) { - super(soundSpotWorker); - } - - @Override - public void newDLResult(GenericPrediction soundSpotResult, GroupedRawData groupedRawData) { - soundSpotResult.setClassNameID(GenericDLClassifier.getClassNameIDs(ketosDLParams)); - soundSpotResult.setBinaryClassification(GenericDLClassifier.isBinaryResult(soundSpotResult, ketosDLParams)); - newResult(soundSpotResult, groupedRawData); - } - - } - - /** - * Send a new result form the thread queue to the process. - * @param modelResult - the model result; - * @param groupedRawData - the grouped raw data. - */ - protected void newResult(GenericPrediction modelResult, GroupedRawData groupedRawData) { - this.dlControl.getDLClassifyProcess().newModelResult(modelResult, groupedRawData); - } - - - @Override - public void closeModel() { - // TODO Auto-generated method stub - - } - @Override public String getName() { - return "Ketos"; + return MODEL_NAME; } @Override public DLCLassiferModelUI getModelUI() { return this.ketosUI; } + @Override - public Serializable getDLModelSettings() { + public DLModelWorker getDLWorker() { + return getKetosWorker(); + } + + + @Override + public StandardModelParams getDLParams() { return ketosDLParams; } - - @Override - public int getNumClasses() { - return ketosDLParams.numClasses; - } - - @Override - public DLClassName[] getClassNames() { - //System.out.println("Ketos Model: " + ketosDLParams.numClasses); - return ketosDLParams.classNames; - } - - @Override - public DLControl getDLControl() { - return dlControl; - } - - @Override - public boolean checkModelOK() { - return getKetosWorker().getModel()!=null; - } - + /** * Get the parameters for the Ketos classifier. * @param ketosDLParams - the Ketos parameters. @@ -245,6 +103,11 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { this.ketosDLParams = ketosDLParams; } + @Override + public Serializable getDLModelSettings() { + return ketosDLParams; + } + /** * Get the KetosWorker. this handles loading and running the Ketos model. * @return the Ketos worker. @@ -259,13 +122,13 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { @Override public String getUnitName() { - return dlControl.getUnitName()+"_Ketos"; + return dlControl.getUnitName()+"_" + MODEL_NAME; } @Override public String getUnitType() { - return dlControl.getUnitType()+"_Ketos"; + return dlControl.getUnitType()+"_" + MODEL_NAME; } @@ -275,7 +138,13 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { if (ketosDLParams==null) { ketosDLParams = new KetosDLParams(); } - //System.out.println("SoundSpot have been saved. : " + soundSpotParmas.modelPath); + + ArrayList dlTransformParams = DLClassiferModel.getDLTransformParams(ketosDLParams.dlTransfroms); + + ketosDLParams.dlTransfromParams=dlTransformParams; + + System.out.println("KetosParams have been saved. : " + ketosDLParams.dlTransfromParams); + return ketosDLParams; } @@ -291,15 +160,20 @@ public class KetosClassifier implements DLClassiferModel, PamSettings { KetosDLParams newParameters = (KetosDLParams) pamControlledUnitSettings.getSettings(); if (newParameters!=null) { ketosDLParams = newParameters.clone(); - //System.out.println("SoundSpot have been restored. : " + soundSpotParmas.modelPath); + System.out.println("KetosParams have been restored. : " + ketosDLParams.dlTransfromParams); + if (ketosDLParams.dlTransfromParams!=null) { + ketosDLParams.dlTransfroms = DLTransformsFactory.makeDLTransforms((ArrayList) ketosDLParams.dlTransfromParams); + } } else ketosDLParams = new KetosDLParams(); return true; } - + + @Override - public ArrayList checkSettingsOK() { - return GenericDLClassifier.checkSettingsOK(ketosDLParams, dlControl); + public boolean isModelType(URI uri) { + //Ketos is easy because there are not many files with a .ktpb extension. + return super.isModelExtensions(uri); } } diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier2.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier2.java new file mode 100644 index 00000000..70269a3f --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosClassifier2.java @@ -0,0 +1,66 @@ +package rawDeepLearningClassifier.dlClassification.ketos; + +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelClassifier; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; + +/** + * Classifier which uses deep learning models from Meridian's Ketos framework. + *

    + * Ketos uses TensorFlow models and packages them inside a zipped .ktpb file + * which contains a JSON file for the transforms and a .pb model. Users can + * select a .ktpb file - PAMGaurd will decompress it, find the JSON file, set up + * the transforms and load the model. + *

    + * Details on Meridians framework can be found at https://meridian.cs.dal.ca/2015/04/12/ketos/ + *

    + * KetosClassifier2 is a more abstracted version of KetosClassifer which inherits most functionality from ArchiveModel + * @author Jamie Macaulay + * + */ +public class KetosClassifier2 extends ArchiveModelClassifier { + + public static String MODEL_NAME = "Ketos"; + + /** + * The file extensions + */ + private String[] fileExtensions = new String[] {"*.ktpb"}; + + + private KetosWorker2 ketosWorker; + + public KetosClassifier2(DLControl dlControl) { + super(dlControl); + } + + @Override + public String[] getFileExtensions() { + return fileExtensions; + } + + @Override + public String getName() { + return MODEL_NAME; + } + + @Override + public ArchiveModelWorker getModelWorker() { + if (ketosWorker==null) { + ketosWorker= new KetosWorker2(); + } + return ketosWorker; + } + + /** + * Create the parameters class for the model. This can be overridden for bespoke parameters. + *classes. + * @return a new parameters class object. + */ + public StandardModelParams makeParams() { + return new KetosDLParams(); + } + + +} diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelPane.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelPane.java index 23d471b7..bd0b8f2b 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelPane.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelPane.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import javafx.stage.FileChooser.ExtensionFilter; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; /** @@ -14,7 +15,7 @@ import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelPane; */ public class KetosModelPane extends StandardModelPane { - private ArrayList extensionFilters; +// private ArrayList extensionFilters; private KetosClassifier ketosClassifier; @@ -23,41 +24,52 @@ public class KetosModelPane extends StandardModelPane { super(soundSpotClassifier); this.ketosClassifier = soundSpotClassifier; - extensionFilters = new ArrayList(); - //import the settings holder - extensionFilters.add(new ExtensionFilter("Ketos Model", "*.ktpb")); } - @Override - public ArrayList getExtensionFilters() { - return extensionFilters; - } + @Override public void newModelSelected(File file) { - + //A ketos model contains information on the transforms, duration and the class names. - this.setCurrentSelectedFile(file); - this.setParamsClone(new KetosDLParams()); + if (this.getParamsClone()==null) { + this.setParamsClone(new KetosDLParams()); + } + + //set the current paramters for the clone + StandardModelParams params = getParams(getParamsClone()); + + +// if (params.dlTransfromParams!=null) { +// System.out.println("Ketos: Decimator: " + params.dlTransfromParams.get(0).params[0]); +// } +// else { +// System.out.println("Ketos:dltransform params is null" + getParamsClone().dlTransfromParams); +// } //prep the model with current parameters; - ketosClassifier.getKetosWorker().prepModel(getParams(getParamsClone()), ketosClassifier.getDLControl()); + + /** + * Note that the model prep will determine whether new transforms need to be loaded from the + * model or to use the existing transforms in the settings. + */ + ketosClassifier.getKetosWorker().prepModel(params, ketosClassifier.getDLControl()); //get the model transforms calculated from the model by SoundSpoyWorker and apply them to our temporary params clone. // System.out.println("Ketos transforms 1: " + this.ketosClassifier.getKetosWorker().getModelTransforms()); getParamsClone().dlTransfroms = this.ketosClassifier.getKetosWorker().getModelTransforms(); - - if (getParamsClone().defaultSegmentLen!=null) { - usedefaultSeg.setSelected(true); - } - //System.out.println("Ketos: " + getParamsClone().dlTransfroms.size()); +// if (getParamsClone().defaultSegmentLen!=null) { +// usedefaultSeg.setSelected(true); +// } + +// System.out.println("---------------------------- "); +// System.out.println("KETOS: new model selected " +getParamsClone().dlTransfromParams.size() + " \n " + getParamsClone()); + ///set the advanced pane parameters. getAdvSettingsPane().setParams(getParamsClone()); - - } } diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelTest.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelTest.java index 5123df1e..60731992 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelTest.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosModelTest.java @@ -30,20 +30,26 @@ public class KetosModelTest { public static void main(String[] args) { //test on a right whale. - //File file = new File("/Volumes/GoogleDrive/My Drive/PAMGuard_dev/Deep_Learning/Meridian/right_whales/for_pamguard/narw.ktpb"); +// File file = new File("/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/Deep_Learning/Ketos/right_whales/for_pamguard/narw.ktpb"); // File file = new File("/Volumes/GoogleDrive-108005893101854397430/My Drive/PAMGuard_dev/Deep_Learning/Meridian/humpback_whales/SOCAL_Mn_Network.ktpb"); //File file = new File("/Volumes/GoogleDrive-108005893101854397430/My Drive/PAMGuard_dev/Deep_Learning/Meridian/orca/kw_detector_v11_5s.ktpb"); //the wav file to test. - //String wavFilePath = "/Volumes/GoogleDrive/My Drive/PAMGuard_dev/Deep_Learning/Meridian/right_whales/for_pamguard/input.wav"; +// String wavFilePath = "/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/Deep_Learning/Ketos/right_whales/for_pamguard/input.wav"; // String wavFilePath = "/Volumes/GoogleDrive-108005893101854397430/My Drive/PAMGuard_dev/Deep_Learning/Meridian/humpback_whales/wav/5353.210403161502.wav"; -// double windowSize = 3.52; +// double[] window = new double[]{0., 3.0}; +// //Minke model +// File file = new File("/Users/au671271/Desktop/Minke_test/Minke_Network_12s.ktpb"); +// String wavFilePath = "/Users/au671271/Desktop/Minke_test/1705_FLAC_1705_20171106_185953_253.wav"; +// double windowSize = 12; - //Minke model - File file = new File("/Users/au671271/Desktop/Minke_test/Minke_Network_12s.ktpb"); - String wavFilePath = "/Users/au671271/Desktop/Minke_test/1705_FLAC_1705_20171106_185953_253.wav"; - double windowSize = 12; +// + File file = new File("/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/Deep_Learning/Ketos/narw_2/hallo-kw-det_v1_test/hallo-kw-det_v1.ktpb"); + String wavFilePath = "/Users/au671271/Library/CloudStorage/GoogleDrive-macster110@gmail.com/My Drive/PAMGuard_dev/Deep_Learning/Ketos/narw_2/hallo-kw-det_v1_test/audio/jasco_reduced.wav"; +// double[] window = new double[]{10., 15.0176}; + double[] window = new double[]{45, 50.0176}; + try { //the ketos model. @@ -58,22 +64,38 @@ public class KetosModelTest { //System.out.println(ketosParams.toString()); System.out.println("Output shape" + ketosParams.defaultOutputShape); + + System.out.println("Input shape" + ketosParams.defaultInputShape); + + + //28-04-2023 seems like there is a BUG in ketos where the input shape reported by the model is incorrect. + ketosModel.setInputShape(ketosParams.defaultInputShape); + //Open wav files. AudioData soundData = DLUtils.loadWavFile(wavFilePath); - soundData = soundData.trim(0, (int) (soundData.getSampleRate()*windowSize)); - + soundData = soundData.trim((int) (soundData.getSampleRate()*window[0]), (int) (soundData.getSampleRate()*window[1])); + System.out.println("Input sample rate is " + soundData.getSampleRate()); + + //generate the transforms. ArrayList transforms = DLTransformsFactory.makeDLTransforms(ketosParams.dlTransforms); - - + + ((WaveTransform) transforms.get(0)).setWaveData(soundData); - DLTransform transform = transforms.get(0); - for (int i=0; i extensionFilters; /** * SondSpot classifier. @@ -30,6 +37,10 @@ public class KetosUI implements DLCLassiferModelUI { */ public KetosUI(KetosClassifier ketosClassifier) { this.ketosClassifier=ketosClassifier; + + extensionFilters = new ArrayList(); + //import the settings holder + extensionFilters.add(new ExtensionFilter("Ketos Model", "*.ktpb")); } @Override @@ -43,8 +54,8 @@ public class KetosUI implements DLCLassiferModelUI { @Override public void getParams() { - KetosDLParams genericParams = (KetosDLParams) getSettingsPane().getParams(ketosClassifier.getKetosParams()); - ketosClassifier.setKetosParams(genericParams); + KetosDLParams ketosParams = (KetosDLParams) getSettingsPane().getParams(ketosClassifier.getKetosParams()); + ketosClassifier.setKetosParams(ketosParams); } @@ -61,4 +72,17 @@ public class KetosUI implements DLCLassiferModelUI { return null; } + @Override + public List getModelFileExtensions() { + return extensionFilters; + } + + @Override + public Node getIcon() { + // TODO Auto-generated method stub + return null; + } + + + } \ No newline at end of file diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java index d39c40ce..a42422db 100644 --- a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker.java @@ -11,12 +11,11 @@ import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; import org.jamdev.jdl4pam.transforms.DLTransformsFactory; import org.jamdev.jdl4pam.transforms.jsonfile.DLTransformsParser; -import PamModel.PamModel; -import PamModel.PamModel.PluginClassloader; +import PamView.dialog.warn.WarnOnce; import rawDeepLearningClassifier.DLControl; import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; -import rawDeepLearningClassifier.dlClassification.genericModel.PamGenericModel; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; /** * @@ -26,7 +25,7 @@ import rawDeepLearningClassifier.dlClassification.genericModel.PamGenericModel; * @author Jamie Macaulay * */ -public class KetosWorker extends DLModelWorker { +public class KetosWorker extends DLModelWorker { /** @@ -51,21 +50,28 @@ public class KetosWorker extends DLModelWorker { * Prepare the model */ public void prepModel(StandardModelParams ketosDLParams, DLControl dlControl) { - ClassLoader origCL = Thread.currentThread().getContextClassLoader(); + //ClassLoader origCL = Thread.currentThread().getContextClassLoader(); try { // get the plugin class loader and set it as the context class loader // NOTE THAT THIS IS REQUIRED TO MAKE THIS MODULE RUN AS A PLUGIN WHEN THE CLASS FILES // ARE BUNDLED INTO A FATJAR, HOWEVER THIS WILL STOP THE PLUGIN FROM RUNNING AS A SEPARATE // PROJECT IN ECLIPSE. So while testing the code and debugging, make sure the - if (DLControl.PLUGIN_BUILD) { - PluginClassloader newCL = PamModel.getPamModel().getClassLoader(); - Thread.currentThread().setContextClassLoader(newCL); - } +// if (DLControl.PLUGIN_BUILD) { +// PluginClassloader newCL = PamModel.getPamModel().getClassLoader(); +// Thread.currentThread().setContextClassLoader(newCL); +// } //first open the model and get the correct parameters. //21/11/2022 - Added a null and filename check here to stop the mdoel reloading everytime PAMGuard hits a new file or //is stopped or started - this was causing a memory leak. if (ketosModel==null || currentPath ==null || !Paths.get(currentPath).equals(Paths.get(ketosDLParams.modelPath))) { + + + //TODO +// if (ketosModel!=null && ketosModel.getModel()!=null) { +// ketosModel.getModel().close(); +// } + //System.out.println(Paths.get(genericParams.modelPath)); this.currentPath = ketosDLParams.modelPath; ketosModel = new KetosModel(new File(ketosDLParams.modelPath)); @@ -84,15 +90,21 @@ public class KetosWorker extends DLModelWorker { String jsonString = DLTransformsParser.readJSONString(new File(ketosModel.getAudioReprFile())); //convert the JSON string to a parameters object. - KetosParams ketosParams = new KetosParams(jsonString); + KetosParams ketosParams = new KetosParams(jsonString); + //important to add this for Ketos models because the JSON string does not necessarily contain and output shape. //System.out.println("----Default output shape: " + ketosParams.defaultOutputShape + " " + ketosModel.getOutShape()); if (ketosParams.defaultOutputShape==null) { ketosParams.defaultOutputShape = ketosModel.getOutShape(); } - - + //HACK there seems to be some sort of bug in ketos where the params input shape is correct but the model input shape is wrong. + if (ketosModel.getInputShape()==null || !ketosModel.getInputShape().equals(ketosParams.defaultInputShape)) { + WarnOnce.showWarning("Model shape", "The model shape does not match the model metadata. \n Metadata shape will be used used.", WarnOnce.OK_OPTION); + ketosModel.setInputShape(ketosParams.defaultInputShape); + } + + ///HACK here for now to fix an issue with dB and Ketos transforms having zero length somehow... for (int i=0; i { } } - //generate the transforms from the KetosParams objectts. + //generate the transforms from the KetosParams objects. ArrayList transforms = DLTransformsFactory.makeDLTransforms(ketosParams.dlTransforms); ///HACK here for now to fix an issue with dB and Ketos transforms having zero length somehow... for (int i=0; i { if (ketosParams.classNames!=null) { ketosDLParams.classNames = dlControl.getClassNameManager().makeClassNames(ketosParams.classNames); + ketosDLParams.numClasses = ketosDLParams.classNames.length; } + else { + //set the number of class names from the default output shape + ketosDLParams.numClasses = (int) ketosParams.defaultOutputShape.get(1); + } + // if (dlParams.classNames!=null) { // for (int i = 0; i { e.printStackTrace(); //WarnOnce.showWarning(null, "Model Metadata Error", "There was an error extracting the metadata from the model.", WarnOnce.OK_OPTION); } - Thread.currentThread().setContextClassLoader(origCL); + //Thread.currentThread().setContextClassLoader(origCL); } @Override public float[] runModel(float[][][] transformedDataStack) { + System.out.println("Model input: " + transformedDataStack.length + " " + transformedDataStack[0].length + " " + transformedDataStack[0][0].length); return ketosModel.runModel(transformedDataStack); } @@ -175,6 +203,7 @@ public class KetosWorker extends DLModelWorker { */ public void closeModel() { //TODO + this.currentPath = null; } @@ -186,5 +215,10 @@ public class KetosWorker extends DLModelWorker { return ketosModel; } + @Override + public boolean isModelNull() { + return ketosModel==null; + } + } diff --git a/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker2.java b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker2.java new file mode 100644 index 00000000..a77c1abd --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/ketos/KetosWorker2.java @@ -0,0 +1,39 @@ +package rawDeepLearningClassifier.dlClassification.ketos; + +import java.io.File; +import java.io.IOException; + +import org.jamdev.jdl4pam.ArchiveModel; +import org.jamdev.jdl4pam.genericmodel.GenericModelParams; +import org.jamdev.jdl4pam.ketos.KetosModel; +import org.jamdev.jdl4pam.ketos.KetosParams; + +import ai.djl.MalformedModelException; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; + +public class KetosWorker2 extends ArchiveModelWorker { + + /** + * Load a model from a file + * @param currentPath- the path to the model. + * @return- the loaded model object. + * @throws MalformedModelException + * @throws IOException + */ + public ArchiveModel loadModel(String currentPath2) throws MalformedModelException, IOException { + return new KetosModel(new File(currentPath2)); + } + + /** + * Create the parameters from a JSON string. + * @param jsonString - the json string. + * @return the paramters. + */ + public GenericModelParams makeModelParams(String jsonString) { + //ketos parameters are non standard and need a bit of extra work to get right. + //This also deal with legacy paramters. + KetosParams params = new KetosParams(jsonString); +// System.out.println(params); + return params; + } +} diff --git a/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java new file mode 100644 index 00000000..df7e049a --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguClassifier.java @@ -0,0 +1,58 @@ +package rawDeepLearningClassifier.dlClassification.koogu; + +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelClassifier; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.DLModelWorker; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; + +/** + * Classifier which uses deep learning models from Koogus' framework. + *

    + * Koogu uses TensorFlow models and packages them inside a zipped .kgu file + * which contains a standard JSON file for the transforms and a .pb model. Users can + * select a .kgu file - PAMGaurd will decompress it, find the JSON file, set up + * the transforms and load the model. + *

    + * Details on Koogu framework can be found athttps://github.com/shyamblast/Koogu?tab=readme-ov-file + * @author Jamie Macaulay + * + */ +public class KooguClassifier extends ArchiveModelClassifier { + + + public static String MODEL_NAME = "Koogu"; + + /** + * The file extensions + */ + private String[] fileExtensions = new String[] {"*.kgu"}; + + private KooguModelWorker kooguWorker; + + + public KooguClassifier(DLControl dlControl) { + super(dlControl); + } + + @Override + public String[] getFileExtensions() { + return fileExtensions; + } + + @Override + public String getName() { + return MODEL_NAME; + } + + @Override + public ArchiveModelWorker getModelWorker() { + if (kooguWorker==null) { + kooguWorker= new KooguModelWorker(); + } + return kooguWorker; + } + + + +} \ No newline at end of file diff --git a/src/rawDeepLearningClassifier/dlClassification/koogu/KooguModelWorker.java b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguModelWorker.java new file mode 100644 index 00000000..4026575a --- /dev/null +++ b/src/rawDeepLearningClassifier/dlClassification/koogu/KooguModelWorker.java @@ -0,0 +1,31 @@ +package rawDeepLearningClassifier.dlClassification.koogu; + +import java.io.File; +import java.io.IOException; + +import org.jamdev.jdl4pam.ArchiveModel; +import org.jamdev.jdl4pam.koogu.KooguModel; + +import ai.djl.MalformedModelException; +import rawDeepLearningClassifier.dlClassification.archiveModel.ArchiveModelWorker; + +/** + * The Koogu model worker. + * + * @author Jamie Macaulay + * + */ +public class KooguModelWorker extends ArchiveModelWorker { + + + /** + * Load a model from a file + * @param currentPath- the path to the model. + * @return- the loaded model object. + * @throws MalformedModelException + * @throws IOException + */ + public ArchiveModel loadModel(String currentPath2) throws MalformedModelException, IOException { + return new KooguModel(new File(currentPath2)); + } +} diff --git a/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifier.java b/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifier.java index 74f70941..68fb845a 100644 --- a/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifier.java +++ b/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifier.java @@ -1,6 +1,7 @@ package rawDeepLearningClassifier.dlClassification.orcaSpot; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -9,14 +10,16 @@ import java.util.concurrent.atomic.AtomicBoolean; import PamController.PamControlledUnitSettings; import PamController.PamSettingManager; import PamController.PamSettings; +import PamguardMVC.PamDataUnit; import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.DLStatus; import rawDeepLearningClassifier.dlClassification.DLClassName; import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import rawDeepLearningClassifier.dlClassification.DLDataUnit; import rawDeepLearningClassifier.dlClassification.DLDetection; import rawDeepLearningClassifier.dlClassification.PredictionResult; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; -import rawDeepLearningClassifier.segmenter.SegmenterProcess.GroupedRawData; +import rawDeepLearningClassifier.segmenter.GroupedRawData; import warnings.PamWarning; /** @@ -97,18 +100,18 @@ public class OrcaSpotClassifier implements DLClassiferModel, PamSettings { } @Override - public ArrayList runModel(ArrayList rawDataUnits) { + public ArrayList runModel(ArrayList rawDataUnits) { - for (GroupedRawData groupedRawData: rawDataUnits){ - if (queue.size()>MAX_QUEUE_SIZE) { - //we are not doing well - clear the buffer - queue.clear(); - } - queue.add(groupedRawData); - - - } - this.orcaSpotUI.notifyUpdate(-1); +// for (PamDataUnit groupedRawData: rawDataUnits){ +// if (queue.size()>MAX_QUEUE_SIZE) { +// //we are not doing well - clear the buffer +// queue.clear(); +// } +// queue.add(groupedRawData); +// +// +// } +// this.orcaSpotUI.notifyUpdate(-1); return null; } @@ -339,14 +342,28 @@ public class OrcaSpotClassifier implements DLClassiferModel, PamSettings { } @Override - public boolean checkModelOK() { - return true; + public DLStatus getModelStatus() { + return null; + } + +// @Override +// public ArrayList checkSettingsOK() { +// // TODO Auto-generated method stub +// return null; +// } + + @Override + public boolean isModelType(URI uri) { + // TODO Auto-generated method stub + return false; } @Override - public ArrayList checkSettingsOK() { + public DLStatus setModel(URI model) { // TODO Auto-generated method stub return null; } + + } diff --git a/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifierUI.java b/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifierUI.java index 6917d124..fae4e0b9 100644 --- a/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifierUI.java +++ b/src/rawDeepLearningClassifier/dlClassification/orcaSpot/OrcaSpotClassifierUI.java @@ -1,8 +1,12 @@ package rawDeepLearningClassifier.dlClassification.orcaSpot; +import java.util.List; + import javax.swing.SwingUtilities; import PamController.SettingsPane; +import javafx.scene.Node; +import javafx.stage.FileChooser.ExtensionFilter; import rawDeepLearningClassifier.layoutFX.DLCLassiferModelUI; /** @@ -70,4 +74,16 @@ public class OrcaSpotClassifierUI implements DLCLassiferModelUI { return orcaSpotSidePanel; } + @Override + public List getModelFileExtensions() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Node getIcon() { + // TODO Auto-generated method stub + return null; + } + } diff --git a/src/rawDeepLearningClassifier/layoutFX/DLCLassiferModelUI.java b/src/rawDeepLearningClassifier/layoutFX/DLCLassiferModelUI.java index a1845d78..30a5e4ef 100644 --- a/src/rawDeepLearningClassifier/layoutFX/DLCLassiferModelUI.java +++ b/src/rawDeepLearningClassifier/layoutFX/DLCLassiferModelUI.java @@ -1,7 +1,11 @@ package rawDeepLearningClassifier.layoutFX; +import java.util.List; + import javax.swing.JPanel; +import javafx.scene.Node; +import javafx.stage.FileChooser.ExtensionFilter; import PamController.SettingsPane; /** @@ -27,11 +31,25 @@ public interface DLCLassiferModelUI { */ public void setParams(); + + /** + * If using a file dialog to search for + * @return the file extensions (if any) for this type of classifier + */ + public List getModelFileExtensions(); + /** * Get a side panel specific to the classifier. * @return the side panel. */ public JPanel getSidePanel(); + /** + * Get an icon for the model. Note that this can be null in + * which case the icon in the UI will be a label with the mdoel name. + * @return teh model icon. + */ + public Node getIcon(); + } diff --git a/src/rawDeepLearningClassifier/layoutFX/DLModelSelectPane.java b/src/rawDeepLearningClassifier/layoutFX/DLModelSelectPane.java new file mode 100644 index 00000000..c69bd5cb --- /dev/null +++ b/src/rawDeepLearningClassifier/layoutFX/DLModelSelectPane.java @@ -0,0 +1,614 @@ +package rawDeepLearningClassifier.layoutFX; + +import java.io.File; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.controlsfx.control.PopOver; + +import ai.djl.Device; +import javafx.concurrent.Task; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; + +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.stage.FileChooser; +import javafx.stage.FileChooser.ExtensionFilter; +import javafx.geometry.Insets; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxGlyphs.PamGlyphDude; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamTextField; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.DLStatus; +import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.DefaultModels; +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.layoutFX.defaultModels.DefaultModelPane; + + +/** + * A pane which allows users to select a model and then loads the model on + * on a different thread, showing a progress indicator. + * + * Models could be potentially selected from + * 1) A file (implemented) + * 2) A URL (not implemented) + * 3) A default list of models. + * + * + * @author Jamie Macaulay + * + */ +public class DLModelSelectPane extends PamBorderPane { + + /** + * The directory chooser. + */ + private FileChooser fileChooser; + + /** + * Currently selected file. + */ + protected URI currentSelectedFile = new File(System.getProperty("user.home")).toURI(); + + /** + * The label showing the path to the file. + */ + private Label pathLabel; + + + /** + * The label showing the path to file. + */ + private ProgressIndicator modelLoadIndicator; + + /** + * The type of classifier selected + */ + private Label classiferInfoLabel; + + /** + * The DL control. + */ + private DLControl dlControl; + + /** + * The current classifier model. + */ + DLClassiferModel currentClassifierModel; + + /** + * The default models. + */ + private DefaultModels defaultModels; + + /** + * Pop over + */ + private PopOver urlPopOver; + + private TextField uriTextField; + + private DLSettingsPane rawDLSettingsPane; + + private DefaultModelPane defaultModelPane; + + + public DLModelSelectPane(DLSettingsPane rawDLSettingsPane) { + this.rawDLSettingsPane=rawDLSettingsPane; + this.dlControl=rawDLSettingsPane.getDLControl(); + this.setCenter(createDLSelectPane()); + //the directory chooser. + fileChooser = new FileChooser(); + fileChooser.setTitle("Classifier Model Location"); + } + + + public Pane createDLSelectPane() { + + + classiferInfoLabel = new Label(" Classifier"); + //PamGuiManagerFX.titleFont2style(classiferInfoLabel); + Font font= Font.font(null, FontWeight.BOLD, 11); + classiferInfoLabel.setFont(font); + + /**Basic classifier info**/ + pathLabel = new Label("No classifier file selected"); + // PamButton pamButton = new PamButton("", PamGlyphDude.createPamGlyph(MaterialDesignIcon.FILE, PamGuiManagerFX.iconSize)); + PamButton pamButton = new PamButton("", PamGlyphDude.createPamIcon("mdi2f-file", PamGuiManagerFX.iconSize)); + pathLabel.setMinWidth(100); + + modelLoadIndicator = new ProgressIndicator(-1); + modelLoadIndicator.setVisible(false); + modelLoadIndicator.prefHeightProperty().bind(pamButton.heightProperty().subtract(3)); + + pamButton.setMinWidth(30); + pamButton.setTooltip(new Tooltip("Load a model from a file")); + + + pamButton.setOnAction((action)->{ + + fileChooser.getExtensionFilters().clear(); + fileChooser.getExtensionFilters().addAll(getExtensionFilters()); + + + Path path = Paths.get(currentSelectedFile); + if(path!=null && Files.exists(path, LinkOption.NOFOLLOW_LINKS)) { + fileChooser.setInitialDirectory(new File(new File(currentSelectedFile).getParent())); + } + else { + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + } + + File file = fileChooser.showOpenDialog(null); + + if (file==null) { + return; + } + + loadNewModel(file.toURI()); + + }); + + + PamHBox urlBox = new PamHBox(); + urlBox.setSpacing(5); + + PamButton download = new PamButton(); + download.setTooltip(new Tooltip("Download the model from set URL")); + download.setGraphic(PamGlyphDude.createPamIcon("mdi2d-download", PamGuiManagerFX.iconSize)); + download.setDisable(true); + + download.setOnAction((action)->{ + + if (uriTextField.getText().isEmpty()) return; + + try { + URI uri = new URI(uriTextField.getText()); + + loadNewModel(uri); + + } catch (Exception e) { + PamDialogFX.showWarning("A valid URL could not be created"); + e.printStackTrace(); + } + + }); + + final String urlHelpText = "Enter the internet address (URL) for the model"; + + uriTextField = new PamTextField(); + uriTextField.prefHeightProperty().bind(download.heightProperty()); + uriTextField.setPrefWidth(300); + uriTextField.setText("Enter the internet address (URL) for the model"); + uriTextField.textProperty().addListener((obsVal, oldVal, newVal)->{ + if (uriTextField.textProperty().get().isBlank() || uriTextField.textProperty().get().equals(urlHelpText)) { + download.setDisable(true); + } + else { + download.setDisable(false); + } + }); + + urlBox.getChildren().addAll(uriTextField, download); + + PamVBox urlHolder = new PamVBox(); + urlHolder.setPadding(new Insets(5,5,5,5)); + urlHolder.setSpacing(5); + urlHolder.getChildren().addAll(new Label("Download Model"), urlBox); + + urlPopOver = new PopOver(); + urlPopOver.setContentNode(urlHolder); + + PamButton urlButton = new PamButton("", PamGlyphDude.createPamIcon("mdi2l-link-variant", PamGuiManagerFX.iconSize)); + urlButton.setTooltip(new Tooltip("Load a model from a URL")); + urlButton.setOnAction((action)->{ + urlPopOver.show(urlButton); + }); + + + PamButton defaults = new PamButton(); + defaults.setTooltip(new Tooltip("Default models")); + defaults.setGraphic(PamGlyphDude.createPamIcon("mdi2d-dots-vertical", PamGuiManagerFX.iconSize)); + defaults.setTooltip(new Tooltip("Load a default model")); + +// for (DefualtModel defaultmodel: defaultModels.getDefaultModels()) { +// defaults.getItems().add(new MenuItem(defaultmodel.name)); +// } +// defaults.prefHeightProperty().bind(urlButton.heightProperty()); + + defaultModelPane = new DefaultModelPane(this.dlControl.getDefaultModelManager()); + defaultModelPane.setPadding(new Insets(5,5,5,5)); + defaultModelPane.defaultModelProperty().addListener((obsVal, oldVal, newVal)->{ + if (newVal!=oldVal) { + //a default model needs to be loaded. + Task task = loadNewModel(newVal.getModelURI()); + + task.setOnSucceeded((val)->{ + if (currentClassifierModel!=null) { + + //set the parameters from the classifer model to have correct + //transfroms etc. + newVal.setParams(currentClassifierModel.getDLModelSettings()); + + //set the correct classifier pane. + rawDLSettingsPane.setClassifierPane(); + + //need to make sure the classifier pane is updated + currentClassifierModel.getModelUI().setParams(); + + //need to update the segment length... + if (currentClassifierModel.getDLModelSettings() instanceof StandardModelParams) { + double segLen = ((StandardModelParams) currentClassifierModel.getDLModelSettings()).defaultSegmentLen; + this.rawDLSettingsPane.setSegmentLength(segLen); + //set to half the hop size too + this.rawDLSettingsPane.setHopLength(segLen/2); + } + + } + else { + //this should never happen unless there is no internet + System.err.println("Default model failed ot load: "+ newVal); + } + }); + } + }); + + PopOver defualtModelPopOver = new PopOver(); + defualtModelPopOver.setContentNode(defaultModelPane); + + defaults.setOnAction((action)->{ + defualtModelPopOver.show(defaults); + }); + + PamHBox hBox = new PamHBox(); + hBox.setSpacing(5); + hBox.getChildren().addAll(modelLoadIndicator, pathLabel, urlButton, pamButton, defaults); + hBox.setAlignment(Pos.CENTER_RIGHT); + PamHBox.setHgrow(pathLabel, Priority.ALWAYS); + + return hBox; + } + + /** + * Load a new model on a seperate thread. + * @param uri - the uri to the model. + */ + public Task loadNewModel(URI uri) { + // separate non-FX thread - load the model + //on a separate thread so we can show a moving load + //bar on the FX thread. Otherwise the GUI locks up + //whilst stuff is loaded. + if (uri==null) return null; + +// pathLabel.setText("Loading model..."); + modelLoadIndicator.setVisible(true); + + Task task = new LoadTask(uri); + + modelLoadIndicator.progressProperty().bind(task.progressProperty()); + + pathLabel.setGraphic(null); //remove error icon if there is one. + pathLabel.textProperty().bind(task.messageProperty()); + + + //start the load taks. + Thread th = new Thread(task); + th.setDaemon(true); + th.start(); + + return task; + + + + // Thread th = new Thread(task); + // th.setDaemon(true); + // th.start(); + // + // new Thread() { + // // runnable for that thread + // public void run() { + // try { + // + // //either downloads the model or instantly returns the model path. + // URI modelPath = dlControl.getDownloadManager().downloadModel(uri); + // + // newModelSelected(modelPath); + // currentSelectedFile = modelPath; + // Thread.sleep(1000); //just show the user something happened if model loading is rapid. + // } catch (InterruptedException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // + // Platform.runLater(new Runnable() { + // + // public void run() { + // try { + // rawDLSettingsPane.setClassifierPane(); + // modelLoadIndicator.setVisible(false); + // updatePathLabel(); + // } + // catch (Exception e) { + // e.printStackTrace(); + // } + // } + // + // }); + // } + // }.start(); + } + + + /** + * A new model has been selected + * @param the load status of the model. + */ + private DLStatus newModelSelected(URI file) { + + if (file == null) { + currentClassifierModel=null; + return DLStatus.FILE_NULL; + } + + this.currentClassifierModel = this.dlControl.getDlClassifierChooser().selectClassiferModel(file); + + System.out.println("New classifier model selected!: " + currentClassifierModel); + if (currentClassifierModel!=null) { + + try { + //we are loading model from a file - anything can happen so put in a try catch. + DLStatus status = currentClassifierModel.setModel(file); + + + if (status.isError()) { + System.err.println("Model load failed: " + currentClassifierModel.getModelStatus()); + currentClassifierModel=null; + return status; + } + + return DLStatus.MODEL_LOAD_SUCCESS; + } + catch (Exception e) { + e.printStackTrace(); + currentClassifierModel=null; + return DLStatus.MODEL_LOAD_FAILED; + } + } + else { + currentClassifierModel=null; + return DLStatus.MODEL_LOAD_FAILED; + } + } + + private void showWarningDialog(DLStatus status) { + this.rawDLSettingsPane.showWarning(status); +// PamDialogFX.showError(status.getName(), status.getDescription()); + } + + + /** + * Create an error icon based on the status message. + * @param status - the status + * @return error icon or null of the status is not an error. + */ + private Node createErrorIcon(DLStatus status) { + Node decoration = null; + if (status.isError()){ + decoration = PamGlyphDude.createPamIcon("mdi2c-close-circle-outline", Color.RED, 10); + } + else if (status.isWarning()) { + decoration = PamGlyphDude.createPamIcon("mdi2c-close-circle-outline", Color.ORANGE, 10); + } + return decoration; + } + + /** + * Update the path label and tool tip text + */ + protected void updatePathLabel(DLStatus status) { + + /** + * This a bit complicated. We want the label to show errors if they have occured and warning to prompt the + * user to do things. The sequence of this is quite important to get right. + */ + + pathLabel.setGraphic(null); + + if (currentClassifierModel == null) { + //no frameowrk could be selected for the model. + pathLabel.setGraphic(createErrorIcon(DLStatus.NO_MODEL_LOADED)); + pathLabel.setText("No classifier model loaded: Select model"); + pathLabel.setTooltip(new Tooltip("Use the browse button/ URI botton to select a model or select a default model")); + } + else if (currentClassifierModel.getModelStatus().isError()) { + pathLabel.setGraphic(createErrorIcon(currentClassifierModel.getModelStatus())); + pathLabel.setText(currentClassifierModel.getModelStatus().getName()); + pathLabel.setTooltip(new Tooltip(currentClassifierModel.getModelStatus().getDescription())); + } + else if (status.isError()) { + pathLabel.setGraphic(createErrorIcon(status)); + pathLabel.setText(status.getName()); + pathLabel.setTooltip(new Tooltip(status.getDescription())); + } + else { + pathLabel .setText(new File(this.currentSelectedFile).getName()); + //show a warning icon if needed. + String tooltip = ""; + try { + tooltip += (this.currentSelectedFile.getPath() + + "\n" +" Processor CPU " + Device.cpu() + " " + Device.gpu()); + tooltip+="\n"; + } + catch (Exception e) { + //sometimes get an error here for some reason + //does not make a difference other than tooltip. + System.err.println("StandardModelPane: Error getting the default device!"); + } + if (status.isWarning()) { + tooltip+="Warning: " + status.getDescription(); + } + pathLabel.setTooltip(new Tooltip(tooltip)); + } + + } + + /** + * Get the extension filters for the file selection dialog. + * @return the extension filters for the model file dialog. + */ + + public ArrayList getExtensionFilters() { + + + ArrayList extensionFilters = new ArrayList(); + + for (DLClassiferModel dlModel: dlControl.getDLModels()) { + //System.out.println("Model: " + dlModel.getModelUI()); + + if (dlModel.getModelUI()!=null) { + for (ExtensionFilter extFilter: dlModel.getModelUI().getModelFileExtensions()){ + //System.out.println("Extensions: " + extFilter.getExtensions()); + extensionFilters.addAll(extFilter.getExtensions()); + } + } + } + + //Now we don't really want lots of extension filters + ArrayList dlExtFilter = new ArrayList(); + dlExtFilter.add(new ExtensionFilter("Deep Learning Models", extensionFilters)); + + return dlExtFilter ; + } + + /** + * Task for loading and/or downloading deep learning models. + */ + class LoadTask extends Task { + + private URI uri; + + + + public LoadTask(URI uri) { + this.uri=uri; + + + //TODO - not the best as some other part of the program could be using download listeners... + dlControl.getDownloadManager().clearDownloadListeners(); + + dlControl.getDownloadManager().addDownloadListener((status, bytesDownLoaded)->{ + updateMessage( status, bytesDownLoaded); + }); + + } + + private void updateMessage(DLStatus status, long bytesDownLoaded) { + //the updates have their own messages but let's take some more control here. + this.updateProgress(-1, 1); //set to intermediate + //System.out.println("Status: " + status); + switch (status) { + case CONNECTION_TO_URL: + this.updateMessage("Checking URL"); + break; + case DOWNLOADING: + this.updateMessage(String.format("Download %.2f MB", ((double) bytesDownLoaded)/1024./1024.)); + break; + case DOWNLOAD_FINISHED: + this.updateMessage("Download complete"); + break; + case DOWNLOAD_STARTING: + this.updateMessage("Download starting"); + break; + default: + this.updateMessage(status.getDescription()); + break; + } + } + + + @Override + public DLStatus call() throws Exception { + try { + this.updateMessage("Loading model..."); + + //either downloads the model or instantly returns the model path if it's a file. + //The download listener will update progress if this starts downloading a file. + URI modelPath = dlControl.getDownloadManager().downloadModel(uri); + + + if (modelPath==null) { + return DLStatus.MODEL_DOWNLOAD_FAILED; + } + + this.updateMessage("Loading model..."); + + DLStatus result = newModelSelected(modelPath); + + currentSelectedFile = modelPath; + Thread.sleep(1000); //just show the user something happened if model loading is rapid. + + return result; + + } catch (Exception e) { + System.out.println("UNABLE TO LOAD MODEL"); + currentClassifierModel=null; //this will reset the pane + e.printStackTrace(); + return DLStatus.MODEL_LOAD_FAILED; + } + } + + private void finishedLoading() { + + if (this.getValue().isError()) { + showWarningDialog(this.getValue()); + } + + rawDLSettingsPane.setClassifierPane(); + modelLoadIndicator.setVisible(false); + + //important to stop set a bound property exception. + pathLabel.textProperty().unbind(); + modelLoadIndicator.progressProperty().unbind(); + + updatePathLabel(this.getValue()); + } + + @Override protected void succeeded() { + super.succeeded(); + finishedLoading(); + updateMessage("Done!"); + } + + @Override protected void cancelled() { + super.cancelled(); + finishedLoading(); + updateMessage("Cancelled!"); + } + + @Override protected void failed() { + super.failed(); + finishedLoading(); + updateMessage("Failed!"); + } + }; + + +} diff --git a/src/rawDeepLearningClassifier/layoutFX/RawDLSettingsPane.java b/src/rawDeepLearningClassifier/layoutFX/DLSettingsPane.java similarity index 60% rename from src/rawDeepLearningClassifier/layoutFX/RawDLSettingsPane.java rename to src/rawDeepLearningClassifier/layoutFX/DLSettingsPane.java index c867654d..b2c8c0e4 100644 --- a/src/rawDeepLearningClassifier/layoutFX/RawDLSettingsPane.java +++ b/src/rawDeepLearningClassifier/layoutFX/DLSettingsPane.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import org.controlsfx.control.PopOver; -import PamController.FlipSettingsPane; +import PamController.PamGUIManager; import PamController.SettingsPane; import PamDetection.RawDataUnit; import PamView.dialog.warn.WarnOnce; @@ -16,15 +16,14 @@ import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; import javafx.scene.control.Label; -import javafx.scene.control.PopupControl; import javafx.scene.control.Spinner; import javafx.scene.control.Tooltip; import javafx.scene.control.Alert.AlertType; +import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; -import javafx.stage.Stage; +import javafx.scene.layout.Priority; import pamViewFX.PamGuiManagerFX; import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; @@ -37,9 +36,10 @@ import pamViewFX.fxNodes.pamDialogFX.PamDialogFX; import pamViewFX.fxNodes.utilityPanes.GroupedSourcePaneFX; import pamViewFX.fxNodes.utilityPanes.PamToggleSwitch; import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.DLStatus; import rawDeepLearningClassifier.RawDLParams; -import rawDeepLearningClassifier.dlClassification.DLClassiferModel; import warnings.PamWarning; +import whistlesAndMoans.ConnectedRegionDataUnit; /** * The settings pane. @@ -47,11 +47,11 @@ import warnings.PamWarning; * @author Jamie Macaulay * */ -public class RawDLSettingsPane extends SettingsPane{ +public class DLSettingsPane extends SettingsPane{ public static double MAX_WIDTH = 270; - + /** * The source for the FFT data source. */ @@ -67,10 +67,10 @@ public class RawDLSettingsPane extends SettingsPane{ */ private PamBorderPane mainPane; - /** - * Combo box which allows users to select model. - */ - private ComboBox dlModelBox; + // /** + // * Combo box which allows users to select model. + // */ + // private ComboBox dlModelBox; /** * The window length spinner for the segmenter process @@ -114,11 +114,11 @@ public class RawDLSettingsPane extends SettingsPane{ private Label infoLabel; - private Object flipPane; + private DLModelSelectPane modelSelectPane; - private PopupControl advLabel; - public RawDLSettingsPane(DLControl dlControl){ + + public DLSettingsPane(DLControl dlControl){ super(null); this.dlControl=dlControl; // Button newButton=new Button("Test"); @@ -129,16 +129,19 @@ public class RawDLSettingsPane extends SettingsPane{ // stage.sizeToScene(); // }); // this.setTop(newButton); - - + + mainPane=new PamBorderPane(); mainPane.setCenter(createDLPane()); mainPane.setPadding(new Insets(5,5,5,5)); - mainPane.setMinHeight(400); - mainPane.setMaxWidth(MAX_WIDTH); - mainPane.setPrefWidth(MAX_WIDTH); + + if (!PamGUIManager.isFX()){ + mainPane.setMinHeight(430); + mainPane.setMaxWidth(MAX_WIDTH); + mainPane.setPrefWidth(MAX_WIDTH); + } //this.getAdvPane().setMaxWidth(MAX_WIDTH); - + //mainPane.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); @@ -157,19 +160,20 @@ public class RawDLSettingsPane extends SettingsPane{ sourcePane = new GroupedSourcePaneFX("Raw Sound Data", RawDataUnit.class, true, false, true); sourcePane.addSourceType(ClickDetection.class, false); sourcePane.addSourceType(ClipDataUnit.class, false); + sourcePane.addSourceType(ConnectedRegionDataUnit.class, false); vBox.getChildren().add(sourcePane); sourcePane.prefWidthProperty().bind(vBox.widthProperty()); sourcePane.setMaxWidth(Double.MAX_VALUE); - + sourcePane.getDataBlockBox().setOnAction((action)->{ //need to create a data selector if one exists. this.dlControl.createDataSelector(getSelectedParentDataBlock()); //enable the controls to show a data selector or not. enableControls(); }); - + //create the detection vBox.getChildren().add(createDataSelectorPane()); @@ -193,14 +197,14 @@ public class RawDLSettingsPane extends SettingsPane{ hopLength.valueProperty().addListener((obsVal, oldVal, newVal)->{ setSegInfoLabel(); }); - + reMergeSeg = new PamSpinner(0, Integer.MAX_VALUE, 1, 1); reMergeSeg.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); reMergeSeg.setEditable(true); //button to set default hop size Button defaultButton = new Button(); -// defaultButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.REFRESH, PamGuiManagerFX.iconSize-3)); + // defaultButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.REFRESH, PamGuiManagerFX.iconSize-3)); defaultButton.setGraphic(PamGlyphDude.createPamIcon("mdi2r-refresh", PamGuiManagerFX.iconSize-3)); defaultButton.setTooltip(new Tooltip("Set default hop size")); defaultButton.setOnAction((action)->{ @@ -208,6 +212,14 @@ public class RawDLSettingsPane extends SettingsPane{ }); PamGridPane segmenterGridPane = new PamGridPane(); + segmenterGridPane.setHgap(5); + + ColumnConstraints col1 = new ColumnConstraints(); + col1.setHgrow( Priority.SOMETIMES ); + ColumnConstraints col2 = new ColumnConstraints(); + col2.setHgrow( Priority.ALWAYS ); + segmenterGridPane.getColumnConstraints().addAll(col1, col2 ); + segmenterGridPane.add(new Label("Window length"), 0, 0); segmenterGridPane.add(windowLength, 1, 0); segmenterGridPane.add(new Label("samples"), 2, 0); @@ -222,45 +234,50 @@ public class RawDLSettingsPane extends SettingsPane{ segmenterGridPane.add(new Label("segments"), 2, 2); vBox.getChildren().add(segmenterGridPane); - + vBox.getChildren().add(infoLabel = new Label()); Label label2 = new Label("Deep Learning Model"); label2.setPadding(new Insets(5,0,0,0)); PamGuiManagerFX.titleFont2style(label2); - + vBox.getChildren().add(label2); - //add the possible deep learning models. - dlModelBox= new ComboBox(); - for (int i=0; i{ - setClassifierPane(); - if (mainPane!=null) { - if (mainPane.getScene().getWindow() instanceof Stage) { - Stage stage = (Stage) mainPane.getScene().getWindow(); - stage.sizeToScene(); - } - } - //this.dlControl.getAnnotationType().getSymbolModifier(symbolChooser). - }); - - vBox.getChildren().add(dlModelBox); + // //add the possible deep learning models. + // dlModelBox= new ComboBox(); + // for (int i=0; i{ + // setClassifierPane(); + // if (mainPane!=null) { + // if (mainPane.getScene().getWindow() instanceof Stage) { + // Stage stage = (Stage) mainPane.getScene().getWindow(); + // stage.sizeToScene(); + // } + // } + // //this.dlControl.getAnnotationType().getSymbolModifier(symbolChooser). + // }); + // + // vBox.getChildren().add(dlModelBox); classifierPane = new PamBorderPane(); - vBox.getChildren().add(classifierPane); + vBox.getChildren().addAll(modelSelectPane, classifierPane); return vBox; } - + /** * Create the data selector. * @return the data selector. @@ -269,13 +286,13 @@ public class RawDLSettingsPane extends SettingsPane{ dataSelectorPane = new PamHBox(); dataSelectorPane.setSpacing(5); dataSelectorPane.setAlignment(Pos.CENTER_LEFT); - + dataSelectorCheckBox = new PamToggleSwitch("Detection Selector"); dataSelectorCheckBox.selectedProperty().addListener((obsval, oldval, newval)->{ enableControls(); }); dataSelectorButton = new PamButton(); -// dataSelectorButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS, PamGuiManagerFX.iconSize)); + // dataSelectorButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SETTINGS, PamGuiManagerFX.iconSize)); dataSelectorButton.setGraphic(PamGlyphDude.createPamIcon("mdi2c-cog", PamGuiManagerFX.iconSize)); dataSelectorButton.setOnAction((action)->{ showAdvPane(); @@ -283,7 +300,7 @@ public class RawDLSettingsPane extends SettingsPane{ dataSelectorPane.getChildren().addAll(dataSelectorCheckBox, dataSelectorButton); return dataSelectorPane; } - + /** * Set extra information in the info label. */ @@ -301,7 +318,7 @@ public class RawDLSettingsPane extends SettingsPane{ } infoLabel.setText(text); } - + /** * Creates pane allowing the user to change fine scale things such as error limits. * @return the pop over pane. @@ -322,21 +339,23 @@ public class RawDLSettingsPane extends SettingsPane{ popOver.show(dataSelectorButton); } - + /** * Enable the controls. */ private void enableControls() { this.dataSelectorPane.setVisible(true); - //only show the data selector box for detection data. - if (sourcePane.getSource().getDataSelectCreator() instanceof NullDataSelectorCreator) { + //only show the data selector box for detec tion data. + if (sourcePane.getSource() == null) this.dataSelectorPane.setVisible(false); + + else if (sourcePane.getSource().getDataSelectCreator() instanceof NullDataSelectorCreator) { //^bit messy but cannot think of a better way to do it. this.dataSelectorPane.setVisible(false); } - + dataSelectorButton.setDisable(!dataSelectorCheckBox.isSelected()); } - + /** @@ -360,13 +379,21 @@ public class RawDLSettingsPane extends SettingsPane{ /** * Set the classifier pane. */ - private void setClassifierPane() { + protected void setClassifierPane() { + + //set the classifier Pane.class - DLClassiferModel classifierModel = this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()); + if (modelSelectPane.currentClassifierModel!=null && modelSelectPane.currentClassifierModel.getModelUI()!=null) { + + classifierPane.setCenter(modelSelectPane.currentClassifierModel.getModelUI().getSettingsPane().getContentNode()); + + if (modelSelectPane.currentClassifierModel!=null) { + modelSelectPane.currentClassifierModel.getModelUI().setParams(); + } + else { + classifierPane.setCenter(null); + } - if (classifierModel.getModelUI()!=null) { - classifierPane.setCenter(classifierModel.getModelUI().getSettingsPane().getContentNode()); - classifierModel.getModelUI().setParams(); } else { classifierPane.setCenter(null); @@ -379,6 +406,7 @@ public class RawDLSettingsPane extends SettingsPane{ if (currParams==null ) currParams = new RawDLParams(); + @SuppressWarnings("rawtypes") PamDataBlock rawDataBlock = sourcePane.getSource(); if (rawDataBlock == null){ Platform.runLater(()->{ @@ -388,8 +416,8 @@ public class RawDLSettingsPane extends SettingsPane{ } sourcePane.getParams(currParams.groupedSourceParams); - - currParams.modelSelection = dlModelBox.getSelectionModel().getSelectedIndex(); + + // currParams.modelSelection = dlModelBox.getSelectionModel().getSelectedIndex(); if (windowLength.getValue() == 0 || hopLength.getValue()==0){ Platform.runLater(()->{ @@ -403,41 +431,87 @@ public class RawDLSettingsPane extends SettingsPane{ currParams.maxMergeHops = reMergeSeg.getValue(); - //update any changes - if (this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).getModelUI()!=null){ - this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).getModelUI().getParams(); - - //display any warnings from the settings. - ArrayList warnings = this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).checkSettingsOK(); - showWarnings(warnings); - - for (int i=0; i1) { - //Serious error. Do not close dialog. - return null; - } - } + if (modelSelectPane.currentClassifierModel == null) { + currParams.modelSelection = -1; } - + else { + currParams.modelSelection = dlControl.getDLModels().indexOf((modelSelectPane.currentClassifierModel)); + } + + + // //update any changes + // if (this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).getModelUI()!=null){ + // this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).getModelUI().getParams(); + // + // //display any warnings from the settings. + // ArrayList warnings = this.dlControl.getDLModels().get(dlModelBox.getSelectionModel().getSelectedIndex()).checkSettingsOK(); + // showWarnings(warnings); + // + // for (int i=0; i1) { + // //Serious error. Do not close dialog. + // return null; + // } + // } + // } + currParams.useDataSelector = dataSelectorCheckBox.isSelected(); + if (dlControl.getDataSelector()!=null) { dlControl.getDataSelector().getDialogPaneFX().getParams(true); } - + + + //need to make sure we call get params for the current model when the oK button is pressed. + if (this.modelSelectPane.currentClassifierModel!=null) { + if (this.modelSelectPane.currentClassifierModel.getModelUI()!=null) { + this.modelSelectPane.currentClassifierModel.getModelUI().getParams(); + } + } + + currParams.modelURI = this.modelSelectPane.currentSelectedFile; return currParams; } + + public static PamWarning statusToWarnings(DLStatus dlStatus) { + PamWarning pamWarning = new PamWarning(dlStatus.getName(), dlStatus.getDescription(), dlStatus.isError() ? 2 : 1); + return pamWarning; + } + + /** + * Show a warning dialog for the status + * @param the status to show + */ + public void showWarning(DLStatus dlWarning) { + ArrayList dlWarnings = new ArrayList(); + dlWarnings.add(statusToWarnings(dlWarning)); + showWarning(dlWarnings); + } + /** * Show a warning dialog. + * @param the warning to show. */ - public void showWarnings(ArrayList dlWarnings) { - + public void showWarning(PamWarning dlWarning) { + ArrayList dlWarnings = new ArrayList(); + dlWarnings.add(dlWarning); + showWarning(dlWarnings); + } + + + /** + * Show a warning dialog. + * @param dlWarnings - list of warnings - the most important will be shown. + */ + public void showWarning(ArrayList dlWarnings) { + if (dlWarnings==null || dlWarnings.size()<1) return; - + String warnings =""; - - + + boolean error = false; for (int i=0; i{ error=true; } } - + final String warningsF = warnings; final boolean errorF = error; Platform.runLater(()->{ WarnOnce.showWarningFX(null, "Deep Learning Settings Warning", warningsF , errorF ? AlertType.ERROR : AlertType.WARNING); + // WarnOnce.showWarning( "Deep Learning Settings Warning", warningsF , WarnOnce.WARNING_MESSAGE); }); - + //user presses OK - these warnings are just a message - they do not prevent running the module. } @@ -460,25 +535,40 @@ public class RawDLSettingsPane extends SettingsPane{ sourcePane.setParams(currParams.groupedSourceParams); sourcePane.sourceChanged(); - + + dlControl.createDataSelector(sourcePane.getSource()); - dlModelBox.getSelectionModel().select(currParams.modelSelection); + //set the classifier model. + if (currParams.modelURI !=null) { + modelSelectPane.currentClassifierModel = dlControl.getDLModel(); + } + // dlModelBox.getSelectionModel().select(currParams.modelSelection); windowLength.getValueFactory().setValue(currParams.rawSampleSize); hopLength.getValueFactory().setValue(currParams.sampleHop); reMergeSeg.getValueFactory().setValue(currParams.maxMergeHops); - + dataSelectorCheckBox.setSelected(currParams.useDataSelector); setClassifierPane(); - + enableControls(); - + setSegInfoLabel(); + // //set up the model and the custom pane if necessary. + this.modelSelectPane.loadNewModel(currParams.modelURI); + //this.modelSelectPane.updatePathLabel(); + this.setClassifierPane(); + + //For some reason, in the FX GUI, this causes a root used in multiple scenes exceptions...not sure why. + Platform.runLater(()->{ + sourcePane.getChannelValidator().validate(); + }); + } @@ -496,17 +586,57 @@ public class RawDLSettingsPane extends SettingsPane{ @Override public void paneInitialized() { - // TODO Auto-generated method stub + //don't know why this is required but causes a root used in multiple scenes exceptions...really not sure why. + Platform.runLater(()->{ + sourcePane.getChannelValidator().validate(); + }); } /** * Get the data block currently selected in the pane. * @return the data block currently selected in the pane. */ + @SuppressWarnings("rawtypes") public PamDataBlock getSelectedParentDataBlock() { return sourcePane.getSource(); } + /** + * Get the DLControl associated with the pane. + * @return a reference to the DLControl. + */ + public DLControl getDLControl() { + return dlControl; + } + + /** + * Convenience class to set the segment length in samples from milliseconds + * @param defaultSegmentLen - the segment length in milliseconds. + */ + public void setSegmentLength(Double defaultSegmentLen) { + if (defaultSegmentLen==null) return; + + + double sR = getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); + + System.out.println("Set the segment length: " + defaultSegmentLen + " sR " + sR); + + //automatically set the default segment length. + getDLControl().getSettingsPane().getSegmentLenSpinner().getValueFactory().setValue((int) (sR*defaultSegmentLen/1000.)); + } + + /** + * Convenience class to set the hop length in samples from milliseconds + * @param defaultSegmentLen - the segment length in milliseconds. + */ + public void setHopLength(Double hopLength) { + if (hopLength==null) return; + + double sR = getDLControl().getSettingsPane().getSelectedParentDataBlock().getSampleRate(); + //automatically set the default segment length. + getDLControl().getSettingsPane().getHopLenSpinner().getValueFactory().setValue((int) (sR*hopLength/1000.)); + } + } diff --git a/src/rawDeepLearningClassifier/layoutFX/DLSidePanelSwing.java b/src/rawDeepLearningClassifier/layoutFX/DLSidePanelSwing.java index 1a6bc73e..c1a73b25 100644 --- a/src/rawDeepLearningClassifier/layoutFX/DLSidePanelSwing.java +++ b/src/rawDeepLearningClassifier/layoutFX/DLSidePanelSwing.java @@ -35,7 +35,7 @@ public class DLSidePanelSwing implements PamSidePanel { */ public void setupPanel() { //System.out - if (dlControl.getDLModel().getModelUI()!=null && dlControl.getDLModel().getModelUI().getSidePanel()!=null) { + if (dlControl.getDLModel()!=null && dlControl.getDLModel().getModelUI()!=null && dlControl.getDLModel().getModelUI().getSidePanel()!=null) { mainPanel.add(dlControl.getDLModel().getModelUI().getSidePanel(), BorderLayout.CENTER); // mainPanel.add(new JLabel("Hello"), BorderLayout.WEST); mainPanel.validate(); @@ -44,6 +44,7 @@ public class DLSidePanelSwing implements PamSidePanel { //blank mainPanel.removeAll(); } + } @Override diff --git a/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBug.java b/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBug.java new file mode 100644 index 00000000..a4466f84 --- /dev/null +++ b/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBug.java @@ -0,0 +1,69 @@ +package rawDeepLearningClassifier.layoutFX; + +import javafx.application.Application; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.SpinnerValueFactory.ListSpinnerValueFactory; +import javafx.stage.Stage; +import javafx.util.StringConverter; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamSpinner; +import rawDeepLearningClassifier.layoutFX.dlTransfroms.FFTTransformPane; + +public class FFTSpinnerBug extends Application{ + + protected static final String INITAL_VALUE = "0"; + + public static void main(String[] args) { + Application.launch(args); + } + + @Override + public void start(Stage stage) { + //System.out.println("Create step list: " + createStepList().size()); + PamSpinner spinner = new PamSpinner(FFTTransformPane.createStepList()); + spinner.getValueFactory().setConverter(new NumberConverter()); + spinner.setPrefWidth(80); + + PamBorderPane pane = new PamBorderPane(); + + pane.setCenter(spinner); + + pane.setPadding(new Insets(20,20,20,20)); + + Scene scene = new Scene(pane, 350, 300); + + stage.setTitle("Hello Spinner"); + stage.setScene(scene); + stage.show(); + + + System.out.println("Size before: " + ((ListSpinnerValueFactory) spinner.getValueFactory()).getItems().size()); + spinner.getValueFactory().setValue(1019); + System.out.println("Size after: " + ((ListSpinnerValueFactory) spinner.getValueFactory()).getItems().size()); + + for (int i=0; i<((ListSpinnerValueFactory) spinner.getValueFactory()).getItems().size(); i++) { + System.out.println("List value: " + i + ": " + ((ListSpinnerValueFactory) spinner.getValueFactory()).getItems().get(i)); + } + + spinner.increment(0); + + } + + + class NumberConverter extends StringConverter { + @Override + public String toString(Number object) { + // TODO Auto-generated method stub + return object.toString(); + } + + @Override + public Number fromString(String string) { + return Integer.valueOf(string); + } + + } + + +} diff --git a/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBugLauncher.java b/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBugLauncher.java new file mode 100644 index 00000000..f1adec21 --- /dev/null +++ b/src/rawDeepLearningClassifier/layoutFX/FFTSpinnerBugLauncher.java @@ -0,0 +1,11 @@ +package rawDeepLearningClassifier.layoutFX; + + +public class FFTSpinnerBugLauncher { + + public static void main(String[] args) { + FFTSpinnerBug.main(args); + } + + +} diff --git a/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java b/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java new file mode 100644 index 00000000..b6b2078d --- /dev/null +++ b/src/rawDeepLearningClassifier/layoutFX/defaultModels/DefaultModelPane.java @@ -0,0 +1,176 @@ +package rawDeepLearningClassifier.layoutFX.defaultModels; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javafx.application.HostServices; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import pamViewFX.PamGuiManagerFX; +import pamViewFX.fxGlyphs.PamGlyphDude; +import pamViewFX.fxNodes.PamBorderPane; +import pamViewFX.fxNodes.PamButton; +import pamViewFX.fxNodes.PamStackPane; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.hidingPane.HidingPane; +import rawDeepLearningClassifier.defaultModels.DLDefaultModelManager; +import rawDeepLearningClassifier.defaultModels.DLModel; + +/** + * Pane which allows users to view and select default models. + */ +public class DefaultModelPane extends PamBorderPane{ + + private static final double PREF_WIDTH = 200; + + + /** + * Reference to the deafult model manager that contains the default models. + */ + private DLDefaultModelManager defaultModelManager; + + + private PamBorderPane hidingPaneContent; + + + private HidingPane hidingPane; + + ObjectProperty defaultModel = new SimpleObjectProperty(); + + + + /** + * Constructor for the DefaultModelPane + * @param defaultModelManager - the default model manager to use. + */ + public DefaultModelPane(DLDefaultModelManager defaultModelManager) { + this.defaultModelManager=defaultModelManager; + this.setCenter(createDefaultModelPane() ); + } + + + + private Pane createDefaultModelPane() { + + PamVBox vBox = new PamVBox(); + +// vBox.setSpacing(5); + + Label label = new Label("Default Models"); + PamGuiManagerFX.titleFont2style(label); + + vBox.getChildren().add(label); +// vBox.setPrefWidth(120); + + hidingPaneContent= new PamBorderPane(); + hidingPaneContent.setPrefWidth(PREF_WIDTH); + hidingPane = new HidingPane(Side.RIGHT, hidingPaneContent, vBox, true, 0); + + PamButton button; + for (int i=0; i{ + hidingPaneContent.setCenter(createModelPane(defaultModelManager.getDefaultModel(ii))); + hidingPane.showHidePane(true); + }); + + if (i>0 && i{ + try { + Desktop.getDesktop().browse(dlModel.getCitationLink()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } +// HostServices.getInstance(this).getHostServices().showDocument(yahooURL) + }); + + + PamButton importButton = new PamButton("Import"); + importButton.setGraphic(PamGlyphDude.createPamIcon("mdi2d-download", PamGuiManagerFX.iconSize)); + importButton.setOnAction((action)->{ + defaultModel.set(dlModel); + }); + + PamBorderPane buttonHolder = new PamBorderPane(); + buttonHolder.setRight(importButton); + BorderPane.setAlignment(importButton, Pos.BOTTOM_RIGHT); + VBox.setVgrow(buttonHolder, Priority.ALWAYS); + + + vBox.getChildren().addAll(titleLabel, descriptionLabel, link, buttonHolder); + vBox.setPrefHeight(200); + + return vBox; + } + + + public ObjectProperty defaultModelProperty() { + return defaultModel; + } + + + + +} diff --git a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformImage.java b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformImage.java index d1051b9f..594aee48 100644 --- a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformImage.java +++ b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformImage.java @@ -4,11 +4,16 @@ import java.util.ArrayList; import java.util.Arrays; import org.controlsfx.control.RangeSlider; +import org.controlsfx.control.decoration.Decorator; +import org.controlsfx.control.decoration.GraphicDecoration; +import org.controlsfx.control.decoration.StyleClassDecoration; import org.jamdev.jdl4pam.transforms.DLTransform; import org.jamdev.jdl4pam.transforms.DLTransform.DLTransformType; import org.jamdev.jdl4pam.transforms.FreqTransform; +import org.jamdev.jdl4pam.transforms.SimpleTransform; import org.jamdev.jdl4pam.transforms.WaveTransform; import org.jamdev.jpamutils.wavFiles.AudioData; +import org.jamdev.jpamutils.wavFiles.FilterParams; import PamUtils.PamArrayUtils; import javafx.collections.FXCollections; @@ -16,16 +21,20 @@ import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.geometry.Side; +import javafx.scene.Node; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; import javafx.util.StringConverter; +import pamViewFX.fxGlyphs.PamGlyphDude; import pamViewFX.fxNodes.PamBorderPane; import pamViewFX.fxNodes.PamHBox; import pamViewFX.fxNodes.sliders.ColourRangeSlider; import pamViewFX.fxNodes.utilsFX.ColourArray; import pamViewFX.fxNodes.utilsFX.ColourArray.ColourArrayType; import pamViewFX.fxPlotPanes.PlotPane; +import pamViewFX.validator.PamValidator; import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSound; import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory; import rawDeepLearningClassifier.layoutFX.exampleSounds.ExampleSoundFactory.ExampleSoundType; @@ -106,7 +115,12 @@ public abstract class DLTransformImage extends PamBorderPane{ /** * The time label. */ - private Label timeLabel; + private Label timeLabel; + + /** + * The error label. + */ + private Label errorLabel; public DLTransformImage() { @@ -133,8 +147,8 @@ public abstract class DLTransformImage extends PamBorderPane{ transformschoiceBox = new ChoiceBox(); transformschoiceBox.valueProperty().addListener((obsval, oldval, newval)->{ updateTransformImage(); - calcTimeBins(transformsR); - plotPane.repaint(); + // calcTimeBins(transformsR); + // plotPane.repaint(); }); transformschoiceBox.setConverter(new DLTransformConverter()); transformschoiceBox.setPrefWidth(170); @@ -194,11 +208,18 @@ public abstract class DLTransformImage extends PamBorderPane{ timeSlider.maxWidthProperty().bind(plotPane.getPlotCanvas().widthProperty().add(5)); + //create an error label + errorLabel = new Label(); + Node decoration = PamGlyphDude.createPamIcon("mdi2c-close-circle-outline", Color.RED, 10); + errorLabel.setGraphic(decoration); +// Decorator.addDecoration(errorLabel, new GraphicDecoration(decoration, Pos.CENTER_RIGHT)); + BorderPane.setAlignment(timeLabel, Pos.CENTER); this.setBottom(timeLabel); } + /** * Get the species choice box. * @return - the species choice box. @@ -212,7 +233,7 @@ public abstract class DLTransformImage extends PamBorderPane{ * Calculate the sample time bins. */ private void calcTimeBins(float sampleRate) { - + timeBins[0] = (int) (sampleRate*(timeSlider.getLowValue()/1000.0)); timeBins[1] = (int) (sampleRate*(timeSlider.getHighValue()/1000.0)); @@ -221,9 +242,8 @@ public abstract class DLTransformImage extends PamBorderPane{ long[] shape = calcShape(); - double nSamples = this.exampleSound.getSampleRate()*((timeSlider.getHighValue()-timeSlider.getLowValue())/1000.0); - + if (shape!=null && shape.length==2) { int timeShape = (int) (shape[0]*(nSamples/(double) exampleSound.getWave().length)); @@ -238,6 +258,7 @@ public abstract class DLTransformImage extends PamBorderPane{ } } + /** * Calculate the output shape. */ @@ -247,24 +268,30 @@ public abstract class DLTransformImage extends PamBorderPane{ if (this.exampleSound==null) return null; ArrayList transforms = getDLTransforms(); - + if (transforms==null) return null; - + long[] shape; if (transforms.get(transforms.size()-1) instanceof FreqTransform) { FreqTransform freqTransform = ((FreqTransform) transforms.get(transforms.size()-1)); - + if (freqTransform.getSpecTransfrom()==null) return null; - + double[][] data2D = freqTransform.getSpecTransfrom().getTransformedData(); - + shape = new long[] {data2D.length, data2D[0].length}; } else { WaveTransform waveTransform = ((WaveTransform) transforms.get(transforms.size()-1)); - - double[] data = waveTransform.getWaveData().getScaledSampleAmpliudes(); - + + // System.out.println("Get wave data: " + waveTransform.getDLTransformType() + " " + waveTransform.getWaveData()); + + if (waveTransform.getWaveData()==null) { + return null; + } + + double[] data = waveTransform.getWaveData().getScaledSampleAmplitudes(); + shape = new long[] {data.length}; } @@ -301,6 +328,7 @@ public abstract class DLTransformImage extends PamBorderPane{ return this.exampleSound.getSampleRate(); } + /** * Update the example sound. * @param exampleSoundType - the example sound type. @@ -310,6 +338,7 @@ public abstract class DLTransformImage extends PamBorderPane{ updateExampleSound(exampleSound); } + /** * Update the example sound. * @param exampleSound - the new example sound. @@ -332,12 +361,19 @@ public abstract class DLTransformImage extends PamBorderPane{ updateTransformImage(); } + /** * Update the transform image to the latest selected transform and data params. */ - public void updateTransformImage() { + public boolean updateTransformImage() { - if (this.exampleSound==null) return; + setErrorMessage(null); + + + if (this.exampleSound==null) { + System.err.println("DLTRanfromImage.updateTransformImage: the example sound is null"); + return false; + } specImage=null; data1D=null; @@ -347,11 +383,20 @@ public abstract class DLTransformImage extends PamBorderPane{ ((WaveTransform) getDLTransforms().get(0)).setWaveData(soundData); DLTransform currentTransform = getDLTransforms().get(0); - for (int i=0; i0) { plotPane.getAxis(Side.LEFT).setLabel("Amplitude"); colRangeSlider.setDisable(true); - data1D = ((WaveTransform) currentTransform).getWaveData().getScaledSampleAmpliudes(); + data1D = ((WaveTransform) currentTransform).getWaveData().getScaledSampleAmplitudes(); data1DminMax= PamArrayUtils.minmax(data1D); plotPane.getAxis(Side.LEFT).setMinVal(data1DminMax[0]); @@ -408,10 +458,125 @@ public abstract class DLTransformImage extends PamBorderPane{ transformsR = ((WaveTransform) currentTransform).getWaveData().getSampleRate(); //System.out.println("Spec wave data: " + ((WaveTransform) currentTransform).getWaveData().getScaledSampleAmpliudes()[10]); } + else { + setErrorMessage("Error in " + currentTransform.getDLTransformType()); + return false; + } + } calcTimeBins(transformsR); plotPane.repaint(); + return true; + } + + + /** + * Set an error message in the plit + * @param string + */ + public void setErrorMessage(String string) { + if (string==null) { + this.setCenter(plotPane); + return; + } + plotPane.getPlotCanvas().getGraphicsContext2D().clearRect(0, 0, plotPane.getPlotCanvas().getWidth(), + plotPane.getPlotCanvas().getHeight()); + + + errorLabel.setText(string); + + + this.setCenter(errorLabel); + + // getPlotCanvas().getGraphicsContext2D().setStroke(Color.RED); + // getPlotCanvas().getGraphicsContext2D().setFill(Color.RED); + // getPlotCanvas().getGraphicsContext2D().strokeText(string, 10, getPlotCanvas().getHeight()/2); + } + + + /** + * Check for error in a transform and return the transform if true; + * @param dlTransform - the new transform + * @param currentTransform - the data t be transformed + * @return a string error message. + */ + private String checkTransform(DLTransform dlTransform, DLTransform currentTransform) { + + String error = null; + SimpleTransform simpleTransform = ((SimpleTransform) dlTransform); + float sampleRate; + switch (dlTransform.getDLTransformType()) { + case DECIMATE: + case DECIMATE_SCIPY: + //decimation sampleRate + sampleRate = simpleTransform.getParams()[0].floatValue(); + double seconds = ((WaveTransform) currentTransform).getWaveData().getLengthInSeconds(); + + if (seconds < 3/sampleRate) { + //only three samples left + error = "The decimation Nyquist is too low for the sample sound. Use a different example species"; + } + + break; + case ENHANCE: + break; + case FILTER: + FilterParams params = WaveTransform.transform2FilterParams(simpleTransform.getParams()); + sampleRate = ((WaveTransform) currentTransform).getWaveData().getSampleRate(); + + if (params.filterType==AudioData.BANDPASS || params.filterType== AudioData.LOWPASS) { + //filter cutoff must be within sample rate. + if (params.highCut>=sampleRate) { + error = "The upper filter frequency cannnot be greater than or equal to Nyquist"; + } + } + if (params.filterType==AudioData.BANDPASS || params.filterType== AudioData.HIGHPASS) { + + if (params.lowCut>=sampleRate) { + error = "The lower filter frequency cannnot be greater than or equal to Nyquist"; + } + } + break; + case FILTER_ISOLATED_SPOTS: + break; + case GAUSSIAN_FILTER: + break; + case MEDIANFILTER: + break; + case NORMALISE_WAV: + break; + case PREEMPHSIS: + break; + case REDUCETONALNOISE_MEAN: + break; + case REDUCETONALNOISE_MEDIAN: + break; + case SPEC2DB: + break; + case SPECCLAMP: + break; + case SPECCROPINTERP: + break; + case SPECNORMALISE: + break; + case SPECNORMALISEROWSUM: + break; + case SPECNORMALISESTD: + break; + case SPECNORMALISE_MINIMAX: + break; + case SPECTROGRAM: + break; + case SPECTROGRAMKETOS: + break; + case TRIM: + break; + default: + break; + + } + return error; } @@ -452,6 +617,8 @@ public abstract class DLTransformImage extends PamBorderPane{ y1= ((dataTrim[i]- data1DminMax[0])/(dataRange))*getPlotCanvas().getHeight(); y2= ((dataTrim[i+1]- data1DminMax[0])/(dataRange))*getPlotCanvas().getHeight(); + getPlotCanvas().getGraphicsContext2D().setStroke(Color.BLACK); + getPlotCanvas().getGraphicsContext2D().setFill(Color.BLACK); getPlotCanvas().getGraphicsContext2D().strokeLine(x1, y1, x2, y2); } } diff --git a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformsPane.java b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformsPane.java index 231aaff3..9ea8a36f 100644 --- a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformsPane.java +++ b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/DLTransformsPane.java @@ -29,7 +29,7 @@ import pamViewFX.fxNodes.orderedList.PamDraggableList; * */ public class DLTransformsPane extends PamBorderPane { - + /** * The order of transforms has changed. */ @@ -73,7 +73,7 @@ public class DLTransformsPane extends PamBorderPane { Label label = new Label("Add Transform"); MenuButton splitButton = new MenuButton(); -// splitButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.PLUS)); + // splitButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.PLUS)); splitButton.setGraphic(PamGlyphDude.createPamIcon("mdi2p-plus")); DLTransformType[] dlTransformTypes = DLTransformType.values(); @@ -113,10 +113,10 @@ public class DLTransformsPane extends PamBorderPane { transformPane.addSettingsListener(()->{ newSettings(TRANSFORM_SETTINGS_CHANGE); }); - + draggablelistPane.addDraggablePane(transformPane); } - + /* * Called whenever a transform is remvoed from the pane. */ @@ -138,7 +138,7 @@ public class DLTransformsPane extends PamBorderPane { * @param dlTransforms - the list of transforms (in order) to set */ public void setTransforms(ArrayList dlTransforms) { - + //System.out.println("Set DL transfroms: " + dlTransforms); if (dlTransforms == null) { @@ -146,15 +146,16 @@ public class DLTransformsPane extends PamBorderPane { mainPane.getChildren().add(controlPane); return; } - + ArrayList dlTransformPanes = new ArrayList(); sampleRate=-1; - -// for (int i=0; i{ newSettings(TRANSFORM_SETTINGS_CHANGE); }); + } draggablelistPane = new DLDraggableList(dlTransformPanes); @@ -212,7 +221,7 @@ public class DLTransformsPane extends PamBorderPane { return null; } - + /** * Get the transform panes. * @return the transofrm panes. @@ -225,11 +234,11 @@ public class DLTransformsPane extends PamBorderPane { return null; } } - + class DLDraggableList extends PamDraggableList { - + public DLDraggableList() { super(); } @@ -239,12 +248,12 @@ public class DLTransformsPane extends PamBorderPane { super(panes); // TODO Auto-generated constructor stub } - + @Override public void paneOrderChanged(boolean success) { newSettings(TRANSFORM_ORDER_CHANGE); } - + @Override public void removePane(BorderPane pane) { super.removePane(pane); @@ -255,14 +264,14 @@ public class DLTransformsPane extends PamBorderPane { @Override public boolean canDrop(DLTransformPane source, int sourceIndex, int targetIndex) { // if a wave transform has to be before any spectral transforms. - + //generate the new possible list of nodes. List newPossibleList = getTempSortedList(sourceIndex, targetIndex); - + if (source.getDLTransform() instanceof WaveTransform) { //System.out.println("Target index: " + targetIndex); //uuuurgh this is complicated because it depends on whether the transform itself is in the list or not... - + int i=0; while (i(createStepList())); break; + case SPECTROGRAMKETOS: + + settingsPane = new FFTTransformPane((SimpleTransform) dlTransfrom, new String[]{"FFT Length ", "FFT Hop", "Window_Length"}, new String[]{"", "", "s"}); + + ((FFTTransformPane) settingsPane).setSpinnerMinMaxValues(1, 4, Integer.MAX_VALUE, 4); + + break; + case TRIM: settingsPane = new SimpleTransformPane((SimpleTransform) dlTransfrom, new String[]{"Start", "End"}, new String[]{"samples ", "samples"}); ((SimpleTransformPane) settingsPane).setSpinnerMinMaxValues(0, 0, Integer.MAX_VALUE, 500); @@ -139,6 +158,9 @@ public class DataTransformPaneFactory { break; } + +// System.out.println("Get transform pane for 2: " + settingsPane); + return settingsPane; } diff --git a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FFTTransformPane.java b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FFTTransformPane.java index 3fb80d6e..52fb85db 100644 --- a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FFTTransformPane.java +++ b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FFTTransformPane.java @@ -6,10 +6,9 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.util.StringConverter; import pamViewFX.fxNodes.PamSpinner; -import javafx.util.StringConverter; /** - * Transform pane for fourier transform + * Transform pane for Fourier transform * @author Jamie Macaulay * */ @@ -29,7 +28,6 @@ public class FFTTransformPane extends SimpleTransformPane { return spinner; } else return super.createSpinner(i); - } @@ -41,13 +39,13 @@ public class FFTTransformPane extends SimpleTransformPane { ObservableList stepSizeListLength=FXCollections.observableArrayList(); for (int i=2; i<15; i++){ stepSizeListLength.add(Integer.valueOf((int) Math.pow(2,i))); - } return stepSizeListLength; } + + class NumberConverter extends StringConverter { - @Override public String toString(Number object) { // TODO Auto-generated method stub @@ -61,4 +59,7 @@ public class FFTTransformPane extends SimpleTransformPane { } + + + } diff --git a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FilterTransformPane.java b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FilterTransformPane.java new file mode 100644 index 00000000..2f070e16 --- /dev/null +++ b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/FilterTransformPane.java @@ -0,0 +1,199 @@ +package rawDeepLearningClassifier.layoutFX.dlTransfroms; + +import org.jamdev.jdl4pam.transforms.DLTransform; +import org.jamdev.jdl4pam.transforms.SimpleTransform; +import org.jamdev.jdl4pam.transforms.WaveTransform; +import org.jamdev.jpamutils.wavFiles.AudioData; +import org.jamdev.jpamutils.wavFiles.FilterParams; + +import Filters.FilterBand; +import fftFilter.FFTFilterParams; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.Pane; +import pamViewFX.fxNodes.PamHBox; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.utilityPanes.SimpleFilterPaneFX; + +/** + * Pane for a waveform filter. + */ +public class FilterTransformPane extends DLTransformPane { + + /** + * The transform associated with the settings pane. + */ + private DLTransform simpleTransfrom; + + /** + * Controls for changing filter settings. + */ + private SimpleFilterPaneFX filterPane; + + /** + * Choice box for changing the filter type - e.g. Butterworth + */ + private ComboBox filterMethodBox; + + /** + * Spinner for changing the filter order. + */ + private Spinner orderSpinner; + + + public FilterTransformPane(DLTransform dlTransfrom) { + super(); + this.simpleTransfrom= dlTransfrom; + this.setCenter(createFilterPane()); +// this.setStyle("-fx-background-color:orangered;"); + + } + + private Node createFilterPane() { + + PamVBox mainPane = new PamVBox(); + mainPane.setSpacing(5); + + filterMethodBox = new ComboBox(); + filterMethodBox.getItems().add(AudioData.BUTTERWORTH, "Butterworth"); + filterMethodBox.getItems().add(AudioData.CHEBYSHEV, "Chebyshev"); + + filterMethodBox.valueProperty().addListener((obsVal, oldVal, newVal)->{ + this.notifySettingsListeners(); + }); + + //spinner for changing filter order. + orderSpinner = new Spinner(1,50,4,1); + orderSpinner.valueProperty().addListener((obsVal, oldVal, newVal)->{ + this.notifySettingsListeners(); + }); + orderSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + + + PamHBox filterTypeHolder = new PamHBox(); + filterTypeHolder.setSpacing(5); + filterTypeHolder.setAlignment(Pos.CENTER_LEFT); + filterTypeHolder.getChildren().addAll(filterMethodBox, new Label("Order"), orderSpinner); + + //create the filter pane - use the standard pG one - a bit of hack but no point in basically replicating + //this whole pane again. + filterPane = new SimpleFilterPaneFX(); + + filterPane.addSettingsListener(()->{ + this.notifySettingsListeners(); + }); + + mainPane.getChildren().addAll(new Label("Filter Type"), filterTypeHolder, filterPane.getContentNode()); + + TitledPane titledPane = new TitledPane(simpleTransfrom.getDLTransformType().toString(), mainPane); + + // PamBorderPane borderPane = new PamBorderPane(); + // borderPane.setTop(new Label(simpleTransfrom.getDLTransformType().toString())); + // borderPane.setCenter(hBox); + + titledPane.setExpanded(false); + + return titledPane; + } + + /** + * Convert parameters array to filter parameters. + * @param params - the parameters to set. + * @return the FFTfilterParams object with params from fileParams + */ + private FFTFilterParams transform2FilterParams(FilterParams filtParams) { + + FFTFilterParams filtParamsPG = new FFTFilterParams(); + + switch (filtParams.filterType) { + case AudioData.LOWPASS: + filtParamsPG.filterBand = FilterBand.LOWPASS; + break; + case AudioData.HIGHPASS: + filtParamsPG.filterBand = FilterBand.HIGHPASS; + break; + case AudioData.BANDPASS: + filtParamsPG.filterBand = FilterBand.BANDPASS; + break; + } + + filtParamsPG.lowPassFreq = filtParams.highCut; + filtParamsPG.highPassFreq = filtParams.lowCut; + + + return filtParamsPG; + + } + + private FilterParams getFFTFilterSettings(FilterParams params) { + + FFTFilterParams paramsFFT = filterPane.getParams(new FFTFilterParams()); + + switch (paramsFFT.filterBand) { + case BANDPASS: + params.filterType = AudioData.BANDPASS; + break; + case BANDSTOP: + break; + case HIGHPASS: + params.filterType = AudioData.HIGHPASS; + break; + case LOWPASS: + params.filterType = AudioData.LOWPASS; + break; + } + + params.highCut = paramsFFT.lowPassFreq; + params.lowCut = paramsFFT.highPassFreq; + + return params; + } + + @Override + public DLTransform getDLTransform() { + return this.getParams(simpleTransfrom) ; + } + + @Override + public DLTransform getParams(DLTransform dlTransform) { + +// System.out.println("GET PARAMS: FILTER"); + + SimpleTransform simpleTransform = (SimpleTransform) dlTransform; + + //create filter params object. + FilterParams filtParams = new FilterParams(); + filtParams = getFFTFilterSettings(filtParams); + + filtParams.filterMethod = filterMethodBox.getSelectionModel().getSelectedIndex(); + filtParams.order = orderSpinner.getValue(); + + simpleTransform.setParams(WaveTransform.filterParams2transform(filtParams)); + + return simpleTransform; + } + + @Override + public void setParams(DLTransform dlTransform) { + +// System.out.println("SET PARAMS: FILTER"); + + SimpleTransform simpleTransform = (SimpleTransform) dlTransform; + + //get filter params as an object to try and make less mistakes! + FilterParams filtParams = WaveTransform.transform2FilterParams(simpleTransform.getParams()); + + //get the selection model. + filterMethodBox.getSelectionModel().select(filtParams.filterMethod); + orderSpinner.getValueFactory().setValue(filtParams.order); + + //bit of a hack but otherwise have to rewrite filter GUI. + filterPane.setParams(transform2FilterParams(filtParams)); + + } + +} diff --git a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/SimpleTransformPane.java b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/SimpleTransformPane.java index 21bcf508..20f99f10 100644 --- a/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/SimpleTransformPane.java +++ b/src/rawDeepLearningClassifier/layoutFX/dlTransfroms/SimpleTransformPane.java @@ -191,6 +191,7 @@ public class SimpleTransformPane extends DLTransformPane { //Set the new numbers Number[] params = new Number[spinners.size()]; for (int i=0; i>{ @Override public void completeTask() { //dlControl.setNotifyProcesses(false); - this.dlControl.getDLModel().closeModel(); + //this.dlControl.getDLModel().closeModel(); dlControl.update(MTClassifierControl.PROCESSING_END); } diff --git a/src/rawDeepLearningClassifier/resources/advanced_settings_animalspot_1.png b/src/rawDeepLearningClassifier/resources/advanced_settings_animalspot_1.png index 8aa822e3..a406874c 100644 Binary files a/src/rawDeepLearningClassifier/resources/advanced_settings_animalspot_1.png and b/src/rawDeepLearningClassifier/resources/advanced_settings_animalspot_1.png differ diff --git a/src/rawDeepLearningClassifier/resources/advanced_settings_generic_1.png b/src/rawDeepLearningClassifier/resources/advanced_settings_generic_1.png index c70cbffb..96145e72 100644 Binary files a/src/rawDeepLearningClassifier/resources/advanced_settings_generic_1.png and b/src/rawDeepLearningClassifier/resources/advanced_settings_generic_1.png differ diff --git a/src/rawDeepLearningClassifier/resources/advanced_settings_generic_2.png b/src/rawDeepLearningClassifier/resources/advanced_settings_generic_2.png index 70abc303..c70a2d4d 100644 Binary files a/src/rawDeepLearningClassifier/resources/advanced_settings_generic_2.png and b/src/rawDeepLearningClassifier/resources/advanced_settings_generic_2.png differ diff --git a/src/rawDeepLearningClassifier/resources/deep_leanring_module_help.png b/src/rawDeepLearningClassifier/resources/deep_leanring_module_help.png index 250486bc..79fff4d3 100644 Binary files a/src/rawDeepLearningClassifier/resources/deep_leanring_module_help.png and b/src/rawDeepLearningClassifier/resources/deep_leanring_module_help.png differ diff --git a/src/rawDeepLearningClassifier/resources/default_settings_humpback_1.png b/src/rawDeepLearningClassifier/resources/default_settings_humpback_1.png new file mode 100644 index 00000000..eaf71e68 Binary files /dev/null and b/src/rawDeepLearningClassifier/resources/default_settings_humpback_1.png differ diff --git a/src/rawDeepLearningClassifier/segmenter/GroupedRawData.java b/src/rawDeepLearningClassifier/segmenter/GroupedRawData.java new file mode 100644 index 00000000..e969e552 --- /dev/null +++ b/src/rawDeepLearningClassifier/segmenter/GroupedRawData.java @@ -0,0 +1,145 @@ +package rawDeepLearningClassifier.segmenter; + +import java.util.Arrays; + +import PamDetection.PamDetection; +import PamUtils.PamUtils; +import PamguardMVC.PamDataUnit; + +/** + * + * Temporary holder for raw data with a pre defined size. This holds one channel group of raw + * sound data. + * + * @author Jamie Macaulay + * + */ +public class GroupedRawData extends PamDataUnit implements PamDetection, Cloneable { + + + /* + * Raw data holder + */ + protected double[][] rawData; + + + /** + * Current position in the rawData; + */ + protected int[] rawDataPointer; + + /** + * The data unit associated with this raw data chunk. + */ + private PamDataUnit rawDataUnit; + + + /** + * Create a grouped raw data unit. This contains a segment of sound data. + * @param timeMilliseconds - the time in milliseconds. + * @param channelBitmap - the channel bitmap of the raw data. + * @param startSample - the start sample of the raw data. + * @param duration - the duration of the raw data in samples. + * @param samplesize - the total sample size of the raw data unit chunk in samples. + */ + public GroupedRawData(long timeMilliseconds, int channelBitmap, long startSample, long duration, int samplesize) { + super(timeMilliseconds, channelBitmap, startSample, duration); + rawData = new double[PamUtils.getNumChannels(channelBitmap)][]; + rawDataPointer = new int[PamUtils.getNumChannels(channelBitmap)]; + // rawDataStartMillis = new long[PamUtils.getNumChannels(channelBitmap)]; + + for (int i =0; i=rawData[groupChan].length) { + arrayCopyLen=copyLen-(lastPos-rawData[groupChan].length)-1; + dataOverflow = copyLen - arrayCopyLen; + } + else { + arrayCopyLen= copyLen; + } + + arrayCopyLen = Math.max(arrayCopyLen, 0); + + //update the current grouped raw data unit with new raw data. + System.arraycopy(src, srcPos, rawData[groupChan], rawDataPointer[groupChan], arrayCopyLen); + + rawDataPointer[groupChan]=rawDataPointer[groupChan] + arrayCopyLen; + + return dataOverflow; + } + + /** + * Get the raw data grouped by channel. + * @return the raw acoustic data. + */ + public double[][] getRawData() { + return rawData; + } + + /** + * Get the current pointer for rawData. + * @return the data pointer per channel. + */ + public int[] getRawDataPointer() { + return rawDataPointer; + } + + + @Override + protected GroupedRawData clone() { + try { + GroupedRawData groupedRawData = (GroupedRawData) super.clone(); + + //hard clone the acoustic data + groupedRawData.rawData = new double[this.rawData.length][]; + for (int i=0; i { //need this to notify classifier in viewer mode. return true; } - + } diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java new file mode 100644 index 00000000..6c152634 --- /dev/null +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterDetectionGroup.java @@ -0,0 +1,74 @@ +package rawDeepLearningClassifier.segmenter; + +import Localiser.detectionGroupLocaliser.GroupDetection; +import PamguardMVC.PamDataUnit; + +/** + * A group of detection which are within a particular segment. This is used to pass detection groups straight to a classifier. + * @author Jamie Macaulay + * + */ +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; + + 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. + * + * @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 in milliseconds. + */ + 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 + public boolean isAllowSubdetectionSharing() { + //segmetns share sub detections + return true; + } + + + public long getSegmentStartMillis() { + return segMillis; + } + + /** + * Get the segment duration in milliseconds. + * @return the segment duration in millis. + */ + public double getSegmentDuration() { + return segDuration; + } + + public long getSegmentEndMillis() { + return (long) (segMillis+segDuration); + } + + public void setStartSecond(double timeS) { + this.timeS = timeS; + } + + public double getStartSecond() { + return timeS; + } + + +} diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java new file mode 100644 index 00000000..3a69c23a --- /dev/null +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterGroupDataBlock.java @@ -0,0 +1,20 @@ +package rawDeepLearningClassifier.segmenter; + +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamProcess; + +/** + * + * @author Jamie Macaulay + * + */ +public class SegmenterGroupDataBlock extends PamDataBlock { + + public SegmenterGroupDataBlock(String dataName, PamProcess parentProcess, int channelMap) { + super(SegmenterDetectionGroup.class, dataName, parentProcess, channelMap); + this.setNaturalLifetimeMillis(15000); //do not want to keep the data for very long - it's raw data segmnents so memory intensive + + } + + +} diff --git a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java index bc0bb24c..7012ac68 100644 --- a/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java +++ b/src/rawDeepLearningClassifier/segmenter/SegmenterProcess.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.Arrays; import PamController.PamController; -import PamDetection.PamDetection; import PamDetection.RawDataUnit; import PamUtils.PamArrayUtils; import PamUtils.PamUtils; @@ -17,11 +16,10 @@ import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import PamguardMVC.PamObservable; import PamguardMVC.PamProcess; -import PamguardMVC.debug.Debug; import clickDetector.ClickDetection; import clipgenerator.ClipDataUnit; import rawDeepLearningClassifier.DLControl; -import PamUtils.PamCalendar; +import whistlesAndMoans.ConnectedRegionDataUnit; /** @@ -58,7 +56,26 @@ public class SegmenterProcess extends PamProcess { private SegmenterDataBlock segmenterDataBlock; PamSymbol defaultSymbol = new PamSymbol(PamSymbolType.SYMBOL_DIAMOND, 10, 12, false, - Color.CYAN, Color.CYAN); + Color.CYAN, Color.CYAN); + + /** + * Holds groups of data units which are within a defined segment. + */ + 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; + + private long segmentStart=-1; + + private long segmenterEnd=-1; public SegmenterProcess(DLControl pamControlledUnit, PamDataBlock parentDataBlock) { @@ -76,7 +93,12 @@ public class SegmenterProcess extends PamProcess { segmenterDataBlock = new SegmenterDataBlock("Segmented Raw Data", this, dlControl.getDLParams().groupedSourceParams.getChanOrSeqBitmap()); + + segmenterGroupDataBlock = new SegmenterGroupDataBlock("Segmented data units", this, + dlControl.getDLParams().groupedSourceParams.getChanOrSeqBitmap()); + addOutputDataBlock(segmenterDataBlock); + addOutputDataBlock(segmenterGroupDataBlock); setProcessName("Segmenter"); @@ -93,6 +115,8 @@ public class SegmenterProcess extends PamProcess { public void prepareProcess() { setupSegmenter(); } + + /** * A list of data block class types which are compatible as parent data blocks @@ -104,7 +128,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)); } @@ -142,6 +166,7 @@ public class SegmenterProcess extends PamProcess { if (chanGroups!=null) { currentRawChunks = new GroupedRawData[chanGroups.length]; nextRawChunks = new GroupedRawData[chanGroups.length][]; + segmenterDetectionGroup = new SegmenterDetectionGroup[chanGroups.length]; } @@ -170,6 +195,8 @@ public class SegmenterProcess extends PamProcess { if (rawDataBlock==null) return; setParentDataBlock(rawDataBlock); + + this.firstClockUpdate = -1; } @@ -196,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); } @@ -208,11 +236,162 @@ public class SegmenterProcess extends PamProcess { else if (pamRawData instanceof ClipDataUnit) { newClipData(pamRawData); } + else if (pamRawData instanceof ConnectedRegionDataUnit) { + newWhistleData(pamRawData); + } } } + /** + * 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 synchronized void newWhistleData(PamDataUnit dataUnit) { + + + ConnectedRegionDataUnit whistle = (ConnectedRegionDataUnit) dataUnit; + + //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 algorithms + + int[] chanGroups = dlControl.getDLParams().groupedSourceParams.getChannelGroups(); + + 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())); + } + + + 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 = whistleStart + dataUnit.getDurationInMilliseconds().longValue(); + + if ((whistleStart>=segStart && whistleStart=segStart && whistleEnd=rawData[groupChan].length) { - arrayCopyLen=copyLen-(lastPos-rawData[groupChan].length)-1; - dataOverflow = copyLen - arrayCopyLen; - } - else { - arrayCopyLen= copyLen; - } - - arrayCopyLen = Math.max(arrayCopyLen, 0); - - //update the current grouped raw data unit with new raw data. - System.arraycopy(src, srcPos, rawData[groupChan], rawDataPointer[groupChan], arrayCopyLen); - - rawDataPointer[groupChan]=rawDataPointer[groupChan] + arrayCopyLen; - - return dataOverflow; - } - - /** - * Get the raw data grouped by channel. - * @return the raw acoustic data. - */ - public double[][] getRawData() { - return rawData; - } - - /** - * Get the current pointer for rawData. - * @return the data pointer per channel. - */ - public int[] getRawDataPointer() { - return rawDataPointer; - } - - - @Override - protected GroupedRawData clone() { - try { - GroupedRawData groupedRawData = (GroupedRawData) super.clone(); - - //hard clone the acoustic data - groupedRawData.rawData = new double[this.rawData.length][]; - for (int i=0; i { + + private DLControl dlControl; + + private static final String unknown = "Unknown"; + + public DLSpeciesManager(DLControl dlControl, PamDataBlock dataBlock) { + super(dataBlock); + this.dlControl = dlControl; + } + + @Override + public DataBlockSpeciesCodes getSpeciesCodes() { + DLClassName[] classNames = getClassNames(); + String[] classStrings = getClassStrings(classNames); + return new DataBlockSpeciesCodes(unknown, classStrings); + } + + /** + * Get just the strings of the class names. + * @param classNames + * @return + */ + private String[] getClassStrings(DLClassName[] classNames) { + if (classNames == null) { + return null; + } + String[] classStrings = new String[classNames.length]; + for (int i = 0; i < classStrings.length; i++) { + classStrings[i] = classNames[i].className; + } + return classStrings; + } + + /** + * Get the list of class names, or make a single unknown category. + * @return + */ + private DLClassName[] getClassNames() { + DLClassiferModel currentModel = dlControl.getDLModel(); + DLClassName[] classNames = null; + if (currentModel != null) { + classNames = currentModel.getClassNames(); + } + if (classNames == null) { + classNames = new DLClassName[1]; + classNames[0] = new DLClassName(unknown, (short) 0); + } + return classNames; + } + + + + @Override + public String getSpeciesCode(DLDetection dlDetection) { + int nAnnot = dlDetection.getNumDataAnnotations(); + if (nAnnot == 0) { + return unknown; + } +// DataAnnotation annot = dlDetection.getDataAnnotation(0); +// ArrayList results = null; +// if (annot instanceof DLAnnotation) { +// DLAnnotation dlAnnot = (DLAnnotation) annot; +// results = dlAnnot.getModelResults(); +// } + PredictionResult result = dlControl.getDLClassifyProcess().getBestModelResult(dlDetection); + if (result == null) { + return unknown; + } + int resInd = 0; + float bestScore = 0; + float[] scores = result.getPrediction(); + if (scores == null || scores.length == 0) { + return unknown; + } + for (int i = 0; i < scores.length; i++) { + if (scores[i] > bestScore) { + bestScore = scores[i]; + resInd = i; + } + } + String[] names = getClassStrings(getClassNames()); + if (resInd < names.length) { + return names[resInd]; + } + return unknown; + } + +} diff --git a/src/rawDeepLearningClassifier/tethys/DLTethysDataProvider.java b/src/rawDeepLearningClassifier/tethys/DLTethysDataProvider.java new file mode 100644 index 00000000..29cd7394 --- /dev/null +++ b/src/rawDeepLearningClassifier/tethys/DLTethysDataProvider.java @@ -0,0 +1,69 @@ +package rawDeepLearningClassifier.tethys; + +import java.io.Serializable; + +import javax.xml.bind.JAXBException; + +import PamguardMVC.PamDataBlock; +import PamguardMVC.PamDataUnit; +import nilus.Detection; +import nilus.Detection.Parameters; +import rawDeepLearningClassifier.DLControl; +import rawDeepLearningClassifier.RawDLParams; +import rawDeepLearningClassifier.dlClassification.DLClassiferModel; +import rawDeepLearningClassifier.dlClassification.DLDetection; +import tethys.TethysControl; +import tethys.output.StreamExportParams; +import tethys.output.TethysExportParams; +import tethys.pamdata.AutoTethysProvider; +import tethys.pamdata.TethysParameterPacker; + +public class DLTethysDataProvider extends AutoTethysProvider { + + private DLControl dlControl; + + public DLTethysDataProvider(TethysControl tethysControl, DLControl dlControl, PamDataBlock pamDataBlock) { + super(tethysControl, pamDataBlock); + this.dlControl = dlControl; + } + + @Override + public Detection createDetection(PamDataUnit dataUnit, TethysExportParams tethysExportParams, + StreamExportParams streamExportParams) { + Detection detection = super.createDetection(dataUnit, tethysExportParams, streamExportParams); + if (detection == null) { + return null; + } + DLDetection dlDetection = (DLDetection) dataUnit; + +// result = + String annotSummary = dlDetection.getAnnotationsSummaryString(); + if (annotSummary != null) { + Parameters parameters = detection.getParameters(); + addUserDefined(parameters, "Annotation", annotSummary); + } + + return detection; + } + + @Override + public nilus.AlgorithmType.Parameters getAlgorithmParameters() { + + /** + * Add the model parameters to the main dlControl parameters so that they get + * correctly serialized to the XML output +// */ +// RawDLParams dlParams = dlControl.getDLParams(); +// DLClassiferModel model = dlControl.getDLModel(); +// Serializable modelParams = null; +// if (model != null) { +// modelParams = model.getDLModelSettings(); +// } +// dlParams.setModelParameters(modelParams); + dlControl.checkModelParams(); + + nilus.AlgorithmType.Parameters parameters = super.getAlgorithmParameters(); + + return parameters; + } +} diff --git a/src/targetMotionModule/panels/TargetMotionMainPanel.java b/src/targetMotionModule/panels/TargetMotionMainPanel.java index 1751961a..7899b3ab 100644 --- a/src/targetMotionModule/panels/TargetMotionMainPanel.java +++ b/src/targetMotionModule/panels/TargetMotionMainPanel.java @@ -1,6 +1,7 @@ package targetMotionModule.panels; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Frame; @@ -13,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import javax.swing.ButtonGroup; -import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; @@ -24,6 +24,9 @@ import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.border.TitledBorder; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; +import org.kordamp.ikonli.swing.FontIcon; + import targetMotionModule.TargetMotionControl; import targetMotionModule.TargetMotionInformation; import targetMotionModule.TargetMotionLocaliser; @@ -35,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; @@ -74,8 +78,9 @@ public class TargetMotionMainPanel implements PamTabPanel public JTextField comment; private PamPanel southPanel; - public ImageIcon settings = new ImageIcon(ClassLoader - .getSystemResource("Resources/SettingsButtonSmall2.png")); +// public ImageIcon settings = new ImageIcon(ClassLoader +// .getSystemResource("Resources/SettingsButtonSmall2.png")); + public static FontIcon settings = FontIcon.of(PamSettingsIconButton.SETTINGS_IKON, PamSettingsIconButton.NORMAL_SIZE, Color.DARK_GRAY); public TargetMotionMainPanel(TargetMotionLocaliser targetMotionLocaliser) { diff --git a/src/targetMotionOld/algorithms/LeastSquaresNew.java b/src/targetMotionOld/algorithms/LeastSquaresNew.java index 57a7aa08..8cf4a869 100644 --- a/src/targetMotionOld/algorithms/LeastSquaresNew.java +++ b/src/targetMotionOld/algorithms/LeastSquaresNew.java @@ -78,8 +78,8 @@ public class LeastSquaresNew extends AbstractTargetMot } @Override - public LocaliserPane getSettingsPane() { - return detectionGroupLocaliser.getSettingsPane(); + public LocaliserPane getAlgorithmSettingsPane() { + return detectionGroupLocaliser.getAlgorithmSettingsPane(); } @Override diff --git a/src/targetMotionOld/algorithms/Simplex2DNew.java b/src/targetMotionOld/algorithms/Simplex2DNew.java index 95ec65d8..8b7e5298 100644 --- a/src/targetMotionOld/algorithms/Simplex2DNew.java +++ b/src/targetMotionOld/algorithms/Simplex2DNew.java @@ -66,8 +66,8 @@ public class Simplex2DNew extends AbstractTargetMotion } @Override - public LocaliserPane getSettingsPane() { - return detectionGroupLocaliser.getSettingsPane(); + public LocaliserPane getAlgorithmSettingsPane() { + return detectionGroupLocaliser.getAlgorithmSettingsPane(); } @Override diff --git a/src/test/matchedTemplateClassifier/MatchedTemplateClassifierTest.java b/src/test/matchedTemplateClassifier/MatchedTemplateClassifierTest.java new file mode 100644 index 00000000..cb4edc52 --- /dev/null +++ b/src/test/matchedTemplateClassifier/MatchedTemplateClassifierTest.java @@ -0,0 +1,179 @@ +package test.matchedTemplateClassifier; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import PamUtils.PamArrayUtils; +import PamUtils.complex.ComplexArray; +import Spectrogram.WindowFunction; +import clickDetector.ClickLength; +import fftManager.FastFFT; +import matchedTemplateClassifer.MTClassifier; +import matchedTemplateClassifer.MTClassifierTest; +import matchedTemplateClassifer.MTProcess; +import matchedTemplateClassifer.MatchTemplate; +import matchedTemplateClassifer.MatchedTemplateParams; +import matchedTemplateClassifer.MatchedTemplateResult; + +/** + * Tests for the matched click classifier. + */ +public class MatchedTemplateClassifierTest { + + + /** + * Test the match correlation algorithm by cross correlating a waveform with itself. Results + * are tested against the matlab xcorr funtion + */ + @Test + public void testMatchCorr() { + + System.out.println("Matched template classifier test: match corr"); + + + /* + * + * Test against MATLAB Xcorr function + * + * load('/Users/au671271/MATLAB-Drive/MATLAB/PAMGUARD/matchedclickclassifer/ + * _test/DS2templates_test.mat') + * + * wave = templates(1).wave; %test correlating waves with themselves using xcorr + * + * r = xcorr( wave , wave); disp(['Max correlation no-norm: ' num2str(max(r))]) + * + * r = xcorr( normalize(wave) , normalize(wave)); disp(['Max correlation rms: ' + * num2str(max(r))]) + * + * r = xcorr( wave/max(wave) , wave/max(wave)); disp(['Max correlation peak: ' + * num2str(max(r))]) + */ + + //Note that the return value of two identical waveforms depends on normalisation. + + String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS2templates_test.mat"; + //float sR = 288000; //sample rate in samples per second. + Path path = Paths.get(templteFilePath); + String templteFilePathR = path.toAbsolutePath().normalize().toString(); + + ArrayList templates = MTClassifierTest.importTemplates(templteFilePathR); + + List matchedClickResult ; + /** + * Correlate a template waveform with itself and check the result is 1. + */ + matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_RMS); + assertEquals(matchedClickResult.get(0).matchCorr, 1.0, 0.01); + + + matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_PEAK); + assertEquals(matchedClickResult.get(0).matchCorr, 134.5961, 0.01); + + + matchedClickResult = MTClassifierTest.testCorrelation(templates.get(0).waveform, templates.get(0).sR, templates, MatchedTemplateParams.NORMALIZATION_NONE); + assertEquals(matchedClickResult.get(0).matchCorr, 7.8457, 0.01); + + } + + /** + * Test the match correlation algorithm combined with click length algorithm. Here we awant to test that + * a long waveform + */ + @Test + public void testMatchCorrLen() { + + System.out.println("Matched template classifier test: match corr len"); + + String testClicksPath = "./src/test/resources/matchedTemplateClassifier/DS3clks_test.mat"; + Path path = Paths.get(testClicksPath); + testClicksPath=path.toAbsolutePath().normalize().toString(); + + String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS3templates_test.mat"; + path = Paths.get(templteFilePath); + templteFilePath=path.toAbsolutePath().normalize().toString(); + + //import some example clicks + float sR = 288000; //sample rate in samples per second. + ArrayList clicks = MTClassifierTest.importClicks(testClicksPath, sR); + + //import some templates + ArrayList templates = MTClassifierTest.importTemplates(templteFilePath); + + int index = 24; //the index of the test clck to use. + //values in MATLAB are9.73577287114938 8.82782814105430 3.51936216182390 +// System.out.println("Number of clicks: " + clicks.size() + " UID " + clicks.get(index).name); + + System.out.println("------Standard Length--------"); + List matchedClickResultLen1 = MTClassifierTest.testCorrelation(clicks.get(index).waveform, sR, templates); + + System.out.println("------Restricted Length--------"); + + int restrictedBins= 2048; + + ClickLength clickLength = new ClickLength(); + int[][] lengthPoints = clickLength.createLengthData(clicks.get(index), sR, 5.5, 3, false, null); + + double[] waveformLen = MTProcess.createRestrictedLenghtWave(clicks.get(index).waveform, lengthPoints[0], + restrictedBins, WindowFunction.hann(restrictedBins)); + + List matchedClickResultLen2 = MTClassifierTest.testCorrelation(waveformLen, sR, templates); + + assertEquals(matchedClickResultLen1.get(0).matchCorr, matchedClickResultLen2.get(0).matchCorr, 0.05); + + + } + + + /** + * Test the FFT method of correlating two identical waveforms. + */ + @Test + public void testFFTCorrelation() { + + System.out.println("Matched template classifier test: xcorr"); + + String templteFilePath= "./src/test/resources/matchedTemplateClassifier/DS2templates_test.mat"; + //float sR = 288000; //sample rate in samples per second. + Path path = Paths.get(templteFilePath); + String templteFilePathR = path.toAbsolutePath().normalize().toString(); + + ArrayList templates = MTClassifierTest.importTemplates(templteFilePathR); + + double[] waveform = templates.get(0).getWaveData()[0]; + waveform = MTClassifier.normaliseWaveform(waveform, MatchedTemplateParams.NORMALIZATION_RMS); + + FastFFT fft = new FastFFT(); + ComplexArray testClick = fft.rfft(waveform, waveform.length); + ComplexArray matchTemplate = fft.rfft(waveform, waveform.length).conj(); + + int fftLength = testClick.length()*2; + ComplexArray matchResult= new ComplexArray(fftLength); + + for (int i=0; i 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[]{50, 50. + 4.}, freqLimits, 10.); + + 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 groupedData = new ArrayList(); + + + groupedData.add(groupedRawData); + + ArrayList gwenericPrediciton = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); + + float[] output = gwenericPrediciton.get(0).getPrediction(); + + System.out.println("Right whale network output: " + output[0] + " " + output[1]); + + //test the predicitons are true. + assertTrue( output[0]<0.01 ); //noise + assertTrue( output[1]>0.98 ); //right whale + + genericModelWorker.closeModel(); + + } catch (IOException | UnsupportedAudioFileException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + assertEquals(false, true); + } + } + + + + /** + * Test Google's humpback whale model. + */ + @Test + public void humpbackWhaleTest() { + + System.out.println("*****Generic DL: Humpback whale test*****"); + + //relative paths to the resource folders. + String relModelPath = "./src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/saved_model.pb"; + String relWavPath = "./src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback.wav"; + String resultsPath = "./src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback_annotations.txt"; + + Path path = Paths.get(relModelPath); + + GenericModelWorker genericModelWorker = new GenericModelWorker(); + + GenericModelParams genericModelParams = new GenericModelParams(); + + genericModelParams.modelPath = path.toAbsolutePath().normalize().toString(); + + path = Paths.get(relWavPath); + String wavFilePath = path.toAbsolutePath().normalize().toString(); + + HumpbackWhaleAtlantic humpbackModel = new HumpbackWhaleAtlantic(); + humpbackModel.setParams(genericModelParams); + + System.out.println(genericModelParams); + + //prep the model + genericModelWorker.prepModel(genericModelParams, null); + + try { + //load audio + AudioData soundData = DLUtils.loadWavFile(wavFilePath); + + + //load true predictions file. + File file = new File(resultsPath); + FileReader fr = new FileReader(file); + BufferedReader br = new BufferedReader(fr); + String line; + int ind=0; + + while((line = br.readLine()) != null){ + if (ind>0) { + //read the data from the text file + String[] data = line.split("\t"); + int chunkID = Integer.valueOf(data[0]); + + double startTimeS = Double.valueOf(data[1]); + double endTimeS = Double.valueOf(data[2]); + double prediction = Double.valueOf(data[5]); + + int startChunk = (int) (soundData.sampleRate*startTimeS); //humpback whale call + int chunkSize = (int) Math.ceil((endTimeS-startTimeS)*soundData.sampleRate); + + chunkSize=7755; + + GroupedRawData groupedRawData = new GroupedRawData(0, 1, 0, chunkSize, chunkSize); + groupedRawData.copyRawData(soundData.getScaledSampleAmplitudes(), startChunk, chunkSize, 0); + + + ArrayList groupedData = new ArrayList(); + + groupedData.add(groupedRawData); + + ArrayList genericPrediction = genericModelWorker.runModel(groupedData, soundData.sampleRate, 0); + + float[] output = genericPrediction.get(0).getPrediction(); + + System.out.println(String.format("Chunk %d %d Predicted output: %.2f true output: %.2f passed: %b", chunkID, startChunk, + output[0], prediction, output[0]>prediction*0.9 && output[0] groupedData = new ArrayList(); + groupedData.add(groupedRawData); + + 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; + System.out.println( i+ " : Ketos whale network output: " + output[0] + " " + output[1] + " " + testPassed); + + assertTrue(testPassed); + + } + + ketosWorker2.closeModel(); + + } catch (IOException | UnsupportedAudioFileException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + assertEquals(false, true); + } + } + + + + + +} diff --git a/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java b/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java new file mode 100644 index 00000000..e3ea9c5b --- /dev/null +++ b/src/test/rawDeepLearningClassifier/KooguDLClassifierTest.java @@ -0,0 +1,135 @@ +package test.rawDeepLearningClassifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.jamdev.jdl4pam.utils.DLMatFile; +import org.jamdev.jdl4pam.utils.DLUtils; +import org.jamdev.jpamutils.wavFiles.AudioData; +import org.junit.jupiter.api.Test; + +import rawDeepLearningClassifier.dlClassification.animalSpot.StandardModelParams; +import rawDeepLearningClassifier.dlClassification.genericModel.StandardPrediction; +import rawDeepLearningClassifier.dlClassification.koogu.KooguModelWorker; +import rawDeepLearningClassifier.segmenter.GroupedRawData; +import us.hebi.matlab.mat.format.Mat5; +import us.hebi.matlab.mat.format.Mat5File; +import us.hebi.matlab.mat.types.Matrix; + +public class KooguDLClassifierTest { + + + /** + * Test the koogu classifier and tests are working properly. This tests loading the koogu model and also using + * functions in KooguWorker. + */ + @Test + public void kooguClassifierTest() { + //relative paths to the resource folders. + String relModelPath = "./src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/blue_whale_24.kgu"; + String relWavPath = "./src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/20190527_190000.wav"; + String relMatPath = "./src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.mat"; + + runKooguClassifier( relModelPath, relWavPath, relMatPath); + } + + public static void runKooguClassifier(String relModelPath, String relWavPath, String relMatPath) { + + + Path path = Paths.get(relModelPath); + + KooguModelWorker kooguWorker = new KooguModelWorker(); + + StandardModelParams genericModelParams = new StandardModelParams(); + genericModelParams.modelPath = path.toAbsolutePath().normalize().toString(); + + //prep the model - all setting are included within the model + kooguWorker.prepModel(genericModelParams, null); + System.out.println("seglen: " + genericModelParams.defaultSegmentLen); + + + for (int i=0; i groupedData = new ArrayList(); + groupedData.add(groupedRawData); + + + 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; + + System.out.println(String.format("Chunk %d %d output[0]: predicted %.5f true %.5f ; output[1]: predicted %.5f true %.5f %b",i, startChunk, + output[0], kooguPredicitions[i][1], output[1], kooguPredicitions[i][2],testPassed)); + + if (testPassed) { + truecount++; + } + } + + //there are occasionaly slight differences between PMAGuard and Python so just make sure most data points are the same. + double percTrue = 100*((double) truecount)/kooguPredicitions.length; + + System.out.println(String.format("Perecentage results true: %.2f count %d", percTrue, truecount)); + + //at least 90% of results must match for the dataset + assertTrue(percTrue>0.9); + + kooguWorker.closeModel(); + + } catch (IOException | UnsupportedAudioFileException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + assertEquals(false, true); + } + } + +} \ No newline at end of file diff --git a/src/test/rawDeepLearningClassifier/PamZipDLClassifierTest.java b/src/test/rawDeepLearningClassifier/PamZipDLClassifierTest.java new file mode 100644 index 00000000..fac851d3 --- /dev/null +++ b/src/test/rawDeepLearningClassifier/PamZipDLClassifierTest.java @@ -0,0 +1,24 @@ +package test.rawDeepLearningClassifier; + +import org.junit.jupiter.api.Test; + +public class PamZipDLClassifierTest { + + /** + * Test the koogu classifier and tests are working properly. This tests loading the koogu model and also using + * functions in KooguWorker. + */ + @Test + public void zipClassifierTest() { + //relative paths to the resource folders. + String relModelPath = "./src/test/resources/rawDeepLearningClassifier/PamZip/blue_whale_24.zip"; + + //the zip classifier is the same as the + String relWavPath = "./src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/20190527_190000.wav"; + String relMatPath = "./src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.mat"; + + //metadata says it should be used with Koogu classifier. + KooguDLClassifierTest.runKooguClassifier( relModelPath, relWavPath, relMatPath); + } + +} diff --git a/src/test/resources/matchedTemplateClassifier/DS2templates_test.mat b/src/test/resources/matchedTemplateClassifier/DS2templates_test.mat new file mode 100644 index 00000000..fa0e893a Binary files /dev/null and b/src/test/resources/matchedTemplateClassifier/DS2templates_test.mat differ diff --git a/src/test/resources/matchedTemplateClassifier/DS3clks_test.mat b/src/test/resources/matchedTemplateClassifier/DS3clks_test.mat new file mode 100644 index 00000000..7e377643 Binary files /dev/null and b/src/test/resources/matchedTemplateClassifier/DS3clks_test.mat differ diff --git a/src/test/resources/matchedTemplateClassifier/DS3templates_test.mat b/src/test/resources/matchedTemplateClassifier/DS3templates_test.mat new file mode 100644 index 00000000..8e668b29 Binary files /dev/null and b/src/test/resources/matchedTemplateClassifier/DS3templates_test.mat differ diff --git a/src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat b/src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat new file mode 100644 index 00000000..4b430277 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/DelphinID/whistle_image_example.mat differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/keras_metadata.pb b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/keras_metadata.pb new file mode 100644 index 00000000..ac53b00b --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/keras_metadata.pb @@ -0,0 +1,348 @@ + +;root"_tf_keras_sequential*;{"name": "sequential", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": false, "class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 7755]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}}, {"class_name": "Lambda", "config": {"name": "lambda", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAAEAAAAUwAAAHMMAAAAdACgAXwAZAGhAlMAqQJO6f////+pAtoCdGba\nC2V4cGFuZF9kaW1zqQHaAXSpAHIIAAAA+isvaG9tZS92aW5jZW50L0NvZGUvTUEvaGJkZXQvaGJk\nZXQvbW9kZWxzLnB52gg8bGFtYmRhPisAAADzAAAAAA==\n", null, null]}, "function_type": "lambda", "module": "hbdet.models", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}}, {"class_name": "MelSpectrogram", "config": {"name": "mel_spectrogram", "trainable": true, "dtype": "float32"}}, {"class_name": "PCEN", "config": {"layer was saved without config": true}}, {"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 128, 64]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_3"}}, {"class_name": "Lambda", "config": {"name": "make_depth_one", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAAEAAAAUwAAAHMMAAAAdACgAXwAZAGhAlMAKQJO6QMAAAApAtoCdGba\nC2V4cGFuZF9kaW1zKQHaAXSpAHIFAAAAekYvaG9tZS92aW5jZW50L0NvZGUvTUEvaGJkZXQvaGJk\nZXQvaHVtcGJhY2tfbW9kZWxfZGlyL2h1bXBiYWNrX21vZGVsLnB52gg8bGFtYmRhPt8AAADzAAAA\nAA==\n", null, null]}, "function_type": "lambda", "module": "hbdet.humpback_model_dir.humpback_model", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}}, {"class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [7, 7]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}}, {"class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}}, {"class_name": "MaxPooling2D", "config": {"name": "pool_pre_resnet", "trainable": true, "dtype": "float32", "pool_size": {"class_name": "__tuple__", "items": [3, 3]}, "padding": "same", "strides": {"class_name": "__tuple__", "items": [2, 2]}, "data_format": "channels_last"}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "Block", "config": {"layer was saved without config": true}}, {"class_name": "GlobalAveragePooling2D", "config": {"name": "pool", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation", "trainable": true, "dtype": "float32", "activation": "sigmoid"}}]}, "shared_object_id": 20, "input_spec": [{"class_name": "InputSpec", "config": {"dtype": null, "shape": {"class_name": "__tuple__", "items": [null, 7755]}, "ndim": 2, "max_ndim": null, "min_ndim": null, "axes": {}}}], "build_input_shape": {"class_name": "TensorShape", "items": [null, 7755]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7755]}, "float32", "input_1"]}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7755]}, "float32", "input_1"]}, "keras_version": "2.10.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential"}, "training_config": {"loss": {"class_name": "BinaryCrossentropy", "config": {"reduction": "auto", "name": "binary_crossentropy", "from_logits": false, "label_smoothing": 0.0, "axis": -1}, "shared_object_id": 22}, "metrics": [[{"class_name": "BinaryAccuracy", "config": {"name": "binary_accuracy", "dtype": "float32", "threshold": 0.5}, "shared_object_id": 23}, {"class_name": "Precision", "config": {"name": "precision", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 24}, {"class_name": "Recall", "config": {"name": "recall", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 25}, {"class_name": "Addons>FBetaScore", "config": {"name": "fbeta", "dtype": "float32", "num_classes": 1, "average": null, "beta": 0.5, "threshold": 0.5}, "shared_object_id": 26}, {"class_name": "Addons>FBetaScore", "config": {"name": "fbeta1", "dtype": "float32", "num_classes": 1, "average": null, "beta": 1.0, "threshold": 0.5}, "shared_object_id": 27}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.0005, "decay": 0.0, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}}}}2 + root.layer-0"_tf_keras_layer*{"name": "lambda", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Lambda", "config": {"name": "lambda", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAAEAAAAUwAAAHMMAAAAdACgAXwAZAGhAlMAqQJO6f////+pAtoCdGba\nC2V4cGFuZF9kaW1zqQHaAXSpAHIIAAAA+isvaG9tZS92aW5jZW50L0NvZGUvTUEvaGJkZXQvaGJk\nZXQvbW9kZWxzLnB52gg8bGFtYmRhPisAAADzAAAAAA==\n", null, null]}, "function_type": "lambda", "module": "hbdet.models", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}, "shared_object_id": 1, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7755]}}2 + root.layer-1"_tf_keras_layer*{"name": "mel_spectrogram", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MelSpectrogram", "config": {"name": "mel_spectrogram", "trainable": true, "dtype": "float32"}, "shared_object_id": 2, "__passive_serialization__": true, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7755, 1]}}2 +root.layer_with_weights-0"_tf_keras_layer*{"name": "pcen", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "PCEN", "config": {"layer was saved without config": true}}2 + root.layer-3"_tf_keras_input_layer*{"class_name": "InputLayer", "name": "input_3", "dtype": "float32", "sparse": false, "ragged": false, "batch_input_shape": {"class_name": "__tuple__", "items": [null, 128, 64]}, "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 128, 64]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_3"}}2 + root.layer-4"_tf_keras_layer*{"name": "make_depth_one", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Lambda", "config": {"name": "make_depth_one", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAAEAAAAUwAAAHMMAAAAdACgAXwAZAGhAlMAKQJO6QMAAAApAtoCdGba\nC2V4cGFuZF9kaW1zKQHaAXSpAHIFAAAAekYvaG9tZS92aW5jZW50L0NvZGUvTUEvaGJkZXQvaGJk\nZXQvaHVtcGJhY2tfbW9kZWxfZGlyL2h1bXBiYWNrX21vZGVsLnB52gg8bGFtYmRhPt8AAADzAAAA\nAA==\n", null, null]}, "function_type": "lambda", "module": "hbdet.humpback_model_dir.humpback_model", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}, "shared_object_id": 4}2 + +root.layer_with_weights-1"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [7, 7]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 5}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 6}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 7, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1}}, "shared_object_id": 28}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 128, 64, 1]}}2 + root.layer_with_weights-2"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 9}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 10}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 11}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 12, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 29}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 128, 64, 64]}}2 + root.layer-7"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 13}2 +  root.layer-8"_tf_keras_layer*{"name": "pool_pre_resnet", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MaxPooling2D", "config": {"name": "pool_pre_resnet", "trainable": true, "dtype": "float32", "pool_size": {"class_name": "__tuple__", "items": [3, 3]}, "padding": "same", "strides": {"class_name": "__tuple__", "items": [2, 2]}, "data_format": "channels_last"}, "shared_object_id": 14, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {}}, "shared_object_id": 30}}2 + +root.layer_with_weights-3"_tf_keras_layer*{"name": "block_widen_0", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 + root.layer_with_weights-4"_tf_keras_layer*{"name": "block_1_0", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 + root.layer_with_weights-5"_tf_keras_layer*{"name": "block_2_0", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 + root.layer_with_weights-6"_tf_keras_layer*{"name": "block_widen_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-7"_tf_keras_layer*{"name": "block_1_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-8"_tf_keras_layer*{"name": "block_2_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-9"_tf_keras_layer*{"name": "block_3_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-10"_tf_keras_layer*{"name": "block_widen_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-11"_tf_keras_layer*{"name": "block_1_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-12"_tf_keras_layer*{"name": "block_2_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-13"_tf_keras_layer*{"name": "block_3_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-14"_tf_keras_layer*{"name": "block_4_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-15"_tf_keras_layer*{"name": "block_5_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-16"_tf_keras_layer*{"name": "block_widen_3", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-17"_tf_keras_layer*{"name": "block_1_3", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 +root.layer_with_weights-18"_tf_keras_layer*{"name": "block_2_3", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Block", "config": {"layer was saved without config": true}}2 + root.layer-25"_tf_keras_layer*{"name": "pool", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "GlobalAveragePooling2D", "config": {"name": "pool", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "shared_object_id": 15, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {}}, "shared_object_id": 31}}2 +root.layer_with_weights-19"_tf_keras_layer*{"name": "dense", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 16}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 17}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 18, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 2048}}, "shared_object_id": 32}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 2048]}}2 + root.layer-27"_tf_keras_layer*{"name": "activation", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Activation", "config": {"name": "activation", "trainable": true, "dtype": "float32", "activation": "sigmoid"}, "shared_object_id": 19}2 + +2root.layer-1._stft"_tf_keras_layer* {"name": "stft", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Lambda", "config": {"name": "stft", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAAFAAAAEwAAAHMiAAAAdABqAWoCdACgA3wAZAGhAogAagRqBYgAagRq\nBmQCjQNTACkDTukCAAAAKQLaDGZyYW1lX2xlbmd0aNoKZnJhbWVfc3RlcCkH2gJ0ZtoGc2lnbmFs\n2gRzdGZ02gdzcXVlZXpl2gZjb25maWfaEXN0ZnRfZnJhbWVfbGVuZ3Ro2g9zdGZ0X2ZyYW1lX3N0\nZXCpAdoBdCkB2gRzZWxmqQD6QS9ob21lL3ZpbmNlbnQvQ29kZS9NQS9oYmRldC9oYmRldC9odW1w\nYmFja19tb2RlbF9kaXIvZnJvbnRfZW5kLnB52gg8bGFtYmRhPkkAAABzCAAAAAYBCgEGAQb9\n", null, {"class_name": "__tuple__", "items": [{"class_name": "MelSpectrogram", "config": {"name": "mel_spectrogram", "trainable": true, "dtype": "float32"}, "shared_object_id": 2, "__passive_serialization__": true}]}]}, "function_type": "lambda", "module": "hbdet.humpback_model_dir.front_end", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}, "shared_object_id": 33, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7755, 1]}}2 + 3root.layer-1._bin"_tf_keras_layer* {"name": "mel_bins", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Lambda", "config": {"name": "mel_bins", "trainable": true, "dtype": "float32", "function": {"class_name": "__tuple__", "items": ["4wEAAAAAAAAAAAAAAAEAAAANAAAAEwAAAHM+AAAAdACgAXQAoAJ0AKADfAChAXQAagRqBYgBagZq\nB4gAiAFqBmoIiAFqBmoJiAFqBmoKZAFkAo0GZAOhA6EBUwApBE7aBm1hdHJpeCkG2gxudW1fbWVs\nX2JpbnPaFG51bV9zcGVjdHJvZ3JhbV9iaW5z2gtzYW1wbGVfcmF0ZdoQbG93ZXJfZWRnZV9oZXJ0\netoQdXBwZXJfZWRnZV9oZXJ0etoEbmFtZekBAAAAKQvaAnRm2gZzcXVhcmXaCXRlbnNvcmRvdNoD\nYWJz2gZzaWduYWzaG2xpbmVhcl90b19tZWxfd2VpZ2h0X21hdHJpeNoGY29uZmln2glmcmVxX2Jp\nbnNyBAAAANoHbG93ZXJfZtoHdXBwZXJfZqkB2gF0qQJyAwAAANoEc2VsZqkA+kEvaG9tZS92aW5j\nZW50L0NvZGUvTUEvaGJkZXQvaGJkZXQvaHVtcGJhY2tfbW9kZWxfZGlyL2Zyb250X2VuZC5wedoI\nPGxhbWJkYT5RAAAAcxoAAAAEAQQBCAEGAQYBAgEGAQYBBgEC+gQGAvgC/w==\n", null, {"class_name": "__tuple__", "items": [513, {"class_name": "MelSpectrogram", "config": {"name": "mel_spectrogram", "trainable": true, "dtype": "float32"}, "shared_object_id": 2, "__passive_serialization__": true}]}]}, "function_type": "lambda", "module": "hbdet.humpback_model_dir.front_end", "output_shape": null, "output_shape_type": "raw", "output_shape_module": null, "arguments": {}}, "shared_object_id": 34, "build_input_shape": {"class_name": "TensorShape", "items": [null, 128, 513]}}2 + =root.layer_with_weights-0.ema"_tf_keras_rnn_layer* +{"name": "simple_rnn", "trainable": false, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "SimpleRNN", "config": {"name": "simple_rnn", "trainable": false, "dtype": "float32", "return_sequences": true, "return_state": false, "go_backwards": false, "stateful": false, "unroll": false, "time_major": false, "units": 64, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "Identity", "config": {"gain": 0.025}, "shared_object_id": 35}, "recurrent_initializer": {"class_name": "Identity", "config": {"gain": 0.975}, "shared_object_id": 36}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 37}, "kernel_regularizer": null, "recurrent_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "recurrent_constraint": null, "bias_constraint": null, "dropout": 0.0, "recurrent_dropout": 0.0}, "shared_object_id": 39, "input_spec": [{"class_name": "InputSpec", "config": {"dtype": null, "shape": {"class_name": "__tuple__", "items": [null, null, 64]}, "ndim": 3, "max_ndim": null, "min_ndim": null, "axes": {}}, "shared_object_id": 40}], "build_input_shape": {"class_name": "TensorShape", "items": [null, null, 64]}}2 +p(root.layer_with_weights-3._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +q$root.layer_with_weights-3._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +r%root.layer_with_weights-3._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 41}2 +z(root.layer_with_weights-4._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +{$root.layer_with_weights-4._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +|%root.layer_with_weights-4._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 42}2 +(root.layer_with_weights-5._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +$root.layer_with_weights-5._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-5._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 43}2 +(root.layer_with_weights-6._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +$root.layer_with_weights-6._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-6._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 44}2 +(root.layer_with_weights-7._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +$root.layer_with_weights-7._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-7._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 45}2 +(root.layer_with_weights-8._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +$root.layer_with_weights-8._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-8._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 46}2 +(root.layer_with_weights-9._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +$root.layer_with_weights-9._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-9._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 47}2 +)root.layer_with_weights-10._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-10._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-10._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 48}2 +)root.layer_with_weights-11._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-11._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-11._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 49}2 +)root.layer_with_weights-12._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-12._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-12._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 50}2 +)root.layer_with_weights-13._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-13._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-13._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 51}2 +)root.layer_with_weights-14._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-14._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-14._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 52}2 +)root.layer_with_weights-15._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-15._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-15._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 53}2 +)root.layer_with_weights-16._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-16._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-16._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 54}2 +)root.layer_with_weights-17._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-17._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-17._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 55}2 +)root.layer_with_weights-18._residual_path"_tf_keras_layer*{"name": "residual_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ResidualPath", "config": {"layer was saved without config": true}}2 +%root.layer_with_weights-18._main_path"_tf_keras_layer*{"name": "main_path", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "MainPath", "config": {"layer was saved without config": true}}2 +&root.layer_with_weights-18._activation"_tf_keras_layer*{"name": "relu_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_output", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 56}2 +"root.layer_with_weights-0.ema.cell"_tf_keras_layer*{"name": "simple_rnn_cell", "trainable": false, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "SimpleRNNCell", "config": {"name": "simple_rnn_cell", "trainable": false, "dtype": "float32", "units": 64, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "Identity", "config": {"gain": 0.025}, "shared_object_id": 35}, "recurrent_initializer": {"class_name": "Identity", "config": {"gain": 0.975}, "shared_object_id": 36}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 37}, "kernel_regularizer": null, "recurrent_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "recurrent_constraint": null, "bias_constraint": null, "dropout": 0.0, "recurrent_dropout": 0.0}, "shared_object_id": 38, "build_input_shape": {"class_name": "__tuple__", "items": [null, 64]}}2 +root.keras_api.metrics.0"_tf_keras_metric*{"class_name": "Mean", "name": "loss", "dtype": "float32", "config": {"name": "loss", "dtype": "float32"}, "shared_object_id": 57}2 +root.keras_api.metrics.1"_tf_keras_metric*{"class_name": "BinaryAccuracy", "name": "binary_accuracy", "dtype": "float32", "config": {"name": "binary_accuracy", "dtype": "float32", "threshold": 0.5}, "shared_object_id": 23}2 +root.keras_api.metrics.2"_tf_keras_metric*{"class_name": "Precision", "name": "precision", "dtype": "float32", "config": {"name": "precision", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 24}2 +root.keras_api.metrics.3"_tf_keras_metric*{"class_name": "Recall", "name": "recall", "dtype": "float32", "config": {"name": "recall", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 25}2 +root.keras_api.metrics.4"_tf_keras_metric*{"class_name": "Addons>FBetaScore", "name": "fbeta", "dtype": "float32", "config": {"name": "fbeta", "dtype": "float32", "num_classes": 1, "average": null, "beta": 0.5, "threshold": 0.5}, "shared_object_id": 26}2 +root.keras_api.metrics.5"_tf_keras_metric*{"class_name": "Addons>FBetaScore", "name": "fbeta1", "dtype": "float32", "config": {"name": "fbeta1", "dtype": "float32", "num_classes": 1, "average": null, "beta": 1.0, "threshold": 0.5}, "shared_object_id": 27}2 + + 2root.layer_with_weights-3._residual_path._layers.0"_tf_keras_layer* {"name": "conv_residual", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_residual", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 58}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 59}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 60, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 61}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + + 2root.layer_with_weights-3._residual_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_residual", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_residual", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 62}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 63}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 64}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 65}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 66, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 67}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-3._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 68}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 69}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 70, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 71}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-3._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 72}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 73}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 74}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 75}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 76, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 77}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-3._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 78}2 + + .root.layer_with_weights-3._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 79}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 80}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 81, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 82}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-3._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 83}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 84}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 85}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 86}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 87, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 88}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-3._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 89}2 + + .root.layer_with_weights-3._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 90}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 91}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 92, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 93}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-3._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 94}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 95}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 96}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 97}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 98, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 99}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-4._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 100}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 101}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 102, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 103}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-4._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 104}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 105}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 106}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 107}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 108, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 109}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-4._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 110}2 + + .root.layer_with_weights-4._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 111}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 112}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 113, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 114}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-4._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 115}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 116}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 117}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 118}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 119, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 120}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-4._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 121}2 + + .root.layer_with_weights-4._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 122}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 123}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 124, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 125}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-4._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 126}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 127}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 128}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 129}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 130, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 131}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-5._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 132}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 133}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 134, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 135}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-5._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 136}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 137}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 138}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 139}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 140, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 141}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-5._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 142}2 + + .root.layer_with_weights-5._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 143}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 144}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 145, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 146}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-5._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 147}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 148}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 149}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 150}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 151, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 64}}, "shared_object_id": 152}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 + .root.layer_with_weights-5._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 153}2 + + .root.layer_with_weights-5._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 154}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 155}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 156, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 64}}, "shared_object_id": 157}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 64]}}2 +  .root.layer_with_weights-5._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 158}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 159}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 160}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 161}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 162, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 163}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + 2root.layer_with_weights-6._residual_path._layers.0"_tf_keras_layer* {"name": "conv_residual", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_residual", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 164}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 165}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 166, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 167}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + 2root.layer_with_weights-6._residual_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_residual", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_residual", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 168}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 169}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 170}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 171}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 172, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 173}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + .root.layer_with_weights-6._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 174}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 175}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 176, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 177}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 64, 32, 256]}}2 + + .root.layer_with_weights-6._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 178}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 179}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 180}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 181}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 182, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 183}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + .root.layer_with_weights-6._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 184}2 + + .root.layer_with_weights-6._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 185}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 186}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 187, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 188}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  .root.layer_with_weights-6._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 189}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 190}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 191}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 192}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 193, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 194}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + .root.layer_with_weights-6._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 195}2 + + .root.layer_with_weights-6._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 196}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 197}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 198, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 199}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  .root.layer_with_weights-6._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 200}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 201}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 202}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 203}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 204, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 205}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + .root.layer_with_weights-7._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 206}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 207}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 208, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 209}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + .root.layer_with_weights-7._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 210}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 211}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 212}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 213}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 214, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 215}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + .root.layer_with_weights-7._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 216}2 + + .root.layer_with_weights-7._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 217}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 218}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 219, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 220}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  .root.layer_with_weights-7._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 221}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 222}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 223}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 224}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 225, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 226}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + .root.layer_with_weights-7._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 227}2 + + .root.layer_with_weights-7._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 228}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 229}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 230, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 231}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  +.root.layer_with_weights-7._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 232}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 233}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 234}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 235}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 236, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 237}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +.root.layer_with_weights-8._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 238}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 239}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 240, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 241}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +.root.layer_with_weights-8._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 242}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 243}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 244}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 245}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 246, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 247}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + +.root.layer_with_weights-8._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 248}2 + + +.root.layer_with_weights-8._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 249}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 250}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 251, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 252}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  +.root.layer_with_weights-8._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 253}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 254}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 255}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 256}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 257, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 258}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + +.root.layer_with_weights-8._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 259}2 + + +.root.layer_with_weights-8._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 260}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 261}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 262, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 263}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  +.root.layer_with_weights-8._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 264}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 265}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 266}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 267}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 268, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 269}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +.root.layer_with_weights-9._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 270}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 271}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 272, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 273}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +.root.layer_with_weights-9._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 274}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 275}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 276}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 277}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 278, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 279}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + +.root.layer_with_weights-9._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 280}2 + + +.root.layer_with_weights-9._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 281}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 282}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 283, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 284}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  +.root.layer_with_weights-9._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 285}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 286}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 287}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 288}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 289, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 128}}, "shared_object_id": 290}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 + +.root.layer_with_weights-9._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 291}2 + + +.root.layer_with_weights-9._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 292}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 293}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 294, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 128}}, "shared_object_id": 295}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 128]}}2 +  +.root.layer_with_weights-9._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 296}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 297}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 298}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 299}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 300, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 301}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +3root.layer_with_weights-10._residual_path._layers.0"_tf_keras_layer* {"name": "conv_residual", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_residual", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 302}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 303}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 304, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 305}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +3root.layer_with_weights-10._residual_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_residual", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_residual", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 306}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 307}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 308}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 309}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 310, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 311}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + +/root.layer_with_weights-10._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 312}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 313}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 314, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 315}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 32, 16, 512]}}2 + + +/root.layer_with_weights-10._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 316}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 317}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 318}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 319}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 320, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 321}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-10._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 322}2 + + +/root.layer_with_weights-10._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 323}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 324}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 325, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 326}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  +/root.layer_with_weights-10._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 327}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 328}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 329}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 330}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 331, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 332}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-10._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 333}2 + + +/root.layer_with_weights-10._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 334}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 335}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 336, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 337}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + +/root.layer_with_weights-10._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 338}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 339}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 340}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 341}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 342, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 343}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + +/root.layer_with_weights-11._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 344}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 345}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 346, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 347}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + +/root.layer_with_weights-11._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 348}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 349}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 350}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 351}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 352, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 353}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-11._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 354}2 + + +/root.layer_with_weights-11._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 355}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 356}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 357, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 358}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  +/root.layer_with_weights-11._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 359}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 360}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 361}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 362}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 363, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 364}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-11._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 365}2 + + +/root.layer_with_weights-11._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 366}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 367}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 368, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 369}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + +/root.layer_with_weights-11._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 370}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 371}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 372}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 373}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 374, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 375}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + +/root.layer_with_weights-12._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 376}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 377}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 378, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 379}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + +/root.layer_with_weights-12._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 380}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 381}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 382}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 383}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 384, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 385}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-12._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 386}2 + + +/root.layer_with_weights-12._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 387}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 388}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 389, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 390}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  +/root.layer_with_weights-12._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 391}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 392}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 393}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 394}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 395, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 396}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + +/root.layer_with_weights-12._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 397}2 + + +/root.layer_with_weights-12._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 398}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 399}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 400, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 401}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + +/root.layer_with_weights-12._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 402}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 403}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 404}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 405}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 406, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 407}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-13._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 408}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 409}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 410, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 411}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-13._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 412}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 413}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 414}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 415}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 416, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 417}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-13._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 418}2 + + /root.layer_with_weights-13._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 419}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 420}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 421, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 422}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  /root.layer_with_weights-13._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 423}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 424}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 425}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 426}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 427, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 428}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-13._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 429}2 + + /root.layer_with_weights-13._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 430}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 431}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 432, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 433}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + /root.layer_with_weights-13._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 434}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 435}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 436}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 437}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 438, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 439}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-14._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 440}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 441}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 442, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 443}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-14._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 444}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 445}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 446}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 447}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 448, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 449}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-14._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 450}2 + + /root.layer_with_weights-14._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 451}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 452}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 453, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 454}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  /root.layer_with_weights-14._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 455}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 456}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 457}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 458}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 459, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 460}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-14._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 461}2 + + /root.layer_with_weights-14._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 462}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 463}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 464, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 465}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + /root.layer_with_weights-14._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 466}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 467}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 468}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 469}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 470, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 471}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-15._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 472}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 473}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 474, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 475}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-15._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 476}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 477}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 478}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 479}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 480, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 481}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-15._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 482}2 + + /root.layer_with_weights-15._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 256, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 483}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 484}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 485, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 486}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 +  /root.layer_with_weights-15._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 487}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 488}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 489}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 490}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 491, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 256}}, "shared_object_id": 492}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + /root.layer_with_weights-15._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 493}2 + + /root.layer_with_weights-15._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 1024, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 494}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 495}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 496, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 256}}, "shared_object_id": 497}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 256]}}2 + + /root.layer_with_weights-15._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 498}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 499}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 500}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 501}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 502, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 1024}}, "shared_object_id": 503}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + 3root.layer_with_weights-16._residual_path._layers.0"_tf_keras_layer* {"name": "conv_residual", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_residual", "trainable": true, "dtype": "float32", "filters": 2048, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 504}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 505}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 506, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 507}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + 3root.layer_with_weights-16._residual_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_residual", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_residual", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 508}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 509}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 510}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 511}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 512, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 2048}}, "shared_object_id": 513}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 + + /root.layer_with_weights-16._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [2, 2]}, "padding": "valid", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 514}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 515}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 516, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 1024}}, "shared_object_id": 517}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16, 8, 1024]}}2 + + /root.layer_with_weights-16._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 518}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 519}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 520}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 521}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 522, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 523}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-16._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 524}2 + + /root.layer_with_weights-16._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 525}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 526}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 527, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 528}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-16._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 529}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 530}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 531}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 532}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 533, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 534}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-16._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 535}2 + + /root.layer_with_weights-16._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 2048, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 536}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 537}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 538, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 539}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-16._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 540}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 541}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 542}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 543}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 544, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 2048}}, "shared_object_id": 545}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 + + /root.layer_with_weights-17._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 546}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 547}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 548, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 2048}}, "shared_object_id": 549}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 + + /root.layer_with_weights-17._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 550}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 551}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 552}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 553}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 554, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 555}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-17._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 556}2 + + /root.layer_with_weights-17._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 557}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 558}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 559, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 560}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-17._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 561}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 562}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 563}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 564}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 565, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 566}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-17._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 567}2 + + /root.layer_with_weights-17._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 2048, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 568}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 569}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 570, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 571}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-17._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 572}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 573}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 574}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 575}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 576, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 2048}}, "shared_object_id": 577}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 + + /root.layer_with_weights-18._main_path._layers.0"_tf_keras_layer* {"name": "conv_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_bottleneck", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 578}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 579}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 580, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 2048}}, "shared_object_id": 581}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 + + /root.layer_with_weights-18._main_path._layers.1"_tf_keras_layer* {"name": "batch_normalization_bottleneck", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_bottleneck", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 582}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 583}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 584}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 585}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 586, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 587}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-18._main_path._layers.2"_tf_keras_layer*{"name": "relu_bottleneck", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu_bottleneck", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 588}2 + + /root.layer_with_weights-18._main_path._layers.3"_tf_keras_layer* {"name": "conv", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv", "trainable": true, "dtype": "float32", "filters": 512, "kernel_size": {"class_name": "__tuple__", "items": [3, 3]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 589}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 590}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 591, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 592}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-18._main_path._layers.4"_tf_keras_layer* {"name": "batch_normalization", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 593}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 594}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 595}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 596}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 597, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 512}}, "shared_object_id": 598}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 + /root.layer_with_weights-18._main_path._layers.5"_tf_keras_layer*{"name": "relu", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "ReLU", "config": {"name": "relu", "trainable": true, "dtype": "float32", "max_value": null, "negative_slope": 0.0, "threshold": 0.0}, "shared_object_id": 599}2 + + /root.layer_with_weights-18._main_path._layers.6"_tf_keras_layer* {"name": "conv_output", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Conv2D", "config": {"name": "conv_output", "trainable": true, "dtype": "float32", "filters": 2048, "kernel_size": {"class_name": "__tuple__", "items": [1, 1]}, "strides": {"class_name": "__tuple__", "items": [1, 1]}, "padding": "same", "data_format": "channels_last", "dilation_rate": {"class_name": "__tuple__", "items": [1, 1]}, "groups": 1, "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 600}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 601}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 602, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 4, "axes": {"-1": 512}}, "shared_object_id": 603}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 512]}}2 +  /root.layer_with_weights-18._main_path._layers.7"_tf_keras_layer* {"name": "batch_normalization_output", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "BatchNormalization", "config": {"name": "batch_normalization_output", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.9997, "epsilon": 0.0001, "center": true, "scale": false, "beta_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 604}, "gamma_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 605}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 606}, "moving_variance_initializer": {"class_name": "Ones", "config": {}, "shared_object_id": 607}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "shared_object_id": 608, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 4, "max_ndim": null, "min_ndim": null, "axes": {"3": 2048}}, "shared_object_id": 609}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8, 4, 2048]}}2 \ No newline at end of file diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/saved_model.pb b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/saved_model.pb new file mode 100644 index 00000000..07ce2663 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/saved_model.pb differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.data-00000-of-00001 b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.data-00000-of-00001 new file mode 100644 index 00000000..3bdd558c Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.data-00000-of-00001 differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.index b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.index new file mode 100644 index 00000000..490616b2 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/FlatHBNA/variables/variables.index differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback.wav b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback.wav new file mode 100644 index 00000000..2b19f580 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback.wav differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback_annotations.txt b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback_annotations.txt new file mode 100644 index 00000000..567fb21e --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Generic/humpback_whale_atlantic/SAMOSAS_EL1_humpback_annotations.txt @@ -0,0 +1,216 @@ +Selection Begin Time (s) End Time (s) High Freq (Hz) Low Freq (Hz) Prediction/Comments +1 3.8775 7.755 1000 50 0.99972177 +2 7.755 11.6325 1000 50 0.89199716 +3 11.6325 15.51 1000 50 0.64813995 +4 15.51 19.3875 1000 50 0.9713266 +5 19.3875 23.265 1000 50 0.8342721 +6 23.265 27.142500000000002 1000 50 0.99634343 +7 31.02 34.8975 1000 50 0.9933128 +8 34.8975 38.775 1000 50 0.5325079 +9 42.6525 46.53 1000 50 0.9727334 +10 46.53 50.4075 1000 50 0.7700455 +11 54.285 58.162499999999994 1000 50 0.93229276 +12 58.1625 62.04 1000 50 0.5089565 +13 62.04 65.9175 1000 50 0.53604007 +14 65.9175 69.795 1000 50 0.6665228 +15 69.795 73.6725 1000 50 0.8109671 +16 73.6725 77.55 1000 50 0.9998325 +17 77.55 81.4275 1000 50 0.6296093 +18 81.4275 85.30499999999999 1000 50 0.99747694 +19 93.06 96.9375 1000 50 0.99976456 +20 96.9375 100.815 1000 50 0.53218305 +21 104.6925 108.57 1000 50 0.98100567 +22 112.4475 116.325 1000 50 0.91926867 +23 124.08 127.9575 1000 50 0.9990459 +24 127.9575 131.835 1000 50 0.6861532 +25 135.7125 139.59 1000 50 0.5737106 +26 143.4675 147.345 1000 50 0.9537415 +27 147.345 151.2225 1000 50 0.5592457 +28 151.2225 155.1 1000 50 0.97674406 +29 155.1 158.9775 1000 50 0.55292696 +30 166.7325 170.60999999999999 1000 50 0.86637044 +31 170.61 174.4875 1000 50 0.8887141 +32 174.4875 178.365 1000 50 0.65914917 +33 182.2425 186.12 1000 50 0.78331536 +34 189.9975 193.875 1000 50 0.51390845 +35 193.875 197.7525 1000 50 0.8545577 +36 197.7525 201.63 1000 50 0.99297434 +37 209.385 213.2625 1000 50 0.91762686 +38 217.14 221.01749999999998 1000 50 0.9865262 +39 221.0175 224.895 1000 50 0.9802332 +40 224.895 228.7725 1000 50 0.9038371 +41 228.7725 232.65 1000 50 0.9299754 +42 232.65 236.5275 1000 50 0.99995977 +43 236.5275 240.405 1000 50 0.75877947 +44 240.405 244.2825 1000 50 0.8074757 +45 244.2825 248.16 1000 50 0.57404643 +46 248.16 252.0375 1000 50 0.5844841 +47 252.0375 255.915 1000 50 0.77901065 +48 267.5475 271.425 1000 50 0.9999262 +49 283.0575 286.935 1000 50 0.91496134 +50 286.935 290.8125 1000 50 0.6016941 +51 290.8125 294.69 1000 50 0.5288352 +52 294.69 298.5675 1000 50 0.7803428 +53 298.5675 302.445 1000 50 0.9457351 +54 302.445 306.3225 1000 50 0.8809803 +55 314.0775 317.955 1000 50 0.76862615 +56 317.955 321.8325 1000 50 0.9179431 +57 321.8325 325.71 1000 50 0.64783925 +58 325.71 329.5875 1000 50 0.63720423 +59 333.465 337.3425 1000 50 0.9080041 +60 337.3425 341.21999999999997 1000 50 0.636528 +61 341.22 345.0975 1000 50 0.8285956 +62 348.975 352.8525 1000 50 0.5541101 +63 360.6075 364.485 1000 50 0.9939563 +64 364.485 368.3625 1000 50 0.5179203 +65 368.3625 372.24 1000 50 0.998485 +66 372.24 376.1175 1000 50 0.7255482 +67 379.995 383.8725 1000 50 0.6432554 +68 387.75 391.6275 1000 50 0.85360736 +69 395.505 399.3825 1000 50 0.63949126 +70 403.26 407.1375 1000 50 0.9875199 +71 407.1375 411.015 1000 50 0.63171786 +72 411.015 414.8925 1000 50 0.51271856 +73 414.8925 418.77 1000 50 0.8624972 +74 418.77 422.6475 1000 50 0.9287566 +75 422.6475 426.525 1000 50 0.79819566 +76 442.035 445.9125 1000 50 0.9992767 +77 445.9125 449.79 1000 50 0.85704327 +78 449.79 453.6675 1000 50 0.606248 +79 465.3 469.1775 1000 50 0.6945031 +80 469.1775 473.055 1000 50 0.91730964 +81 476.9325 480.81 1000 50 0.8548426 +82 480.81 484.6875 1000 50 0.8609153 +83 484.6875 488.565 1000 50 0.53578407 +84 492.4425 496.32 1000 50 0.6815063 +85 504.075 507.9525 1000 50 0.5493686 +86 511.83 515.7075 1000 50 0.57242775 +87 515.7075 519.585 1000 50 0.802748 +88 519.585 523.4625000000001 1000 50 0.75667137 +89 527.34 531.2175000000001 1000 50 0.6283601 +90 535.095 538.9725000000001 1000 50 0.58465177 +91 538.9725 542.85 1000 50 0.7337384 +92 546.7275 550.605 1000 50 0.6839575 +93 562.2375 566.115 1000 50 0.6672469 +94 593.2575 597.1350000000001 1000 50 0.5644848 +95 604.89 608.7675 1000 50 0.78537405 +96 624.2775 628.1550000000001 1000 50 0.52095556 +97 632.0325 635.9100000000001 1000 50 0.86063546 +98 635.91 639.7875 1000 50 0.7444124 +99 643.665 647.5425 1000 50 0.59704643 +100 647.5425 651.4200000000001 1000 50 0.9363932 +101 666.93 670.8075 1000 50 0.78785974 +102 678.5625 682.44 1000 50 0.99932384 +103 690.195 694.0725000000001 1000 50 0.7276663 +104 694.0725 697.95 1000 50 0.6033868 +105 697.95 701.8275000000001 1000 50 0.7666113 +106 705.705 709.5825000000001 1000 50 0.9996606 +107 709.5825 713.46 1000 50 0.87555915 +108 717.3375 721.215 1000 50 0.99977213 +109 721.215 725.0925000000001 1000 50 0.6706097 +110 728.97 732.8475000000001 1000 50 0.99964714 +111 736.725 740.6025000000001 1000 50 0.9942172 +112 740.6025 744.48 1000 50 0.99729407 +113 744.48 748.3575000000001 1000 50 0.98387647 +114 748.3575 752.235 1000 50 0.97733885 +115 756.1125 759.99 1000 50 0.9908622 +116 763.8675 767.745 1000 50 0.9963475 +117 775.5 779.3775 1000 50 0.9301784 +118 779.3775 783.2550000000001 1000 50 0.8671598 +119 783.255 787.1325 1000 50 0.8163382 +120 787.1325 791.0100000000001 1000 50 0.67742085 +121 794.8875 798.7650000000001 1000 50 0.9999998 +122 798.765 802.6425 1000 50 0.9092158 +123 802.6425 806.5200000000001 1000 50 0.5064141 +124 806.52 810.3975 1000 50 0.9245316 +125 810.3975 814.2750000000001 1000 50 0.9987917 +126 814.275 818.1525 1000 50 0.98765403 +127 818.1525 822.0300000000001 1000 50 0.7059854 +128 822.03 825.9075 1000 50 0.9687309 +129 825.9075 829.7850000000001 1000 50 0.99980175 +130 829.785 833.6625 1000 50 0.9667981 +131 833.6625 837.5400000000001 1000 50 0.6459364 +132 837.54 841.4175 1000 50 0.99052876 +133 845.295 849.1725 1000 50 0.98032534 +134 849.1725 853.0500000000001 1000 50 0.9769905 +135 864.6825 868.5600000000001 1000 50 0.9989409 +136 868.56 872.4375 1000 50 0.65797764 +137 876.315 880.1925000000001 1000 50 0.8667758 +138 880.1925 884.07 1000 50 0.8123294 +139 884.07 887.9475000000001 1000 50 0.9994879 +140 887.9475 891.825 1000 50 0.6211881 +141 895.7025 899.58 1000 50 0.9882314 +142 899.58 903.4575000000001 1000 50 0.988451 +143 903.4575 907.335 1000 50 0.8560532 +144 907.335 911.2125000000001 1000 50 0.8371353 +145 911.2125 915.09 1000 50 0.9963776 +146 918.9675 922.845 1000 50 0.99993306 +147 930.6 934.4775000000001 1000 50 0.98831177 +148 938.355 942.2325000000001 1000 50 0.6520295 +149 957.7425 961.62 1000 50 0.6918473 +150 961.62 965.4975000000001 1000 50 0.74858236 +151 965.4975 969.375 1000 50 0.97066724 +152 973.2525 977.1300000000001 1000 50 0.6426704 +153 981.0075 984.8850000000001 1000 50 0.8150208 +154 984.885 988.7625 1000 50 0.5683353 +155 988.7625 992.6400000000001 1000 50 0.581548 +156 992.64 996.5175 1000 50 0.77473736 +157 1000.395 1004.2725 1000 50 0.950805 +158 1004.2725 1008.1500000000001 1000 50 0.53004926 +159 1012.0275 1015.9050000000001 1000 50 0.9998928 +160 1015.905 1019.7825 1000 50 0.90716743 +161 1023.66 1027.5375 1000 50 0.9305084 +162 1031.415 1035.2925 1000 50 0.77918565 +163 1039.17 1043.0475000000001 1000 50 0.9984765 +164 1043.0475 1046.925 1000 50 0.5665361 +165 1050.8025 1054.68 1000 50 0.999446 +166 1054.68 1058.5575000000001 1000 50 0.5452138 +167 1062.435 1066.3125 1000 50 0.99469894 +168 1066.3125 1070.19 1000 50 0.5405148 +169 1074.0675 1077.9450000000002 1000 50 0.5739461 +170 1077.945 1081.8225 1000 50 0.5058043 +171 1081.8225 1085.7 1000 50 0.883449 +172 1089.5775 1093.4550000000002 1000 50 0.9999634 +173 1093.455 1097.3325 1000 50 0.6814424 +174 1097.3325 1101.21 1000 50 0.904202 +175 1105.0875 1108.9650000000001 1000 50 0.55364376 +176 1120.5975 1124.4750000000001 1000 50 0.719839 +177 1136.1075 1139.9850000000001 1000 50 0.57873297 +178 1147.74 1151.6175 1000 50 0.7904256 +179 1151.6175 1155.4950000000001 1000 50 0.88318896 +180 1178.76 1182.6375 1000 50 0.88804716 +181 1186.515 1190.3925000000002 1000 50 0.6625152 +182 1198.1475 1202.025 1000 50 0.9660911 +183 1205.9025 1209.78 1000 50 0.91225654 +184 1209.78 1213.6575 1000 50 0.7151032 +185 1221.4125 1225.29 1000 50 0.9564372 +186 1244.6775 1248.555 1000 50 0.722955 +187 1248.555 1252.4325000000001 1000 50 0.7033242 +188 1252.4325 1256.31 1000 50 0.72211057 +189 1256.31 1260.1875 1000 50 0.6124199 +190 1260.1875 1264.065 1000 50 0.7847693 +191 1264.065 1267.9425 1000 50 0.6674457 +192 1267.9425 1271.8200000000002 1000 50 0.76507866 +193 1271.82 1275.6975 1000 50 0.8226003 +194 1279.575 1283.4525 1000 50 0.5882834 +195 1283.4525 1287.3300000000002 1000 50 0.94607145 +196 1287.33 1291.2075 1000 50 0.5691646 +197 1295.085 1298.9625 1000 50 0.9842685 +198 1298.9625 1302.8400000000001 1000 50 0.9851393 +199 1310.595 1314.4725 1000 50 0.5915515 +200 1314.4725 1318.3500000000001 1000 50 0.56246245 +201 1318.35 1322.2275 1000 50 0.996443 +202 1326.105 1329.9825 1000 50 0.8811614 +203 1329.9825 1333.8600000000001 1000 50 0.9826125 +204 1337.7375 1341.615 1000 50 0.9936857 +205 1345.4925 1349.3700000000001 1000 50 0.997924 +206 1357.125 1361.0025 1000 50 0.88610935 +207 1364.88 1368.7575000000002 1000 50 0.5882369 +208 1388.145 1392.0225 1000 50 0.6528943 +209 1395.9 1399.7775000000001 1000 50 0.7434355 +210 1407.5325 1411.41 1000 50 0.53381926 +211 1438.5525 1442.43 1000 50 0.7328098 +212 1446.3075 1450.185 1000 50 0.65970933 +213 1465.695 1469.5725 1000 50 0.539152 +214 1485.0825 1488.96 1000 50 0.6864708 +215 1492.8375 1496.7150000000001 1000 50 0.6845237 diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/legacy_format/right_whale_DL_settings.pdtf b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/legacy_format/right_whale_DL_settings.pdtf new file mode 100644 index 00000000..d8858a7f --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/legacy_format/right_whale_DL_settings.pdtf @@ -0,0 +1 @@ +{"model_info":"{\"output_shape\":[-1,2],\"input_shape\":[-1,40,40,1]}","class_info":"{\"name_class\":\"Noise,Right Whale\",\"num_class\":2}","seg_size":"{\"size_ms\":2000}","load_audio":"{\"sr\":2000}","spectrogram":"{\"fft\":256,\"hop\":100}","freq_compression":"{\"bins\":40,\"fmin\":47,\"fmax\":357}","norm_row_sum":"{}"} \ No newline at end of file diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/saved_model.pb b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/saved_model.pb new file mode 100644 index 00000000..9d51248a Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/saved_model.pb differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.data-00000-of-00001 b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.data-00000-of-00001 new file mode 100644 index 00000000..cefbba88 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.data-00000-of-00001 differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.index b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.index new file mode 100644 index 00000000..4e56624e Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/model_lenet_dropout_input_conv_all/variables/variables.index differ diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_DL_settings.pdtf b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_DL_settings.pdtf new file mode 100644 index 00000000..6549ec42 --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_DL_settings.pdtf @@ -0,0 +1,50 @@ +{ + "framework_info": {"framework": "Bespoke"}, + "model_info": { + "output_shape": [ + -1, + 2 + ], + "input_shape": [ + -1, + 40, + 40, + 1 + ] + }, + "class_info": { + "name_class": [ + "Noise", + "Right Whale" + ], + "num_class": 2 + }, + "transforms": [ + { + "name": "norm_row_sum", + "params": {} + }, + { + "name": "spectrogram", + "params": { + "fft": 256, + "hop": 100 + } + }, + { + "name": "load_audio", + "params": {"sr": 2000} + }, + { + "name": "freq_compression", + "params": { + "bins": 40, + "fmin": 47, + "fmax": 357 + } + } + ], + "description": "Metadata for acoustic deep learning", + "version_info": {"version": 1}, + "seg_size": {"size_ms": 2000} +} \ No newline at end of file diff --git a/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_example.wav b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_example.wav new file mode 100644 index 00000000..4d54da75 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Generic/right_whale/right_whale_example.wav differ diff --git a/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/detections_no_overlap.csv b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/detections_no_overlap.csv new file mode 100644 index 00000000..be0cc920 --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/detections_no_overlap.csv @@ -0,0 +1,20 @@ +filename,start,duration,score +jasco_reduced.wav,0.0,5.0176,0.1565524 +jasco_reduced.wav,5.0,5.0176,0.99999917 +jasco_reduced.wav,10.0,5.0176,0.99999917 +jasco_reduced.wav,15.0,5.0176,0.97594243 +jasco_reduced.wav,20.0,5.0176,0.8802458 +jasco_reduced.wav,25.0,5.0176,0.9999999 +jasco_reduced.wav,30.0,5.0176,0.9999932 +jasco_reduced.wav,35.0,5.0176,0.9998863 +jasco_reduced.wav,40.0,5.0176,0.99998367 +jasco_reduced.wav,45.0,5.0176,0.21531366 +jasco_reduced.wav,50.0,5.0176,0.9999987 +jasco_reduced.wav,55.0,5.0176,1.0 +jasco_reduced.wav,60.0,5.0176,0.9999989 +jasco_reduced.wav,65.0,5.0176,0.9999993 +jasco_reduced.wav,70.0,5.0176,0.99999845 +jasco_reduced.wav,75.0,5.0176,1.0 +jasco_reduced.wav,80.0,5.0176,0.20126265 +jasco_reduced.wav,85.0,5.0176,0.9797412 +jasco_reduced.wav,90.0,5.0176,1.0 diff --git a/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/hallo-kw-det_v1.ktpb b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/hallo-kw-det_v1.ktpb new file mode 100644 index 00000000..a184ba54 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/hallo-kw-det_v1.ktpb differ diff --git a/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/jasco_reduced.wav b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/jasco_reduced.wav new file mode 100644 index 00000000..a7c4b5c4 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Ketos/hallo-kw-det_v1/jasco_reduced.wav differ diff --git a/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/20190527_190000.wav b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/20190527_190000.wav new file mode 100644 index 00000000..b4770238 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/20190527_190000.wav differ diff --git a/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/ReadMe.md.txt b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/ReadMe.md.txt new file mode 100644 index 00000000..4a2f2189 --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/ReadMe.md.txt @@ -0,0 +1,5 @@ +# Blue whale Koogu models. + +There are two versions of blue whale models BmD_24_tf_model and BmD_23_tf_model in standard Tensorflow saved_.pb format. + +There is also a blue_whale_24.kgu model which is the standard PAMGaurd compatible format for koogu models. This contains settings to set up the model automtically. \ No newline at end of file diff --git a/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/blue_whale_24.kgu b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/blue_whale_24.kgu new file mode 100644 index 00000000..1265875f Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/blue_whale_24.kgu differ diff --git a/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.csv b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.csv new file mode 100644 index 00000000..cfb4724e --- /dev/null +++ b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.csv @@ -0,0 +1,1798 @@ +0.000000000000000000e+00,5.193827673792839050e-02,9.480617642402648926e-01 +5.000000000000000000e+02,3.835381241515278816e-03,9.961646795272827148e-01 +1.000000000000000000e+03,2.028739452362060547e-02,9.797126054763793945e-01 +1.500000000000000000e+03,1.735078287310898304e-03,9.982649683952331543e-01 +2.000000000000000000e+03,1.776782050728797913e-02,9.822322130203247070e-01 +2.500000000000000000e+03,5.662455223500728607e-03,9.943374991416931152e-01 +3.000000000000000000e+03,5.498182028532028198e-03,9.945018291473388672e-01 +3.500000000000000000e+03,8.968043923377990723e-01,1.031956449151039124e-01 +4.000000000000000000e+03,1.809033192694187164e-02,9.819096326828002930e-01 +4.500000000000000000e+03,5.350030260160565376e-04,9.994650483131408691e-01 +5.000000000000000000e+03,1.721951557556167245e-04,9.998277425765991211e-01 +5.500000000000000000e+03,1.795023563317954540e-03,9.982049465179443359e-01 +6.000000000000000000e+03,7.606656436109915376e-05,9.999239444732666016e-01 +6.500000000000000000e+03,6.214079330675303936e-04,9.993785619735717773e-01 +7.000000000000000000e+03,3.204601671313866973e-05,9.999679327011108398e-01 +7.500000000000000000e+03,9.551703114993870258e-04,9.990448355674743652e-01 +8.000000000000000000e+03,1.878460519947111607e-03,9.981215596199035645e-01 +8.500000000000000000e+03,4.259951412677764893e-03,9.957400560379028320e-01 +9.000000000000000000e+03,4.332339740358293056e-04,9.995667338371276855e-01 +9.500000000000000000e+03,9.989676764234900475e-04,9.990010857582092285e-01 +1.000000000000000000e+04,9.432184015167877078e-05,9.999057054519653320e-01 +1.050000000000000000e+04,5.027814768254756927e-04,9.994971752166748047e-01 +1.100000000000000000e+04,7.552343886345624924e-04,9.992448091506958008e-01 +1.150000000000000000e+04,6.083136796951293945e-02,9.391686320304870605e-01 +1.200000000000000000e+04,1.754007098497822881e-04,9.998245835304260254e-01 +1.250000000000000000e+04,5.293995491228997707e-04,9.994706511497497559e-01 +1.300000000000000000e+04,6.138727664947509766e-01,3.861272931098937988e-01 +1.350000000000000000e+04,6.617751903831958771e-03,9.933822154998779297e-01 +1.400000000000000000e+04,5.830759182572364807e-04,9.994169473648071289e-01 +1.450000000000000000e+04,3.248939465265721083e-04,9.996751546859741211e-01 +1.500000000000000000e+04,5.485379602760076523e-03,9.945147037506103516e-01 +1.550000000000000000e+04,2.009915333474054933e-04,9.997990727424621582e-01 +1.600000000000000000e+04,1.963311806321144104e-03,9.980366826057434082e-01 +1.650000000000000000e+04,3.677869448438286781e-03,9.963221549987792969e-01 +1.700000000000000000e+04,2.805884869303554296e-04,9.997194409370422363e-01 +1.750000000000000000e+04,3.867996274493634701e-04,9.996132254600524902e-01 +1.800000000000000000e+04,1.938982307910919189e-02,9.806101322174072266e-01 +1.850000000000000000e+04,2.660461366176605225e-01,7.339538335800170898e-01 +1.900000000000000000e+04,4.061509571329224855e-06,9.999959468841552734e-01 +1.950000000000000000e+04,3.832910151686519384e-04,9.996166229248046875e-01 +2.000000000000000000e+04,9.649378247559070587e-03,9.903505444526672363e-01 +2.050000000000000000e+04,3.297805488109588623e-01,6.702194809913635254e-01 +2.100000000000000000e+04,3.010334670543670654e-01,6.989665031433105469e-01 +2.150000000000000000e+04,3.390581332496367395e-05,9.999661445617675781e-01 +2.200000000000000000e+04,2.993459347635507584e-04,9.997006654739379883e-01 +2.250000000000000000e+04,1.635091751813888550e-02,9.836491346359252930e-01 +2.300000000000000000e+04,1.991654717130586505e-04,9.998008608818054199e-01 +2.350000000000000000e+04,4.963197279721498489e-04,9.995037317276000977e-01 +2.400000000000000000e+04,1.911420113174244761e-04,9.998088479042053223e-01 +2.450000000000000000e+04,9.569693065714091063e-05,9.999042749404907227e-01 +2.500000000000000000e+04,2.452740445733070374e-04,9.997547268867492676e-01 +2.550000000000000000e+04,6.449790816986933351e-05,9.999355077743530273e-01 +2.600000000000000000e+04,9.990211725234985352e-01,9.787887101992964745e-04 +2.650000000000000000e+04,9.999612569808959961e-01,3.880098302033729851e-05 +2.700000000000000000e+04,1.885914476588368416e-03,9.981141090393066406e-01 +2.750000000000000000e+04,9.844623804092407227e-01,1.553760748356580734e-02 +2.800000000000000000e+04,9.978924393653869629e-01,2.107557607814669609e-03 +2.850000000000000000e+04,2.841729903593659401e-03,9.971582889556884766e-01 +2.900000000000000000e+04,1.052950844168663025e-01,8.947048783302307129e-01 +2.950000000000000000e+04,3.529099922161549330e-04,9.996471405029296875e-01 +3.000000000000000000e+04,4.631376214092597365e-06,9.999953508377075195e-01 +3.050000000000000000e+04,3.284719696239335462e-07,9.999996423721313477e-01 +3.100000000000000000e+04,2.709065447561442852e-04,9.997290968894958496e-01 +3.150000000000000000e+04,1.561498851515352726e-03,9.984385371208190918e-01 +3.200000000000000000e+04,2.453411980241071433e-05,9.999754428863525391e-01 +3.250000000000000000e+04,8.419973892159759998e-04,9.991580247879028320e-01 +3.300000000000000000e+04,2.606973566798842512e-06,9.999973773956298828e-01 +3.350000000000000000e+04,2.828014548867940903e-04,9.997171759605407715e-01 +3.400000000000000000e+04,2.027954178629443049e-05,9.999797344207763672e-01 +3.450000000000000000e+04,6.792444619350135326e-04,9.993207454681396484e-01 +3.500000000000000000e+04,5.260556936264038086e-04,9.994739890098571777e-01 +3.550000000000000000e+04,7.813695265213027596e-05,9.999217987060546875e-01 +3.600000000000000000e+04,7.514603785239160061e-04,9.992485642433166504e-01 +3.650000000000000000e+04,1.099482877179980278e-03,9.989005327224731445e-01 +3.700000000000000000e+04,4.218474496155977249e-04,9.995781779289245605e-01 +3.750000000000000000e+04,3.285778802819550037e-05,9.999670982360839844e-01 +3.800000000000000000e+04,1.890823990106582642e-02,9.810917377471923828e-01 +3.850000000000000000e+04,6.573823094367980957e-02,9.342617988586425781e-01 +3.900000000000000000e+04,7.048679253784939647e-05,9.999295473098754883e-01 +3.950000000000000000e+04,9.562879567965865135e-04,9.990436434745788574e-01 +4.000000000000000000e+04,8.081613600552373100e-08,9.999998807907104492e-01 +4.050000000000000000e+04,4.859699693042784929e-04,9.995139837265014648e-01 +4.100000000000000000e+04,3.359363472554832697e-04,9.996640682220458984e-01 +4.150000000000000000e+04,6.333794444799423218e-04,9.993665814399719238e-01 +4.200000000000000000e+04,5.036517541157081723e-05,9.999495744705200195e-01 +4.250000000000000000e+04,6.810786435380578041e-04,9.993189573287963867e-01 +4.300000000000000000e+04,3.196616307832300663e-04,9.996803998947143555e-01 +4.350000000000000000e+04,1.031387597322463989e-03,9.989686012268066406e-01 +4.400000000000000000e+04,3.536416334100067616e-04,9.996463060379028320e-01 +4.450000000000000000e+04,2.714316069614142179e-04,9.997285008430480957e-01 +4.500000000000000000e+04,5.037084338255226612e-05,9.999495744705200195e-01 +4.550000000000000000e+04,2.042202831944450736e-04,9.997958540916442871e-01 +4.600000000000000000e+04,2.222418988822028041e-04,9.997777342796325684e-01 +4.650000000000000000e+04,2.673578856047242880e-04,9.997326731681823730e-01 +4.700000000000000000e+04,4.226464370731264353e-04,9.995773434638977051e-01 +4.750000000000000000e+04,7.799820159561932087e-04,9.992200136184692383e-01 +4.800000000000000000e+04,9.251679875887930393e-04,9.990748167037963867e-01 +4.850000000000000000e+04,7.181509281508624554e-04,9.992818236351013184e-01 +4.900000000000000000e+04,1.184290498495101929e-01,8.815709948539733887e-01 +4.950000000000000000e+04,1.089882352971471846e-04,9.998910427093505859e-01 +5.000000000000000000e+04,9.986404776573181152e-01,1.359469373710453510e-03 +5.050000000000000000e+04,9.999320507049560547e-01,6.800162373110651970e-05 +5.100000000000000000e+04,1.301611587405204773e-02,9.869838953018188477e-01 +5.150000000000000000e+04,1.149401888251304626e-01,8.850598335266113281e-01 +5.200000000000000000e+04,2.088384935632348061e-04,9.997912049293518066e-01 +5.250000000000000000e+04,6.615508027607575059e-05,9.999338388442993164e-01 +5.300000000000000000e+04,2.405644190730527043e-04,9.997594952583312988e-01 +5.350000000000000000e+04,3.161463420838117599e-03,9.968385696411132812e-01 +5.400000000000000000e+04,9.968839287757873535e-01,3.116026055067777634e-03 +5.450000000000000000e+04,9.845912456512451172e-01,1.540880184620618820e-02 +5.500000000000000000e+04,2.414918038994073868e-03,9.975850582122802734e-01 +5.550000000000000000e+04,8.289563469588756561e-03,9.917104840278625488e-01 +5.600000000000000000e+04,3.433938603848218918e-03,9.965661168098449707e-01 +5.650000000000000000e+04,9.210689924657344818e-03,9.907893538475036621e-01 +5.700000000000000000e+04,1.703783753328025341e-03,9.982962012290954590e-01 +5.750000000000000000e+04,4.067900008521974087e-04,9.995931982994079590e-01 +5.800000000000000000e+04,1.280780124943703413e-04,9.998718500137329102e-01 +5.850000000000000000e+04,9.412337094545364380e-02,9.058766365051269531e-01 +5.900000000000000000e+04,7.471109926700592041e-02,9.252889156341552734e-01 +5.950000000000000000e+04,2.750398358330130577e-03,9.972495436668395996e-01 +6.000000000000000000e+04,2.249133735895156860e-01,7.750865817070007324e-01 +6.050000000000000000e+04,1.452418160624802113e-03,9.985476136207580566e-01 +6.100000000000000000e+04,1.537399512017145753e-04,9.998462200164794922e-01 +6.150000000000000000e+04,6.937792204553261399e-05,9.999306201934814453e-01 +6.200000000000000000e+04,3.076585708186030388e-04,9.996923208236694336e-01 +6.250000000000000000e+04,9.970730543136596680e-01,2.926968503743410110e-03 +6.300000000000000000e+04,9.999982118606567383e-01,1.808172328310320154e-06 +6.350000000000000000e+04,2.396436631679534912e-01,7.603563666343688965e-01 +6.400000000000000000e+04,1.381861716508865356e-01,8.618138432502746582e-01 +6.450000000000000000e+04,2.051277086138725281e-02,9.794872403144836426e-01 +6.500000000000000000e+04,2.155594294890761375e-03,9.978443384170532227e-01 +6.550000000000000000e+04,5.884863808751106262e-02,9.411513209342956543e-01 +6.600000000000000000e+04,3.542453050613403320e-03,9.964575171470642090e-01 +6.650000000000000000e+04,7.397509762085974216e-04,9.992602467536926270e-01 +6.700000000000000000e+04,7.889118278399109840e-04,9.992110729217529297e-01 +6.750000000000000000e+04,1.459216000512242317e-03,9.985407590866088867e-01 +6.800000000000000000e+04,8.239042945206165314e-03,9.917609691619873047e-01 +6.850000000000000000e+04,7.470087613910436630e-03,9.925299286842346191e-01 +6.900000000000000000e+04,6.500784365925937891e-05,9.999350309371948242e-01 +6.950000000000000000e+04,1.338618574663996696e-03,9.986613988876342773e-01 +7.000000000000000000e+04,1.320236711762845516e-03,9.986796975135803223e-01 +7.050000000000000000e+04,4.135838826186954975e-04,9.995864033699035645e-01 +7.100000000000000000e+04,1.311136729782447219e-04,9.998688697814941406e-01 +7.150000000000000000e+04,9.863986968994140625e-01,1.360123232007026672e-02 +7.200000000000000000e+04,9.972949624061584473e-01,2.704979619011282921e-03 +7.250000000000000000e+04,1.031971350312232971e-01,8.968029022216796875e-01 +7.300000000000000000e+04,9.484044276177883148e-04,9.990516304969787598e-01 +7.350000000000000000e+04,5.934836864471435547e-01,4.065163731575012207e-01 +7.400000000000000000e+04,9.942224621772766113e-01,5.777487065643072128e-03 +7.450000000000000000e+04,9.875575304031372070e-01,1.244247425347566605e-02 +7.500000000000000000e+04,5.972678661346435547e-01,4.027321040630340576e-01 +7.550000000000000000e+04,3.380014968570321798e-04,9.996620416641235352e-01 +7.600000000000000000e+04,3.810041380347684026e-05,9.999618530273437500e-01 +7.650000000000000000e+04,2.966856991406530142e-04,9.997032284736633301e-01 +7.700000000000000000e+04,1.366305979900062084e-03,9.986336827278137207e-01 +7.750000000000000000e+04,2.198271639645099640e-02,9.780173301696777344e-01 +7.800000000000000000e+04,9.340520203113555908e-02,9.065947532653808594e-01 +7.850000000000000000e+04,7.979068323038518429e-04,9.992020726203918457e-01 +7.900000000000000000e+04,1.012332795653492212e-04,9.998987913131713867e-01 +7.950000000000000000e+04,8.471526205539703369e-03,9.915285110473632812e-01 +8.000000000000000000e+04,9.936608374118804932e-02,9.006339311599731445e-01 +8.050000000000000000e+04,1.330330851487815380e-03,9.986697435379028320e-01 +8.100000000000000000e+04,2.402597665786743164e-02,9.759740233421325684e-01 +8.150000000000000000e+04,2.283616231579799205e-05,9.999771118164062500e-01 +8.200000000000000000e+04,3.158355597406625748e-03,9.968416690826416016e-01 +8.250000000000000000e+04,1.779725571395829320e-04,9.998220801353454590e-01 +8.300000000000000000e+04,1.304299512412399054e-04,9.998695850372314453e-01 +8.350000000000000000e+04,9.861518740653991699e-01,1.384810078889131546e-02 +8.400000000000000000e+04,9.999452829360961914e-01,5.475183570524677634e-05 +8.450000000000000000e+04,6.602609753608703613e-01,3.397389650344848633e-01 +8.500000000000000000e+04,9.999945163726806641e-01,5.465788035508012399e-06 +8.550000000000000000e+04,9.998707771301269531e-01,1.291606022277846932e-04 +8.600000000000000000e+04,2.898480743169784546e-03,9.971014857292175293e-01 +8.650000000000000000e+04,7.230988703668117523e-04,9.992769360542297363e-01 +8.700000000000000000e+04,9.615758657455444336e-01,3.842410072684288025e-02 +8.750000000000000000e+04,9.625777602195739746e-01,3.742222115397453308e-02 +8.800000000000000000e+04,1.214352156966924667e-02,9.878565073013305664e-01 +8.850000000000000000e+04,9.999136924743652344e-01,8.633434481453150511e-05 +8.900000000000000000e+04,9.999949932098388672e-01,5.023072844778653234e-06 +8.950000000000000000e+04,9.762700200080871582e-01,2.373000234365463257e-02 +9.000000000000000000e+04,2.129490603692829609e-04,9.997870326042175293e-01 +9.050000000000000000e+04,9.880379438400268555e-01,1.196211390197277069e-02 +9.100000000000000000e+04,6.643359065055847168e-01,3.356640636920928955e-01 +9.150000000000000000e+04,9.704185128211975098e-01,2.958154492080211639e-02 +9.200000000000000000e+04,9.951884746551513672e-01,4.811542574316263199e-03 +9.250000000000000000e+04,9.938104748725891113e-01,6.189572159200906754e-03 +9.300000000000000000e+04,9.990001320838928223e-01,9.999012108892202377e-04 +9.350000000000000000e+04,1.501314062625169754e-02,9.849868416786193848e-01 +9.400000000000000000e+04,1.224855805048719049e-04,9.998774528503417969e-01 +9.450000000000000000e+04,1.979751395992934704e-04,9.998020529747009277e-01 +9.500000000000000000e+04,9.340546966996043921e-05,9.999065399169921875e-01 +9.550000000000000000e+04,9.894397854804992676e-01,1.056020986288785934e-02 +9.600000000000000000e+04,9.999964237213134766e-01,3.629593720688717440e-06 +9.650000000000000000e+04,9.999953508377075195e-01,4.650708433473482728e-06 +9.700000000000000000e+04,9.916265010833740234e-01,8.373457938432693481e-03 +9.750000000000000000e+04,2.535528270527720451e-03,9.974644184112548828e-01 +9.800000000000000000e+04,3.058022446930408478e-02,9.694197177886962891e-01 +9.850000000000000000e+04,1.048963982611894608e-02,9.895104169845581055e-01 +9.900000000000000000e+04,4.951807204633951187e-03,9.950482249259948730e-01 +9.950000000000000000e+04,7.083467789925634861e-04,9.992916584014892578e-01 +1.000000000000000000e+05,6.073914482840336859e-05,9.999392032623291016e-01 +1.005000000000000000e+05,8.982514739036560059e-01,1.017484888434410095e-01 +1.010000000000000000e+05,8.054814934730529785e-01,1.945185363292694092e-01 +1.015000000000000000e+05,5.675726104527711868e-03,9.943243265151977539e-01 +1.020000000000000000e+05,9.999424219131469727e-01,5.758121551480144262e-05 +1.025000000000000000e+05,9.999991655349731445e-01,7.830465733604796696e-07 +1.030000000000000000e+05,9.979948997497558594e-01,2.005131216719746590e-03 +1.035000000000000000e+05,9.686871431767940521e-03,9.903131723403930664e-01 +1.040000000000000000e+05,1.580704152584075928e-01,8.419295549392700195e-01 +1.045000000000000000e+05,9.999994039535522461e-01,5.958082738288794644e-07 +1.050000000000000000e+05,1.000000000000000000e+00,9.250480381695069809e-09 +1.055000000000000000e+05,9.973427653312683105e-01,2.657212549820542336e-03 +1.060000000000000000e+05,1.468468544771894813e-04,9.998531341552734375e-01 +1.065000000000000000e+05,4.018658946733921766e-04,9.995982050895690918e-01 +1.070000000000000000e+05,8.500309922965243459e-05,9.999150037765502930e-01 +1.075000000000000000e+05,1.852979912655428052e-04,9.998146891593933105e-01 +1.080000000000000000e+05,1.201774575747549534e-03,9.987982511520385742e-01 +1.085000000000000000e+05,1.413681544363498688e-02,9.858631491661071777e-01 +1.090000000000000000e+05,1.953510101884603500e-03,9.980465173721313477e-01 +1.095000000000000000e+05,9.998326301574707031e-01,1.673970109550282359e-04 +1.100000000000000000e+05,1.000000000000000000e+00,1.241484581804286336e-09 +1.105000000000000000e+05,9.965049028396606445e-01,3.495117882266640663e-03 +1.110000000000000000e+05,9.999991655349731445e-01,7.955983960528101306e-07 +1.115000000000000000e+05,9.999996423721313477e-01,3.206867233984667109e-07 +1.120000000000000000e+05,9.879641532897949219e-01,1.203585416078567505e-02 +1.125000000000000000e+05,4.036966129206120968e-04,9.995962977409362793e-01 +1.130000000000000000e+05,8.572885417379438877e-04,9.991426467895507812e-01 +1.135000000000000000e+05,2.131306100636720657e-03,9.978686571121215820e-01 +1.140000000000000000e+05,7.373112021014094353e-04,9.992626309394836426e-01 +1.145000000000000000e+05,6.111404509283602238e-04,9.993888139724731445e-01 +1.150000000000000000e+05,2.117867320775985718e-01,7.882131934165954590e-01 +1.155000000000000000e+05,3.119233436882495880e-02,9.688076376914978027e-01 +1.160000000000000000e+05,9.998724460601806641e-01,1.274945971090346575e-04 +1.165000000000000000e+05,9.995058774948120117e-01,4.941578954458236694e-04 +1.170000000000000000e+05,3.063041018322110176e-03,9.969369173049926758e-01 +1.175000000000000000e+05,1.751106174197047949e-04,9.998249411582946777e-01 +1.180000000000000000e+05,1.117775216698646545e-03,9.988822340965270996e-01 +1.185000000000000000e+05,5.241601229499792680e-06,9.999947547912597656e-01 +1.190000000000000000e+05,8.532915671821683645e-06,9.999914169311523438e-01 +1.195000000000000000e+05,1.845408842200413346e-04,9.998155236244201660e-01 +1.200000000000000000e+05,5.358408088795840740e-04,9.994642138481140137e-01 +1.205000000000000000e+05,3.930488973855972290e-02,9.606950879096984863e-01 +1.210000000000000000e+05,8.027505129575729370e-02,9.197249412536621094e-01 +1.215000000000000000e+05,3.383037168532609940e-03,9.966170191764831543e-01 +1.220000000000000000e+05,9.999978542327880859e-01,2.086825134028913453e-06 +1.225000000000000000e+05,1.000000000000000000e+00,8.594932404681898674e-11 +1.230000000000000000e+05,9.999799728393554688e-01,2.005163878493476659e-05 +1.235000000000000000e+05,9.820980429649353027e-01,1.790202222764492035e-02 +1.240000000000000000e+05,9.196645114570856094e-04,9.990803003311157227e-01 +1.245000000000000000e+05,2.446934922772925347e-05,9.999755620956420898e-01 +1.250000000000000000e+05,9.923347830772399902e-01,7.665141019970178604e-03 +1.255000000000000000e+05,9.999871253967285156e-01,1.287792565562995151e-05 +1.260000000000000000e+05,3.473562002182006836e-02,9.652643799781799316e-01 +1.265000000000000000e+05,9.876000881195068359e-01,1.239996775984764099e-02 +1.270000000000000000e+05,9.999901056289672852e-01,9.893161404761485755e-06 +1.275000000000000000e+05,9.996494054794311523e-01,3.506100329104810953e-04 +1.280000000000000000e+05,9.974570870399475098e-01,2.542970469221472740e-03 +1.285000000000000000e+05,9.954082369804382324e-01,4.591718781739473343e-03 +1.290000000000000000e+05,4.057663900312036276e-04,9.995942711830139160e-01 +1.295000000000000000e+05,9.949475061148405075e-04,9.990050196647644043e-01 +1.300000000000000000e+05,2.994159162044525146e-01,7.005841135978698730e-01 +1.305000000000000000e+05,9.999808073043823242e-01,1.919202986755408347e-05 +1.310000000000000000e+05,9.999998807907104492e-01,1.492692547344631748e-07 +1.315000000000000000e+05,9.890239238739013672e-01,1.097614597529172897e-02 +1.320000000000000000e+05,9.999866485595703125e-01,1.331891871814150363e-05 +1.325000000000000000e+05,9.999998807907104492e-01,1.498889190543195582e-07 +1.330000000000000000e+05,9.984807372093200684e-01,1.519303652457892895e-03 +1.335000000000000000e+05,2.036523222923278809e-01,7.963476777076721191e-01 +1.340000000000000000e+05,1.449721679091453552e-02,9.855028390884399414e-01 +1.345000000000000000e+05,1.157810911536216736e-01,8.842189311981201172e-01 +1.350000000000000000e+05,9.999947547912597656e-01,5.299362783262040466e-06 +1.355000000000000000e+05,1.000000000000000000e+00,7.022221693020469502e-11 +1.360000000000000000e+05,9.999879598617553711e-01,1.202046496473485604e-05 +1.365000000000000000e+05,9.816764593124389648e-01,1.832356117665767670e-02 +1.370000000000000000e+05,2.219074964523315430e-02,9.778092503547668457e-01 +1.375000000000000000e+05,6.334364414215087891e-02,9.366563558578491211e-01 +1.380000000000000000e+05,5.728363757953047752e-04,9.994271993637084961e-01 +1.385000000000000000e+05,2.406235318630933762e-03,9.975937008857727051e-01 +1.390000000000000000e+05,7.280509918928146362e-02,9.271949529647827148e-01 +1.395000000000000000e+05,9.999659061431884766e-01,3.408603879506699741e-05 +1.400000000000000000e+05,9.999347925186157227e-01,6.518841837532818317e-05 +1.405000000000000000e+05,9.384301304817199707e-01,6.156991794705390930e-02 +1.410000000000000000e+05,8.653615950606763363e-04,9.991347193717956543e-01 +1.415000000000000000e+05,9.994366765022277832e-01,5.632754764519631863e-04 +1.420000000000000000e+05,9.995546936988830566e-01,4.453393921721726656e-04 +1.425000000000000000e+05,8.345201015472412109e-01,1.654798537492752075e-01 +1.430000000000000000e+05,1.351582631468772888e-02,9.864842295646667480e-01 +1.435000000000000000e+05,9.720509842736646533e-05,9.999028444290161133e-01 +1.440000000000000000e+05,9.965374469757080078e-01,3.462531603872776031e-03 +1.445000000000000000e+05,9.999995231628417969e-01,4.860782496507454198e-07 +1.450000000000000000e+05,9.999982118606567383e-01,1.833246756177686621e-06 +1.455000000000000000e+05,9.999266862869262695e-01,7.330256630666553974e-05 +1.460000000000000000e+05,6.029192209243774414e-01,3.970807790756225586e-01 +1.465000000000000000e+05,1.419564709067344666e-03,9.985804557800292969e-01 +1.470000000000000000e+05,8.343333378434181213e-03,9.916566610336303711e-01 +1.475000000000000000e+05,2.198146581649780273e-01,7.801853418350219727e-01 +1.480000000000000000e+05,8.691595867276191711e-03,9.913083910942077637e-01 +1.485000000000000000e+05,2.239620126783847809e-02,9.776037931442260742e-01 +1.490000000000000000e+05,2.113621485477779061e-05,9.999788999557495117e-01 +1.495000000000000000e+05,1.857085619121789932e-03,9.981429576873779297e-01 +1.500000000000000000e+05,3.355794469825923443e-04,9.996644258499145508e-01 +1.505000000000000000e+05,4.946722765453159809e-04,9.995052814483642578e-01 +1.510000000000000000e+05,9.997287392616271973e-01,2.713002322707325220e-04 +1.515000000000000000e+05,9.999991655349731445e-01,8.088859999588748906e-07 +1.520000000000000000e+05,9.853377342224121094e-01,1.466225087642669678e-02 +1.525000000000000000e+05,9.998893737792968750e-01,1.106664567487314343e-04 +1.530000000000000000e+05,5.217574238777160645e-01,4.782425463199615479e-01 +1.535000000000000000e+05,1.736067831516265869e-01,8.263932466506958008e-01 +1.540000000000000000e+05,7.063688244670629501e-03,9.929363131523132324e-01 +1.545000000000000000e+05,2.905501285567879677e-03,9.970945119857788086e-01 +1.550000000000000000e+05,9.609861969947814941e-01,3.901381045579910278e-02 +1.555000000000000000e+05,9.998676776885986328e-01,1.322942844126373529e-04 +1.560000000000000000e+05,9.985877275466918945e-01,1.412322279065847397e-03 +1.565000000000000000e+05,9.999996423721313477e-01,3.039268676729989238e-07 +1.570000000000000000e+05,9.999990463256835938e-01,9.874921715891105123e-07 +1.575000000000000000e+05,9.997704625129699707e-01,2.294856967637315392e-04 +1.580000000000000000e+05,9.999911785125732422e-01,8.843859177432022989e-06 +1.585000000000000000e+05,9.979946613311767578e-01,2.005361020565032959e-03 +1.590000000000000000e+05,9.810144901275634766e-01,1.898550428450107574e-02 +1.595000000000000000e+05,3.652338054962456226e-04,9.996347427368164062e-01 +1.600000000000000000e+05,1.101277419365942478e-03,9.988987445831298828e-01 +1.605000000000000000e+05,2.942176361102610826e-04,9.997057318687438965e-01 +1.610000000000000000e+05,1.867295950651168823e-01,8.132704496383666992e-01 +1.615000000000000000e+05,1.997096842387691140e-05,9.999799728393554688e-01 +1.620000000000000000e+05,4.347091889940202236e-04,9.995653033256530762e-01 +1.625000000000000000e+05,4.258494591340422630e-04,9.995741248130798340e-01 +1.630000000000000000e+05,5.786507725715637207e-01,4.213491976261138916e-01 +1.635000000000000000e+05,9.999953508377075195e-01,4.694691597251221538e-06 +1.640000000000000000e+05,9.999998807907104492e-01,6.493858251133133308e-08 +1.645000000000000000e+05,9.994238615036010742e-01,5.761112552136182785e-04 +1.650000000000000000e+05,6.174861788749694824e-01,3.825138509273529053e-01 +1.655000000000000000e+05,1.351442537270486355e-03,9.986485838890075684e-01 +1.660000000000000000e+05,2.979053242597728968e-04,9.997020363807678223e-01 +1.665000000000000000e+05,3.100437223911285400e-01,6.899562478065490723e-01 +1.670000000000000000e+05,8.763132095336914062e-01,1.236867904663085938e-01 +1.675000000000000000e+05,1.035575496644014493e-05,9.999896287918090820e-01 +1.680000000000000000e+05,1.274652895517647266e-03,9.987253546714782715e-01 +1.685000000000000000e+05,1.275995600735768676e-04,9.998723268508911133e-01 +1.690000000000000000e+05,9.950533509254455566e-01,4.946650471538305283e-03 +1.695000000000000000e+05,9.995934367179870605e-01,4.065202083438634872e-04 +1.700000000000000000e+05,1.725058443844318390e-02,9.827493429183959961e-01 +1.705000000000000000e+05,9.999779462814331055e-01,2.207912984886206686e-05 +1.710000000000000000e+05,9.999998807907104492e-01,1.092556232151764561e-07 +1.715000000000000000e+05,9.992747902870178223e-01,7.252380019053816795e-04 +1.720000000000000000e+05,4.556880295276641846e-01,5.443119406700134277e-01 +1.725000000000000000e+05,1.450070762075483799e-03,9.985498785972595215e-01 +1.730000000000000000e+05,1.372448023175820708e-04,9.998627901077270508e-01 +1.735000000000000000e+05,1.883285949588753283e-05,9.999811649322509766e-01 +1.740000000000000000e+05,6.228178972378373146e-04,9.993771910667419434e-01 +1.745000000000000000e+05,1.012817607261240482e-03,9.989871382713317871e-01 +1.750000000000000000e+05,9.110833052545785904e-04,9.990888833999633789e-01 +1.755000000000000000e+05,2.161749216611497104e-05,9.999784231185913086e-01 +1.760000000000000000e+05,1.047956743605027441e-06,9.999989271163940430e-01 +1.765000000000000000e+05,7.697667460888624191e-04,9.992302656173706055e-01 +1.770000000000000000e+05,9.911064058542251587e-02,9.008893966674804688e-01 +1.775000000000000000e+05,8.036985673243179917e-05,9.999196529388427734e-01 +1.780000000000000000e+05,2.779085189104080200e-04,9.997220635414123535e-01 +1.785000000000000000e+05,6.236405461095273495e-04,9.993763566017150879e-01 +1.790000000000000000e+05,1.698997948551550508e-04,9.998301267623901367e-01 +1.795000000000000000e+05,7.342267053900286555e-06,9.999926090240478516e-01 +1.800000000000000000e+05,5.819674115628004074e-04,9.994180202484130859e-01 +1.805000000000000000e+05,1.730947842588648200e-04,9.998269677162170410e-01 +1.810000000000000000e+05,4.520561778917908669e-04,9.995478987693786621e-01 +1.815000000000000000e+05,3.883131779730319977e-03,9.961168766021728516e-01 +1.820000000000000000e+05,4.515092587098479271e-04,9.995484948158264160e-01 +1.825000000000000000e+05,9.867030382156372070e-01,1.329697296023368835e-02 +1.830000000000000000e+05,9.999996423721313477e-01,3.351118778027739609e-07 +1.835000000000000000e+05,9.999473094940185547e-01,5.271693589747883379e-05 +1.840000000000000000e+05,1.874302923679351807e-01,8.125696778297424316e-01 +1.845000000000000000e+05,2.576909027993679047e-02,9.742308855056762695e-01 +1.850000000000000000e+05,3.326825797557830811e-02,9.667317867279052734e-01 +1.855000000000000000e+05,1.048544887453317642e-02,9.895145893096923828e-01 +1.860000000000000000e+05,1.472125062718987465e-03,9.985278844833374023e-01 +1.865000000000000000e+05,2.484502270817756653e-03,9.975155591964721680e-01 +1.870000000000000000e+05,7.763672620058059692e-03,9.922363162040710449e-01 +1.875000000000000000e+05,6.340502295643091202e-04,9.993659853935241699e-01 +1.880000000000000000e+05,6.094135096645914018e-05,9.999390840530395508e-01 +1.885000000000000000e+05,2.796854590997099876e-03,9.972031116485595703e-01 +1.890000000000000000e+05,5.136788822710514069e-03,9.948632121086120605e-01 +1.895000000000000000e+05,1.279757823795080185e-03,9.987202882766723633e-01 +1.900000000000000000e+05,2.917010860983282328e-04,9.997082352638244629e-01 +1.905000000000000000e+05,4.050888528581708670e-04,9.995948672294616699e-01 +1.910000000000000000e+05,7.081071089487522840e-05,9.999291896820068359e-01 +1.915000000000000000e+05,4.096246557310223579e-04,9.995903372764587402e-01 +1.920000000000000000e+05,4.790408129338175058e-04,9.995210170745849609e-01 +1.925000000000000000e+05,7.047785766189917922e-05,9.999295473098754883e-01 +1.930000000000000000e+05,1.192584022646769881e-04,9.998806715011596680e-01 +1.935000000000000000e+05,1.575261703692376614e-03,9.984247684478759766e-01 +1.940000000000000000e+05,9.969437718391418457e-01,3.056222572922706604e-03 +1.945000000000000000e+05,9.997361302375793457e-01,2.638071600813418627e-04 +1.950000000000000000e+05,9.144326448440551758e-01,8.556735515594482422e-02 +1.955000000000000000e+05,1.102217435836791992e-01,8.897782564163208008e-01 +1.960000000000000000e+05,1.866328902542591095e-02,9.813367724418640137e-01 +1.965000000000000000e+05,7.796094287186861038e-03,9.922039508819580078e-01 +1.970000000000000000e+05,9.999806880950927734e-01,1.935213003889657557e-05 +1.975000000000000000e+05,9.991282820701599121e-01,8.716728771105408669e-04 +1.980000000000000000e+05,9.273317456245422363e-01,7.266826182603836060e-02 +1.985000000000000000e+05,2.377928030909970403e-04,9.997622370719909668e-01 +1.990000000000000000e+05,1.277113333344459534e-04,9.998723268508911133e-01 +1.995000000000000000e+05,2.431037602946162224e-03,9.975689053535461426e-01 +2.000000000000000000e+05,6.820268463343381882e-03,9.931797981262207031e-01 +2.005000000000000000e+05,1.161779975518584251e-03,9.988382458686828613e-01 +2.010000000000000000e+05,2.007446484640240669e-03,9.979925155639648438e-01 +2.015000000000000000e+05,1.584369922056794167e-03,9.984155893325805664e-01 +2.020000000000000000e+05,5.816434510052204132e-03,9.941835999488830566e-01 +2.025000000000000000e+05,1.386332958936691284e-01,8.613666892051696777e-01 +2.030000000000000000e+05,5.827385932207107544e-02,9.417260885238647461e-01 +2.035000000000000000e+05,4.329413175582885742e-02,9.567059278488159180e-01 +2.040000000000000000e+05,1.913818268803879619e-04,9.998086094856262207e-01 +2.045000000000000000e+05,7.986491254996508360e-06,9.999920129776000977e-01 +2.050000000000000000e+05,6.253148312680423260e-04,9.993746876716613770e-01 +2.055000000000000000e+05,1.514401083113625646e-04,9.998484849929809570e-01 +2.060000000000000000e+05,3.953779488801956177e-02,9.604622125625610352e-01 +2.065000000000000000e+05,3.060322627425193787e-02,9.693967700004577637e-01 +2.070000000000000000e+05,5.092104765935800970e-05,9.999490976333618164e-01 +2.075000000000000000e+05,3.215990145690739155e-04,9.996783733367919922e-01 +2.080000000000000000e+05,9.750652313232421875e-01,2.493472769856452942e-02 +2.085000000000000000e+05,9.999841451644897461e-01,1.584182427905034274e-05 +2.090000000000000000e+05,9.968166947364807129e-01,3.183305263519287109e-03 +2.095000000000000000e+05,3.658226458355784416e-03,9.963417649269104004e-01 +2.100000000000000000e+05,2.476644003763794899e-03,9.975233674049377441e-01 +2.105000000000000000e+05,6.742317676544189453e-01,3.257682919502258301e-01 +2.110000000000000000e+05,6.359861493110656738e-01,3.640138804912567139e-01 +2.115000000000000000e+05,1.060943905031308532e-04,9.998939037322998047e-01 +2.120000000000000000e+05,1.516762040409957990e-05,9.999848604202270508e-01 +2.125000000000000000e+05,2.873089979402720928e-04,9.997126460075378418e-01 +2.130000000000000000e+05,1.553793670609593391e-03,9.984462857246398926e-01 +2.135000000000000000e+05,5.018242518417537212e-05,9.999498128890991211e-01 +2.140000000000000000e+05,2.928854373749345541e-04,9.997070431709289551e-01 +2.145000000000000000e+05,7.641569827683269978e-04,9.992358684539794922e-01 +2.150000000000000000e+05,1.314996331930160522e-01,8.685003519058227539e-01 +2.155000000000000000e+05,7.702629773120861501e-06,9.999922513961791992e-01 +2.160000000000000000e+05,4.742567543871700764e-04,9.995257854461669922e-01 +2.165000000000000000e+05,1.960577355930581689e-04,9.998039603233337402e-01 +2.170000000000000000e+05,1.727019262034446001e-04,9.998273253440856934e-01 +2.175000000000000000e+05,2.616879646666347980e-05,9.999737739562988281e-01 +2.180000000000000000e+05,2.772122513761132723e-07,9.999997615814208984e-01 +2.185000000000000000e+05,5.205502020544372499e-05,9.999479055404663086e-01 +2.190000000000000000e+05,5.887888744473457336e-02,9.411211609840393066e-01 +2.195000000000000000e+05,1.549651753157377243e-02,9.845035076141357422e-01 +2.200000000000000000e+05,1.820746983867138624e-04,9.998179078102111816e-01 +2.205000000000000000e+05,4.093985538929700851e-03,9.959059953689575195e-01 +2.210000000000000000e+05,3.686403855681419373e-02,9.631359577178955078e-01 +2.215000000000000000e+05,6.918151974678039551e-01,3.081847727298736572e-01 +2.220000000000000000e+05,1.076306030154228210e-02,9.892370104789733887e-01 +2.225000000000000000e+05,1.315355766564607620e-03,9.986845850944519043e-01 +2.230000000000000000e+05,1.321295974776148796e-03,9.986787438392639160e-01 +2.235000000000000000e+05,4.053341399412602186e-04,9.995946288108825684e-01 +2.240000000000000000e+05,1.867210492491722107e-02,9.813279509544372559e-01 +2.245000000000000000e+05,3.735800040885806084e-03,9.962641596794128418e-01 +2.250000000000000000e+05,2.205838391091674566e-04,9.997794032096862793e-01 +2.255000000000000000e+05,8.405421249335631728e-05,9.999159574508666992e-01 +2.260000000000000000e+05,1.135007012635469437e-03,9.988650083541870117e-01 +2.265000000000000000e+05,2.393712848424911499e-01,7.606287598609924316e-01 +2.270000000000000000e+05,2.224475145339965820e-02,9.777552485466003418e-01 +2.275000000000000000e+05,1.828308566473424435e-03,9.981716871261596680e-01 +2.280000000000000000e+05,1.788193942047655582e-03,9.982118606567382812e-01 +2.285000000000000000e+05,4.997711948817595840e-05,9.999500513076782227e-01 +2.290000000000000000e+05,1.928081386722624302e-03,9.980719089508056641e-01 +2.295000000000000000e+05,4.047989787068217993e-04,9.995952248573303223e-01 +2.300000000000000000e+05,3.853885573334991932e-05,9.999614953994750977e-01 +2.305000000000000000e+05,2.268542448291555047e-04,9.997732043266296387e-01 +2.310000000000000000e+05,3.720957040786743164e-01,6.279042959213256836e-01 +2.315000000000000000e+05,1.789928064681589603e-04,9.998210072517395020e-01 +2.320000000000000000e+05,1.862834542989730835e-01,8.137165307998657227e-01 +2.325000000000000000e+05,9.996612071990966797e-01,3.388261247891932726e-04 +2.330000000000000000e+05,5.177778005599975586e-01,4.822221696376800537e-01 +2.335000000000000000e+05,9.331261515617370605e-01,6.687386333942413330e-02 +2.340000000000000000e+05,9.254751801490783691e-01,7.452482730150222778e-02 +2.345000000000000000e+05,2.902173902839422226e-03,9.970978498458862305e-01 +2.350000000000000000e+05,9.999117851257324219e-01,8.821917435852810740e-05 +2.355000000000000000e+05,9.999998807907104492e-01,9.250235422086916515e-08 +2.360000000000000000e+05,9.986792206764221191e-01,1.320782699622213840e-03 +2.365000000000000000e+05,9.939495325088500977e-01,6.050518248230218887e-03 +2.370000000000000000e+05,9.999493360519409180e-01,5.068897735327482224e-05 +2.375000000000000000e+05,9.992782473564147949e-01,7.217302336357533932e-04 +2.380000000000000000e+05,5.250111818313598633e-01,4.749888181686401367e-01 +2.385000000000000000e+05,3.916951827704906464e-03,9.960830211639404297e-01 +2.390000000000000000e+05,1.669086399488151073e-03,9.983308911323547363e-01 +2.395000000000000000e+05,9.997715242207050323e-04,9.990002512931823730e-01 +2.400000000000000000e+05,4.619789542630314827e-04,9.995380640029907227e-01 +2.405000000000000000e+05,8.288820390589535236e-04,9.991711378097534180e-01 +2.410000000000000000e+05,1.728657167404890060e-03,9.982713460922241211e-01 +2.415000000000000000e+05,4.148099943995475769e-02,9.585189223289489746e-01 +2.420000000000000000e+05,2.239372115582227707e-03,9.977606534957885742e-01 +2.425000000000000000e+05,5.548630952835083008e-01,4.451369345188140869e-01 +2.430000000000000000e+05,9.999172687530517578e-01,8.276404696516692638e-05 +2.435000000000000000e+05,8.976599574089050293e-01,1.023400574922561646e-01 +2.440000000000000000e+05,6.595925660803914070e-04,9.993403553962707520e-01 +2.445000000000000000e+05,6.651445478200912476e-02,9.334855079650878906e-01 +2.450000000000000000e+05,9.999513626098632812e-01,4.860738408751785755e-05 +2.455000000000000000e+05,9.999985694885253906e-01,1.425531763743492775e-06 +2.460000000000000000e+05,9.960057139396667480e-01,3.994326572865247726e-03 +2.465000000000000000e+05,1.859697222243994474e-04,9.998140931129455566e-01 +2.470000000000000000e+05,1.349551894236356020e-04,9.998650550842285156e-01 +2.475000000000000000e+05,3.145537993987090886e-05,9.999685287475585938e-01 +2.480000000000000000e+05,4.449774278327822685e-04,9.995550513267517090e-01 +2.485000000000000000e+05,3.654046158771961927e-04,9.996346235275268555e-01 +2.490000000000000000e+05,8.318212167068850249e-06,9.999916553497314453e-01 +2.495000000000000000e+05,8.509228791808709502e-06,9.999915361404418945e-01 +2.500000000000000000e+05,1.161351799964904785e-03,9.988387227058410645e-01 +2.505000000000000000e+05,9.063485860824584961e-01,9.365133941173553467e-02 +2.510000000000000000e+05,9.997079968452453613e-01,2.920336264651268721e-04 +2.515000000000000000e+05,9.970876574516296387e-01,2.912370720878243446e-03 +2.520000000000000000e+05,9.466332197189331055e-01,5.336679890751838684e-02 +2.525000000000000000e+05,1.489267498254776001e-01,8.510732650756835938e-01 +2.530000000000000000e+05,9.955790638923645020e-01,4.420902114361524582e-03 +2.535000000000000000e+05,9.973256587982177734e-01,2.674282295629382133e-03 +2.540000000000000000e+05,4.957732185721397400e-02,9.504226446151733398e-01 +2.545000000000000000e+05,9.990649819374084473e-01,9.350375039502978325e-04 +2.550000000000000000e+05,9.935197234153747559e-01,6.480233278125524521e-03 +2.555000000000000000e+05,1.036236062645912170e-02,9.896376132965087891e-01 +2.560000000000000000e+05,7.830167305655777454e-04,9.992169141769409180e-01 +2.565000000000000000e+05,3.322500269860029221e-03,9.966775178909301758e-01 +2.570000000000000000e+05,1.716356782708317041e-04,9.998283386230468750e-01 +2.575000000000000000e+05,9.915671944618225098e-01,8.432838134467601776e-03 +2.580000000000000000e+05,9.933025836944580078e-01,6.697423290461301804e-03 +2.585000000000000000e+05,9.848207831382751465e-01,1.517917308956384659e-02 +2.590000000000000000e+05,7.348668575286865234e-01,2.651331126689910889e-01 +2.595000000000000000e+05,6.591568235307931900e-04,9.993408322334289551e-01 +2.600000000000000000e+05,8.310410380363464355e-01,1.689589321613311768e-01 +2.605000000000000000e+05,9.856239557266235352e-01,1.437602378427982330e-02 +2.610000000000000000e+05,1.041605646605603397e-04,9.998958110809326172e-01 +2.615000000000000000e+05,4.887846298515796661e-04,9.995112419128417969e-01 +2.620000000000000000e+05,3.470386436674743891e-04,9.996529817581176758e-01 +2.625000000000000000e+05,6.122273392975330353e-03,9.938777089118957520e-01 +2.630000000000000000e+05,6.443914771080017090e-01,3.556085526943206787e-01 +2.635000000000000000e+05,2.209649886935949326e-03,9.977903366088867188e-01 +2.640000000000000000e+05,4.186466336250305176e-03,9.958135485649108887e-01 +2.645000000000000000e+05,1.324385851621627808e-01,8.675614595413208008e-01 +2.650000000000000000e+05,8.796321926638484001e-04,9.991204142570495605e-01 +2.655000000000000000e+05,2.498913789168000221e-03,9.975010752677917480e-01 +2.660000000000000000e+05,3.259941586293280125e-04,9.996739625930786133e-01 +2.665000000000000000e+05,8.210384403355419636e-04,9.991789460182189941e-01 +2.670000000000000000e+05,9.777341485023498535e-01,2.226580493152141571e-02 +2.675000000000000000e+05,9.994078874588012695e-01,5.921117262914776802e-04 +2.680000000000000000e+05,9.992666840553283691e-01,7.333499379456043243e-04 +2.685000000000000000e+05,2.288944870233535767e-01,7.711055278778076172e-01 +2.690000000000000000e+05,5.795959234237670898e-01,4.204041063785552979e-01 +2.695000000000000000e+05,4.621524829417467117e-03,9.953784942626953125e-01 +2.700000000000000000e+05,9.996374845504760742e-01,3.625221434049308300e-04 +2.705000000000000000e+05,9.749571084976196289e-01,2.504287287592887878e-02 +2.710000000000000000e+05,9.588469862937927246e-01,4.115296900272369385e-02 +2.715000000000000000e+05,9.857069849967956543e-01,1.429297402501106262e-02 +2.720000000000000000e+05,3.986805677413940430e-02,9.601318836212158203e-01 +2.725000000000000000e+05,1.967467740178108215e-02,9.803254008293151855e-01 +2.730000000000000000e+05,4.896698985248804092e-03,9.951032400131225586e-01 +2.735000000000000000e+05,1.925051510334014893e-01,8.074948787689208984e-01 +2.740000000000000000e+05,2.891092002391815186e-02,9.710890650749206543e-01 +2.745000000000000000e+05,2.567817922681570053e-03,9.974321722984313965e-01 +2.750000000000000000e+05,9.244133234024047852e-01,7.558669149875640869e-02 +2.755000000000000000e+05,9.997593760490417480e-01,2.406633138889446855e-04 +2.760000000000000000e+05,9.970735311508178711e-01,2.926410408690571785e-03 +2.765000000000000000e+05,5.968493223190307617e-02,9.403150677680969238e-01 +2.770000000000000000e+05,2.548712538555264473e-03,9.974512457847595215e-01 +2.775000000000000000e+05,1.177114062011241913e-02,9.882287979125976562e-01 +2.780000000000000000e+05,4.638493657112121582e-01,5.361506342887878418e-01 +2.785000000000000000e+05,9.985532164573669434e-01,1.446788781322538853e-03 +2.790000000000000000e+05,9.777031540870666504e-01,2.229687012732028961e-02 +2.795000000000000000e+05,2.629852890968322754e-01,7.370146512985229492e-01 +2.800000000000000000e+05,9.591294452548027039e-03,9.904086589813232422e-01 +2.805000000000000000e+05,2.071970142424106598e-03,9.979280233383178711e-01 +2.810000000000000000e+05,7.111493614502251148e-04,9.992887973785400391e-01 +2.815000000000000000e+05,3.058539237827062607e-03,9.969413876533508301e-01 +2.820000000000000000e+05,3.110624675173312426e-05,9.999688863754272461e-01 +2.825000000000000000e+05,1.020798226818442345e-04,9.998979568481445312e-01 +2.830000000000000000e+05,1.665811141720041633e-04,9.998333454132080078e-01 +2.835000000000000000e+05,9.041076302528381348e-01,9.589239209890365601e-02 +2.840000000000000000e+05,9.999938011169433594e-01,6.196700269356369972e-06 +2.845000000000000000e+05,9.999188184738159180e-01,8.120231359498575330e-05 +2.850000000000000000e+05,9.909515380859375000e-01,9.048423729836940765e-03 +2.855000000000000000e+05,9.861450195312500000e-01,1.385500654578208923e-02 +2.860000000000000000e+05,1.932701445184648037e-03,9.980673193931579590e-01 +2.865000000000000000e+05,9.758118987083435059e-01,2.418805286288261414e-02 +2.870000000000000000e+05,1.846267282962799072e-01,8.153733015060424805e-01 +2.875000000000000000e+05,4.964838735759258270e-04,9.995034933090209961e-01 +2.880000000000000000e+05,1.116523519158363342e-03,9.988834261894226074e-01 +2.885000000000000000e+05,5.901401891605928540e-06,9.999940395355224609e-01 +2.890000000000000000e+05,4.112231254111975431e-04,9.995887875556945801e-01 +2.895000000000000000e+05,1.067255288944579661e-04,9.998933076858520508e-01 +2.900000000000000000e+05,1.158929392695426941e-01,8.841071128845214844e-01 +2.905000000000000000e+05,7.487473487854003906e-01,2.512526512145996094e-01 +2.910000000000000000e+05,5.019410309614613652e-05,9.999498128890991211e-01 +2.915000000000000000e+05,5.547795910388231277e-03,9.944521784782409668e-01 +2.920000000000000000e+05,4.416962619870901108e-03,9.955829977989196777e-01 +2.925000000000000000e+05,1.967181917279958725e-03,9.980328679084777832e-01 +2.930000000000000000e+05,1.106756855733692646e-03,9.988933205604553223e-01 +2.935000000000000000e+05,9.871389484032988548e-04,9.990128278732299805e-01 +2.940000000000000000e+05,9.535406570648774505e-05,9.999046325683593750e-01 +2.945000000000000000e+05,7.567036151885986328e-01,2.432963997125625610e-01 +2.950000000000000000e+05,9.999974966049194336e-01,2.527657443351927213e-06 +2.955000000000000000e+05,9.998391866683959961e-01,1.607883314136415720e-04 +2.960000000000000000e+05,8.829446509480476379e-03,9.911705255508422852e-01 +2.965000000000000000e+05,1.214960217475891113e-02,9.878503680229187012e-01 +2.970000000000000000e+05,7.136807441711425781e-01,2.863192558288574219e-01 +2.975000000000000000e+05,6.756019592285156250e-02,9.324398040771484375e-01 +2.980000000000000000e+05,6.079379454604350030e-05,9.999392032623291016e-01 +2.985000000000000000e+05,5.670283571816980839e-04,9.994329810142517090e-01 +2.990000000000000000e+05,7.002919912338256836e-03,9.929971098899841309e-01 +2.995000000000000000e+05,2.610188093967735767e-04,9.997389912605285645e-01 +3.000000000000000000e+05,9.494220614433288574e-01,5.057794600725173950e-02 +3.005000000000000000e+05,9.048762321472167969e-01,9.512381255626678467e-02 +3.010000000000000000e+05,5.684888362884521484e-01,4.315111041069030762e-01 +3.015000000000000000e+05,5.926027521491050720e-02,9.407396912574768066e-01 +3.020000000000000000e+05,8.542725443840026855e-02,9.145727157592773438e-01 +3.025000000000000000e+05,7.064378261566162109e-01,2.935621738433837891e-01 +3.030000000000000000e+05,9.217506051063537598e-01,7.824943959712982178e-02 +3.035000000000000000e+05,9.999992847442626953e-01,6.946789312678447459e-07 +3.040000000000000000e+05,1.000000000000000000e+00,2.768085138882270257e-08 +3.045000000000000000e+05,9.906751513481140137e-01,9.324858896434307098e-03 +3.050000000000000000e+05,9.947199821472167969e-01,5.279998760670423508e-03 +3.055000000000000000e+05,1.054492965340614319e-02,9.894550442695617676e-01 +3.060000000000000000e+05,9.513027907814830542e-05,9.999048709869384766e-01 +3.065000000000000000e+05,4.916996508836746216e-02,9.508301019668579102e-01 +3.070000000000000000e+05,6.108473171480000019e-04,9.993891716003417969e-01 +3.075000000000000000e+05,1.401681103743612766e-03,9.985982775688171387e-01 +3.080000000000000000e+05,4.859429970383644104e-02,9.514057040214538574e-01 +3.085000000000000000e+05,4.294298705644905567e-04,9.995705485343933105e-01 +3.090000000000000000e+05,3.616648958995938301e-03,9.963833093643188477e-01 +3.095000000000000000e+05,2.148359455168247223e-02,9.785163998603820801e-01 +3.100000000000000000e+05,9.932460784912109375e-01,6.753947120159864426e-03 +3.105000000000000000e+05,9.999827146530151367e-01,1.731465999910142273e-05 +3.110000000000000000e+05,9.999601840972900391e-01,3.983465649071149528e-05 +3.115000000000000000e+05,9.990860223770141602e-01,9.140230831690132618e-04 +3.120000000000000000e+05,9.960600733757019043e-01,3.939920105040073395e-03 +3.125000000000000000e+05,7.025424242019653320e-01,2.974576354026794434e-01 +3.130000000000000000e+05,6.216188669204711914e-01,3.783811330795288086e-01 +3.135000000000000000e+05,2.984499558806419373e-02,9.701550006866455078e-01 +3.140000000000000000e+05,9.661584496498107910e-01,3.384153917431831360e-02 +3.145000000000000000e+05,2.613387769088149071e-04,9.997386336326599121e-01 +3.150000000000000000e+05,1.498471572995185852e-02,9.850152730941772461e-01 +3.155000000000000000e+05,1.724139787256717682e-03,9.982758760452270508e-01 +3.160000000000000000e+05,3.687307238578796387e-02,9.631268978118896484e-01 +3.165000000000000000e+05,6.041980304871685803e-05,9.999395608901977539e-01 +3.170000000000000000e+05,3.327446524053812027e-03,9.966725111007690430e-01 +3.175000000000000000e+05,2.241535671055316925e-02,9.775846600532531738e-01 +3.180000000000000000e+05,1.336770132184028625e-02,9.866322875022888184e-01 +3.185000000000000000e+05,2.296359837055206299e-01,7.703640460968017578e-01 +3.190000000000000000e+05,2.925324079114943743e-04,9.997074007987976074e-01 +3.195000000000000000e+05,1.837598538259044290e-04,9.998162388801574707e-01 +3.200000000000000000e+05,1.463668607175350189e-02,9.853633642196655273e-01 +3.205000000000000000e+05,4.492072912398725748e-04,9.995507597923278809e-01 +3.210000000000000000e+05,9.990697503089904785e-01,9.303277474828064442e-04 +3.215000000000000000e+05,9.999995231628417969e-01,4.938581241731299087e-07 +3.220000000000000000e+05,9.999800920486450195e-01,1.990890268643852323e-05 +3.225000000000000000e+05,4.165268540382385254e-01,5.834731459617614746e-01 +3.230000000000000000e+05,6.324098706245422363e-01,3.675901591777801514e-01 +3.235000000000000000e+05,7.977415323257446289e-01,2.022584229707717896e-01 +3.240000000000000000e+05,9.628627896308898926e-01,3.713719174265861511e-02 +3.245000000000000000e+05,9.733672142028808594e-01,2.663275785744190216e-02 +3.250000000000000000e+05,4.934722557663917542e-03,9.950652718544006348e-01 +3.255000000000000000e+05,1.644371077418327332e-03,9.983555674552917480e-01 +3.260000000000000000e+05,2.955658601422328502e-05,9.999704360961914062e-01 +3.265000000000000000e+05,7.856132579036056995e-04,9.992144107818603516e-01 +3.270000000000000000e+05,6.975231226533651352e-03,9.930248260498046875e-01 +3.275000000000000000e+05,2.220384776592254639e-01,7.779614925384521484e-01 +3.280000000000000000e+05,1.462807655334472656e-01,8.537192940711975098e-01 +3.285000000000000000e+05,7.913912995718419552e-04,9.992086291313171387e-01 +3.290000000000000000e+05,5.313437897711992264e-03,9.946866035461425781e-01 +3.295000000000000000e+05,6.109483656473457813e-04,9.993890523910522461e-01 +3.300000000000000000e+05,9.932356476783752441e-01,6.764392834156751633e-03 +3.305000000000000000e+05,9.999778270721435547e-01,2.217176006524823606e-05 +3.310000000000000000e+05,9.747742414474487305e-01,2.522572316229343414e-02 +3.315000000000000000e+05,6.666893023066222668e-04,9.993333220481872559e-01 +3.320000000000000000e+05,1.055631655617617071e-04,9.998943805694580078e-01 +3.325000000000000000e+05,7.278190605575218797e-05,9.999271631240844727e-01 +3.330000000000000000e+05,9.996583461761474609e-01,3.415980900172144175e-04 +3.335000000000000000e+05,9.999828338623046875e-01,1.714857353363186121e-05 +3.340000000000000000e+05,9.956985712051391602e-01,4.301470704376697540e-03 +3.345000000000000000e+05,7.058930117636919022e-03,9.929410219192504883e-01 +3.350000000000000000e+05,3.281513461843132973e-03,9.967184662818908691e-01 +3.355000000000000000e+05,9.932789206504821777e-01,6.721091456711292267e-03 +3.360000000000000000e+05,9.973617196083068848e-01,2.638282952830195427e-03 +3.365000000000000000e+05,8.708162903785705566e-01,1.291837096214294434e-01 +3.370000000000000000e+05,8.280463218688964844e-01,1.719536781311035156e-01 +3.375000000000000000e+05,4.123975634574890137e-01,5.876024365425109863e-01 +3.380000000000000000e+05,3.755281912162899971e-03,9.962447285652160645e-01 +3.385000000000000000e+05,1.161373918876051903e-03,9.988386034965515137e-01 +3.390000000000000000e+05,9.988086223602294922e-01,1.191396964713931084e-03 +3.395000000000000000e+05,9.999948740005493164e-01,5.078793947177473456e-06 +3.400000000000000000e+05,9.985563158988952637e-01,1.443698187358677387e-03 +3.405000000000000000e+05,8.765139617025852203e-03,9.912347793579101562e-01 +3.410000000000000000e+05,2.737431554123759270e-03,9.972625970840454102e-01 +3.415000000000000000e+05,1.745681394822895527e-03,9.982543587684631348e-01 +3.420000000000000000e+05,9.115660795941948891e-04,9.990884065628051758e-01 +3.425000000000000000e+05,2.353266347199678421e-03,9.976467490196228027e-01 +3.430000000000000000e+05,2.561857458204030991e-03,9.974380731582641602e-01 +3.435000000000000000e+05,2.505379088688641787e-04,9.997494816780090332e-01 +3.440000000000000000e+05,4.880836640950292349e-04,9.995119571685791016e-01 +3.445000000000000000e+05,1.976134926080703735e-01,8.023864626884460449e-01 +3.450000000000000000e+05,9.767314195632934570e-01,2.326863445341587067e-02 +3.455000000000000000e+05,9.982719421386718750e-01,1.728064031340181828e-03 +3.460000000000000000e+05,9.868990182876586914e-01,1.310104131698608398e-02 +3.465000000000000000e+05,9.308142662048339844e-01,6.918573379516601562e-02 +3.470000000000000000e+05,9.702982902526855469e-01,2.970167994499206543e-02 +3.475000000000000000e+05,9.690728783607482910e-01,3.092708997428417206e-02 +3.480000000000000000e+05,9.993683695793151855e-01,6.316584185697138309e-04 +3.485000000000000000e+05,9.997563958168029785e-01,2.435588976368308067e-04 +3.490000000000000000e+05,6.852387189865112305e-01,3.147612810134887695e-01 +3.495000000000000000e+05,9.792509078979492188e-01,2.074913494288921356e-02 +3.500000000000000000e+05,2.386648207902908325e-03,9.976133108139038086e-01 +3.505000000000000000e+05,2.972647198475897312e-04,9.997027516365051270e-01 +3.510000000000000000e+05,3.125153779983520508e-01,6.874846220016479492e-01 +3.515000000000000000e+05,2.103220904245972633e-03,9.978967905044555664e-01 +3.520000000000000000e+05,1.271552871912717819e-03,9.987284541130065918e-01 +3.525000000000000000e+05,2.237861007452011108e-01,7.762138843536376953e-01 +3.530000000000000000e+05,1.345452815294265747e-01,8.654546737670898438e-01 +3.535000000000000000e+05,1.117184801842086017e-04,9.998883008956909180e-01 +3.540000000000000000e+05,3.638279158622026443e-04,9.996361732482910156e-01 +3.545000000000000000e+05,1.046474135364405811e-04,9.998953342437744141e-01 +3.550000000000000000e+05,3.056069836020469666e-04,9.996944665908813477e-01 +3.555000000000000000e+05,1.001439057290554047e-03,9.989985823631286621e-01 +3.560000000000000000e+05,9.473083615303039551e-01,5.269164592027664185e-02 +3.565000000000000000e+05,9.999747276306152344e-01,2.532893086026888341e-05 +3.570000000000000000e+05,9.982277750968933105e-01,1.772249815985560417e-03 +3.575000000000000000e+05,5.207396745681762695e-01,4.792603552341461182e-01 +3.580000000000000000e+05,7.757746148854494095e-03,9.922423362731933594e-01 +3.585000000000000000e+05,1.601680298335850239e-03,9.983983635902404785e-01 +3.590000000000000000e+05,7.978550158441066742e-03,9.920215010643005371e-01 +3.595000000000000000e+05,2.655402640812098980e-04,9.997344613075256348e-01 +3.600000000000000000e+05,9.995232820510864258e-01,4.766871570609509945e-04 +3.605000000000000000e+05,9.999645948410034180e-01,3.541077967383898795e-05 +3.610000000000000000e+05,9.671677947044372559e-01,3.283221647143363953e-02 +3.615000000000000000e+05,8.563571609556674957e-03,9.914364218711853027e-01 +3.620000000000000000e+05,8.875119686126708984e-01,1.124880090355873108e-01 +3.625000000000000000e+05,8.440039157867431641e-01,1.559960991144180298e-01 +3.630000000000000000e+05,2.796314889565110207e-03,9.972037076950073242e-01 +3.635000000000000000e+05,2.315477322554215789e-04,9.997684359550476074e-01 +3.640000000000000000e+05,3.204085078323259950e-05,9.999679327011108398e-01 +3.645000000000000000e+05,7.700962014496326447e-04,9.992299079895019531e-01 +3.650000000000000000e+05,4.153288318775594234e-04,9.995847344398498535e-01 +3.655000000000000000e+05,1.491994044044986367e-04,9.998507499694824219e-01 +3.660000000000000000e+05,8.158024284057319164e-04,9.991841912269592285e-01 +3.665000000000000000e+05,3.699107095599174500e-03,9.963008165359497070e-01 +3.670000000000000000e+05,1.186825335025787354e-03,9.988131523132324219e-01 +3.675000000000000000e+05,4.822128452360630035e-03,9.951778650283813477e-01 +3.680000000000000000e+05,7.499494240619242191e-04,9.992499947547912598e-01 +3.685000000000000000e+05,5.543041974306106567e-03,9.944568872451782227e-01 +3.690000000000000000e+05,2.590053901076316833e-02,9.740995168685913086e-01 +3.695000000000000000e+05,7.898442447185516357e-03,9.921016097068786621e-01 +3.700000000000000000e+05,5.193674564361572266e-01,4.806325137615203857e-01 +3.705000000000000000e+05,1.880986541509628296e-01,8.119013905525207520e-01 +3.710000000000000000e+05,2.470332547090947628e-04,9.997529387474060059e-01 +3.715000000000000000e+05,1.602378115057945251e-02,9.839762449264526367e-01 +3.720000000000000000e+05,9.942348599433898926e-01,5.765103735029697418e-03 +3.725000000000000000e+05,9.972828626632690430e-01,2.717095892876386642e-03 +3.730000000000000000e+05,1.448013633489608765e-01,8.551986813545227051e-01 +3.735000000000000000e+05,2.979738346766680479e-04,9.997020363807678223e-01 +3.740000000000000000e+05,1.053837826475501060e-03,9.989461302757263184e-01 +3.745000000000000000e+05,4.543414979707449675e-04,9.995456337928771973e-01 +3.750000000000000000e+05,1.612515625311061740e-04,9.998387098312377930e-01 +3.755000000000000000e+05,1.037930720485746861e-03,9.989620447158813477e-01 +3.760000000000000000e+05,2.424678532406687737e-04,9.997575879096984863e-01 +3.765000000000000000e+05,1.048716949298977852e-03,9.989511966705322266e-01 +3.770000000000000000e+05,3.662998601794242859e-02,9.633700251579284668e-01 +3.775000000000000000e+05,1.134292571805417538e-03,9.988657236099243164e-01 +3.780000000000000000e+05,1.348522258922457695e-03,9.986515641212463379e-01 +3.785000000000000000e+05,8.436195785179734230e-04,9.991563558578491211e-01 +3.790000000000000000e+05,2.308696322143077850e-02,9.769130945205688477e-01 +3.795000000000000000e+05,6.044883630238473415e-04,9.993954896926879883e-01 +3.800000000000000000e+05,5.405845586210489273e-04,9.994594454765319824e-01 +3.805000000000000000e+05,1.064310999936424196e-04,9.998935461044311523e-01 +3.810000000000000000e+05,1.636890578083693981e-03,9.983630776405334473e-01 +3.815000000000000000e+05,7.679540198296308517e-03,9.923204779624938965e-01 +3.820000000000000000e+05,1.803372404538094997e-03,9.981966614723205566e-01 +3.825000000000000000e+05,3.543685423210263252e-03,9.964563250541687012e-01 +3.830000000000000000e+05,5.627053380012512207e-01,4.372946619987487793e-01 +3.835000000000000000e+05,2.531827893108129501e-03,9.974682331085205078e-01 +3.840000000000000000e+05,4.839089160668663681e-05,9.999516010284423828e-01 +3.845000000000000000e+05,5.133048398420214653e-05,9.999486207962036133e-01 +3.850000000000000000e+05,2.195922657847404480e-03,9.978041052818298340e-01 +3.855000000000000000e+05,2.528683980926871300e-03,9.974713325500488281e-01 +3.860000000000000000e+05,7.767136557959020138e-04,9.992232322692871094e-01 +3.865000000000000000e+05,7.540664519183337688e-04,9.992460012435913086e-01 +3.870000000000000000e+05,6.906549970153719187e-05,9.999309778213500977e-01 +3.875000000000000000e+05,1.171614858321845531e-03,9.988283514976501465e-01 +3.880000000000000000e+05,9.952360987663269043e-01,4.763904958963394165e-03 +3.885000000000000000e+05,9.415346980094909668e-01,5.846534296870231628e-02 +3.890000000000000000e+05,1.971502322703599930e-03,9.980284571647644043e-01 +3.895000000000000000e+05,6.027266383171081543e-01,3.972734212875366211e-01 +3.900000000000000000e+05,9.998672008514404297e-01,1.327631325693801045e-04 +3.905000000000000000e+05,9.407376050949096680e-01,5.926235392689704895e-02 +3.910000000000000000e+05,2.937734313309192657e-02,9.706225991249084473e-01 +3.915000000000000000e+05,2.458252944052219391e-02,9.754174947738647461e-01 +3.920000000000000000e+05,1.598083646968007088e-03,9.984019398689270020e-01 +3.925000000000000000e+05,4.601594060659408569e-02,9.539840817451477051e-01 +3.930000000000000000e+05,2.718940086197108030e-04,9.997281432151794434e-01 +3.935000000000000000e+05,5.930499537498690188e-05,9.999406337738037109e-01 +3.940000000000000000e+05,1.256332080811262131e-02,9.874366521835327148e-01 +3.945000000000000000e+05,2.500591427087783813e-02,9.749940633773803711e-01 +3.950000000000000000e+05,1.381052308715879917e-03,9.986189603805541992e-01 +3.955000000000000000e+05,1.188722744700498879e-04,9.998811483383178711e-01 +3.960000000000000000e+05,1.495205651735886931e-04,9.998505115509033203e-01 +3.965000000000000000e+05,6.304105772869661450e-05,9.999369382858276367e-01 +3.970000000000000000e+05,8.281684131361544132e-06,9.999917745590209961e-01 +3.975000000000000000e+05,1.945232070283964276e-04,9.998055100440979004e-01 +3.980000000000000000e+05,9.448813274502754211e-03,9.905512332916259766e-01 +3.985000000000000000e+05,2.813834231346845627e-03,9.971861243247985840e-01 +3.990000000000000000e+05,3.080836322624236345e-04,9.996919631958007812e-01 +3.995000000000000000e+05,1.215328447869978845e-04,9.998784065246582031e-01 +4.000000000000000000e+05,2.427331201033666730e-04,9.997572302818298340e-01 +4.005000000000000000e+05,2.678787313925568014e-05,9.999731779098510742e-01 +4.010000000000000000e+05,1.212331844726577401e-04,9.998787641525268555e-01 +4.015000000000000000e+05,3.993590362370014191e-03,9.960064291954040527e-01 +4.020000000000000000e+05,7.055585592752322555e-05,9.999294281005859375e-01 +4.025000000000000000e+05,1.023214077576994896e-03,9.989768266677856445e-01 +4.030000000000000000e+05,1.196960336528718472e-03,9.988030195236206055e-01 +4.035000000000000000e+05,4.696827232837677002e-01,5.303173065185546875e-01 +4.040000000000000000e+05,8.251830339431762695e-01,1.748169511556625366e-01 +4.045000000000000000e+05,7.588366866111755371e-01,2.411632835865020752e-01 +4.050000000000000000e+05,8.752305060625076294e-02,9.124769568443298340e-01 +4.055000000000000000e+05,5.651621613651514053e-03,9.943483471870422363e-01 +4.060000000000000000e+05,1.921512885019183159e-04,9.998078942298889160e-01 +4.065000000000000000e+05,2.207416400779038668e-04,9.997792840003967285e-01 +4.070000000000000000e+05,3.191744536161422729e-02,9.680826067924499512e-01 +4.075000000000000000e+05,3.858054578304290771e-01,6.141945719718933105e-01 +4.080000000000000000e+05,9.445001184940338135e-02,9.055500030517578125e-01 +4.085000000000000000e+05,2.289596498012542725e-01,7.710403203964233398e-01 +4.090000000000000000e+05,5.553433438763022423e-04,9.994446635246276855e-01 +4.095000000000000000e+05,3.087772638536989689e-04,9.996912479400634766e-01 +4.100000000000000000e+05,6.891191333124879748e-06,9.999930858612060547e-01 +4.105000000000000000e+05,9.959886670112609863e-01,4.011312033981084824e-03 +4.110000000000000000e+05,9.979037046432495117e-01,2.096318639814853668e-03 +4.115000000000000000e+05,2.511488673917483538e-06,9.999974966049194336e-01 +4.120000000000000000e+05,4.084360785782337189e-03,9.959155917167663574e-01 +4.125000000000000000e+05,1.502963304519653320e-01,8.497036695480346680e-01 +4.130000000000000000e+05,1.251251529902219772e-02,9.874874353408813477e-01 +4.135000000000000000e+05,9.977336972951889038e-03,9.900227189064025879e-01 +4.140000000000000000e+05,3.280396776972338557e-05,9.999672174453735352e-01 +4.145000000000000000e+05,6.645514816045761108e-02,9.335448145866394043e-01 +4.150000000000000000e+05,9.997910857200622559e-01,2.088860637741163373e-04 +4.155000000000000000e+05,9.997231364250183105e-01,2.768955891951918602e-04 +4.160000000000000000e+05,9.910324215888977051e-01,8.967545814812183380e-03 +4.165000000000000000e+05,2.993265632539987564e-03,9.970067143440246582e-01 +4.170000000000000000e+05,6.380328559316694736e-04,9.993619322776794434e-01 +4.175000000000000000e+05,9.238344617187976837e-03,9.907616972923278809e-01 +4.180000000000000000e+05,9.856808185577392578e-01,1.431917212903499603e-02 +4.185000000000000000e+05,1.072814370672858786e-06,9.999989271163940430e-01 +4.190000000000000000e+05,6.157071329653263092e-04,9.993842840194702148e-01 +4.195000000000000000e+05,3.001019824296236038e-03,9.969990253448486328e-01 +4.200000000000000000e+05,9.797427058219909668e-01,2.025725878775119781e-02 +4.205000000000000000e+05,9.999909400939941406e-01,9.029189641296397895e-06 +4.210000000000000000e+05,9.999358654022216797e-01,6.412326183635741472e-05 +4.215000000000000000e+05,6.878003478050231934e-02,9.312199354171752930e-01 +4.220000000000000000e+05,2.795737795531749725e-02,9.720426201820373535e-01 +4.225000000000000000e+05,9.998137354850769043e-01,1.862976932898163795e-04 +4.230000000000000000e+05,9.992196559906005859e-01,7.803582702763378620e-04 +4.235000000000000000e+05,7.504967041313648224e-03,9.924950599670410156e-01 +4.240000000000000000e+05,2.829560311511158943e-03,9.971705079078674316e-01 +4.245000000000000000e+05,2.023446140810847282e-03,9.979766011238098145e-01 +4.250000000000000000e+05,5.701795592904090881e-02,9.429820775985717773e-01 +4.255000000000000000e+05,5.645226687192916870e-03,9.943548440933227539e-01 +4.260000000000000000e+05,9.999822378158569336e-01,1.778594560164492577e-05 +4.265000000000000000e+05,9.893424510955810547e-01,1.065753679722547531e-02 +4.270000000000000000e+05,9.930658340454101562e-01,6.934200879186391830e-03 +4.275000000000000000e+05,9.941931366920471191e-01,5.806866101920604706e-03 +4.280000000000000000e+05,3.646996617317199707e-01,6.353003382682800293e-01 +4.285000000000000000e+05,3.232901217415928841e-03,9.967671632766723633e-01 +4.290000000000000000e+05,8.514181375503540039e-01,1.485818624496459961e-01 +4.295000000000000000e+05,9.999587535858154297e-01,4.129127410124056041e-05 +4.300000000000000000e+05,9.999408721923828125e-01,5.915228030062280595e-05 +4.305000000000000000e+05,9.991845488548278809e-01,8.153865928761661053e-04 +4.310000000000000000e+05,9.988903403282165527e-01,1.109635690227150917e-03 +4.315000000000000000e+05,9.936164617538452148e-01,6.383564323186874390e-03 +4.320000000000000000e+05,9.753201603889465332e-01,2.467984333634376526e-02 +4.325000000000000000e+05,2.526309690438210964e-04,9.997473359107971191e-01 +4.330000000000000000e+05,9.988746047019958496e-01,1.125330221839249134e-03 +4.335000000000000000e+05,9.994922876358032227e-01,5.076677771285176277e-04 +4.340000000000000000e+05,8.392111631110310555e-04,9.991607666015625000e-01 +4.345000000000000000e+05,9.990565180778503418e-01,9.434560779482126236e-04 +4.350000000000000000e+05,9.982878565788269043e-01,1.712169498205184937e-03 +4.355000000000000000e+05,9.883311390876770020e-01,1.166880503296852112e-02 +4.360000000000000000e+05,3.777496218681335449e-01,6.222503781318664551e-01 +4.365000000000000000e+05,9.964424967765808105e-01,3.557530930265784264e-03 +4.370000000000000000e+05,9.956047534942626953e-01,4.395229276269674301e-03 +4.375000000000000000e+05,2.039998583495616913e-02,9.796000123023986816e-01 +4.380000000000000000e+05,8.864440023899078369e-04,9.991135001182556152e-01 +4.385000000000000000e+05,9.292414188385009766e-01,7.075855880975723267e-02 +4.390000000000000000e+05,9.559817314147949219e-01,4.401819035410881042e-02 +4.395000000000000000e+05,2.844406664371490479e-01,7.155593633651733398e-01 +4.400000000000000000e+05,9.831051575019955635e-04,9.990168809890747070e-01 +4.405000000000000000e+05,2.940268628299236298e-02,9.705973863601684570e-01 +4.410000000000000000e+05,9.729803800582885742e-01,2.701956592500209808e-02 +4.415000000000000000e+05,9.998121857643127441e-01,1.878828625194728374e-04 +4.420000000000000000e+05,9.997075200080871582e-01,2.924181171692907810e-04 +4.425000000000000000e+05,1.638937294483184814e-01,8.361062407493591309e-01 +4.430000000000000000e+05,9.992075562477111816e-01,7.924326928332448006e-04 +4.435000000000000000e+05,9.985456466674804688e-01,1.454393961466848850e-03 +4.440000000000000000e+05,8.438240289688110352e-01,1.561760157346725464e-01 +4.445000000000000000e+05,9.783051013946533203e-01,2.169487625360488892e-02 +4.450000000000000000e+05,4.654079675674438477e-02,9.534591436386108398e-01 +4.455000000000000000e+05,3.276870772242546082e-02,9.672312736511230469e-01 +4.460000000000000000e+05,1.604923903942108154e-01,8.395076394081115723e-01 +4.465000000000000000e+05,5.799754336476325989e-02,9.420024156570434570e-01 +4.470000000000000000e+05,9.919227361679077148e-01,8.077262900769710541e-03 +4.475000000000000000e+05,9.956440925598144531e-01,4.355867393314838409e-03 +4.480000000000000000e+05,3.417220413684844971e-01,6.582779884338378906e-01 +4.485000000000000000e+05,7.404957711696624756e-02,9.259504079818725586e-01 +4.490000000000000000e+05,1.002423159661702812e-04,9.998997449874877930e-01 +4.495000000000000000e+05,9.180232286453247070e-01,8.197673410177230835e-02 +4.500000000000000000e+05,8.642617103760130703e-06,9.999912977218627930e-01 +4.505000000000000000e+05,7.380011491477489471e-03,9.926200509071350098e-01 +4.510000000000000000e+05,9.998124241828918457e-01,1.875417074188590050e-04 +4.515000000000000000e+05,1.000000000000000000e+00,1.767696211629754544e-08 +4.520000000000000000e+05,9.991810917854309082e-01,8.189397049136459827e-04 +4.525000000000000000e+05,9.999994039535522461e-01,5.541808150155702606e-07 +4.530000000000000000e+05,9.999865293502807617e-01,1.347451507172081620e-05 +4.535000000000000000e+05,9.838581681251525879e-01,1.614183560013771057e-02 +4.540000000000000000e+05,9.599103927612304688e-01,4.008960723876953125e-02 +4.545000000000000000e+05,6.747226871084421873e-05,9.999325275421142578e-01 +4.550000000000000000e+05,4.635984543710947037e-03,9.953639507293701172e-01 +4.555000000000000000e+05,5.976448655128479004e-01,4.023551046848297119e-01 +4.560000000000000000e+05,6.518667936325073242e-01,3.481331765651702881e-01 +4.565000000000000000e+05,2.843650290742516518e-04,9.997156262397766113e-01 +4.570000000000000000e+05,5.937271052971482277e-04,9.994062185287475586e-01 +4.575000000000000000e+05,9.924178123474121094e-01,7.582242134958505630e-03 +4.580000000000000000e+05,9.995054006576538086e-01,4.945466062054038048e-04 +4.585000000000000000e+05,9.992062449455261230e-01,7.937393384054303169e-04 +4.590000000000000000e+05,6.761983782052993774e-02,9.323801398277282715e-01 +4.595000000000000000e+05,5.584298372268676758e-01,4.415701627731323242e-01 +4.600000000000000000e+05,6.887866184115409851e-04,9.993112087249755859e-01 +4.605000000000000000e+05,1.196593418717384338e-02,9.880340695381164551e-01 +4.610000000000000000e+05,9.645355343818664551e-01,3.546442091464996338e-02 +4.615000000000000000e+05,5.479755345731973648e-03,9.945202469825744629e-01 +4.620000000000000000e+05,7.051716092973947525e-04,9.992947578430175781e-01 +4.625000000000000000e+05,9.952656030654907227e-01,4.734348505735397339e-03 +4.630000000000000000e+05,9.999506473541259766e-01,4.930354407406412065e-05 +4.635000000000000000e+05,9.993366599082946777e-01,6.633065058849751949e-04 +4.640000000000000000e+05,7.439553141593933105e-01,2.560447156429290771e-01 +4.645000000000000000e+05,3.064429759979248047e-01,6.935570836067199707e-01 +4.650000000000000000e+05,9.924915432929992676e-01,7.508427370339632034e-03 +4.655000000000000000e+05,9.992388486862182617e-01,7.611648179590702057e-04 +4.660000000000000000e+05,4.626023396849632263e-02,9.537398219108581543e-01 +4.665000000000000000e+05,8.249944949056953192e-05,9.999175071716308594e-01 +4.670000000000000000e+05,1.680959685472771525e-04,9.998319149017333984e-01 +4.675000000000000000e+05,7.824068234185688198e-06,9.999921321868896484e-01 +4.680000000000000000e+05,9.667015671730041504e-01,3.329838067293167114e-02 +4.685000000000000000e+05,9.994478821754455566e-01,5.520772538147866726e-04 +4.690000000000000000e+05,9.931076169013977051e-01,6.892467848956584930e-03 +4.695000000000000000e+05,2.099872678518295288e-01,7.900127172470092773e-01 +4.700000000000000000e+05,9.998917579650878906e-01,1.081870941561646760e-04 +4.705000000000000000e+05,9.995461106300354004e-01,4.538309003692120314e-04 +4.710000000000000000e+05,3.295215964317321777e-01,6.704784035682678223e-01 +4.715000000000000000e+05,1.173905804753303528e-01,8.826094269752502441e-01 +4.720000000000000000e+05,4.532716274261474609e-01,5.467283129692077637e-01 +4.725000000000000000e+05,3.967952355742454529e-02,9.603205323219299316e-01 +4.730000000000000000e+05,7.995547652244567871e-01,2.004452496767044067e-01 +4.735000000000000000e+05,1.003379523754119873e-01,8.996620178222656250e-01 +4.740000000000000000e+05,3.173692675773054361e-04,9.996826648712158203e-01 +4.745000000000000000e+05,9.973927736282348633e-01,2.607167465612292290e-03 +4.750000000000000000e+05,9.889377355575561523e-01,1.106222439557313919e-02 +4.755000000000000000e+05,1.582509430591017008e-04,9.998416900634765625e-01 +4.760000000000000000e+05,4.728042113129049540e-04,9.995272159576416016e-01 +4.765000000000000000e+05,9.248139262199401855e-01,7.518608123064041138e-02 +4.770000000000000000e+05,9.998829364776611328e-01,1.170917312265373766e-04 +4.775000000000000000e+05,9.994889497756958008e-01,5.110113415867090225e-04 +4.780000000000000000e+05,1.097116852179169655e-03,9.989029169082641602e-01 +4.785000000000000000e+05,3.679117362480610609e-04,9.996321201324462891e-01 +4.790000000000000000e+05,6.836442947387695312e-01,3.163557350635528564e-01 +4.795000000000000000e+05,8.206558823585510254e-01,1.793441176414489746e-01 +4.800000000000000000e+05,2.305193990468978882e-02,9.769480228424072266e-01 +4.805000000000000000e+05,9.978796243667602539e-01,2.120391698554158211e-03 +4.810000000000000000e+05,9.466684460639953613e-01,5.333156511187553406e-02 +4.815000000000000000e+05,9.045706689357757568e-02,9.095429182052612305e-01 +4.820000000000000000e+05,4.808668512850999832e-03,9.951913356781005859e-01 +4.825000000000000000e+05,7.470298409461975098e-01,2.529701590538024902e-01 +4.830000000000000000e+05,9.998319149017333984e-01,1.680860441410914063e-04 +4.835000000000000000e+05,9.981990456581115723e-01,1.801027217879891396e-03 +4.840000000000000000e+05,9.979801774024963379e-01,2.019795821979641914e-03 +4.845000000000000000e+05,9.997166991233825684e-01,2.832226164173334837e-04 +4.850000000000000000e+05,9.193766713142395020e-01,8.062329888343811035e-02 +4.855000000000000000e+05,1.184601932764053345e-01,8.815398216247558594e-01 +4.860000000000000000e+05,1.773416437208652496e-02,9.822658300399780273e-01 +4.865000000000000000e+05,9.987351298332214355e-01,1.264935242943465710e-03 +4.870000000000000000e+05,8.883041739463806152e-01,1.116958782076835632e-01 +4.875000000000000000e+05,8.594321012496948242e-01,1.405678987503051758e-01 +4.880000000000000000e+05,9.977699518203735352e-01,2.230108017101883888e-03 +4.885000000000000000e+05,9.958292841911315918e-01,4.170713480561971664e-03 +4.890000000000000000e+05,9.760199189186096191e-01,2.398006245493888855e-02 +4.895000000000000000e+05,1.030018925666809082e-01,8.969981074333190918e-01 +4.900000000000000000e+05,9.999974966049194336e-01,2.481348701621755026e-06 +4.905000000000000000e+05,9.999992847442626953e-01,7.268060926435282454e-07 +4.910000000000000000e+05,9.928938746452331543e-01,7.106063887476921082e-03 +4.915000000000000000e+05,9.964354038238525391e-01,3.564557526260614395e-03 +4.920000000000000000e+05,4.327926635742187500e-01,5.672073960304260254e-01 +4.925000000000000000e+05,2.908074529841542244e-03,9.970918893814086914e-01 +4.930000000000000000e+05,9.947992563247680664e-01,5.200703628361225128e-03 +4.935000000000000000e+05,9.876524209976196289e-01,1.234760973602533340e-02 +4.940000000000000000e+05,1.292804181575775146e-01,8.707195520401000977e-01 +4.945000000000000000e+05,9.709162712097167969e-01,2.908372879028320312e-02 +4.950000000000000000e+05,9.318979382514953613e-01,6.810203939676284790e-02 +4.955000000000000000e+05,2.712220884859561920e-04,9.997287392616271973e-01 +4.960000000000000000e+05,2.145110443234443665e-02,9.785488247871398926e-01 +4.965000000000000000e+05,3.223374858498573303e-02,9.677662849426269531e-01 +4.970000000000000000e+05,7.342828321270644665e-04,9.992657303810119629e-01 +4.975000000000000000e+05,9.776663780212402344e-01,2.233370207250118256e-02 +4.980000000000000000e+05,9.999403953552246094e-01,5.965247328276745975e-05 +4.985000000000000000e+05,9.999401569366455078e-01,5.985681127640418708e-05 +4.990000000000000000e+05,9.986487030982971191e-01,1.351320301182568073e-03 +4.995000000000000000e+05,1.285348273813724518e-02,9.871464967727661133e-01 +5.000000000000000000e+05,9.999698400497436523e-01,3.013704736076761037e-05 +5.005000000000000000e+05,9.999645948410034180e-01,3.546092921169474721e-05 +5.010000000000000000e+05,1.544408053159713745e-01,8.455592393875122070e-01 +5.015000000000000000e+05,4.355895817279815674e-01,5.644104480743408203e-01 +5.020000000000000000e+05,2.866025865077972412e-01,7.133974432945251465e-01 +5.025000000000000000e+05,1.941758580505847931e-02,9.805824160575866699e-01 +5.030000000000000000e+05,9.974421262741088867e-01,2.557915402576327324e-03 +5.035000000000000000e+05,9.999916553497314453e-01,8.379096470889635384e-06 +5.040000000000000000e+05,9.977294802665710449e-01,2.270479453727602959e-03 +5.045000000000000000e+05,1.007722690701484680e-02,9.899228215217590332e-01 +5.050000000000000000e+05,9.862635135650634766e-01,1.373651251196861267e-02 +5.055000000000000000e+05,3.968613222241401672e-03,9.960313439369201660e-01 +5.060000000000000000e+05,9.682349627837538719e-05,9.999032020568847656e-01 +5.065000000000000000e+05,9.559624828398227692e-03,9.904403686523437500e-01 +5.070000000000000000e+05,5.360368266701698303e-02,9.463962912559509277e-01 +5.075000000000000000e+05,1.253148308023810387e-03,9.987468719482421875e-01 +5.080000000000000000e+05,4.198117647320032120e-03,9.958018660545349121e-01 +5.085000000000000000e+05,5.083812400698661804e-02,9.491618275642395020e-01 +5.090000000000000000e+05,9.401184320449829102e-02,9.059881567955017090e-01 +5.095000000000000000e+05,9.018816947937011719e-01,9.811831265687942505e-02 +5.100000000000000000e+05,5.145231261849403381e-03,9.948546886444091797e-01 +5.105000000000000000e+05,2.313679307699203491e-01,7.686321139335632324e-01 +5.110000000000000000e+05,9.977627992630004883e-01,2.237145090475678444e-03 +5.115000000000000000e+05,9.999846220016479492e-01,1.534716830065008253e-05 +5.120000000000000000e+05,9.999992847442626953e-01,7.116249776117911097e-07 +5.125000000000000000e+05,9.877447485923767090e-01,1.225519459694623947e-02 +5.130000000000000000e+05,8.576588630676269531e-01,1.423411518335342407e-01 +5.135000000000000000e+05,4.926418662071228027e-01,5.073580741882324219e-01 +5.140000000000000000e+05,6.136115989647805691e-04,9.993863105773925781e-01 +5.145000000000000000e+05,9.991683959960937500e-01,8.316843886859714985e-04 +5.150000000000000000e+05,9.999961853027343750e-01,3.871168701152782887e-06 +5.155000000000000000e+05,9.950554370880126953e-01,4.944594576954841614e-03 +5.160000000000000000e+05,8.106445074081420898e-01,1.893555074930191040e-01 +5.165000000000000000e+05,8.654049783945083618e-02,9.134594798088073730e-01 +5.170000000000000000e+05,9.974709749221801758e-01,2.529063029214739799e-03 +5.175000000000000000e+05,9.178915061056613922e-03,9.908210635185241699e-01 +5.180000000000000000e+05,9.925824403762817383e-01,7.417608052492141724e-03 +5.185000000000000000e+05,8.321716189384460449e-01,1.678283661603927612e-01 +5.190000000000000000e+05,9.990629553794860840e-01,9.370083571411669254e-04 +5.195000000000000000e+05,9.999964237213134766e-01,3.583204943424789235e-06 +5.200000000000000000e+05,9.940766096115112305e-01,5.923393182456493378e-03 +5.205000000000000000e+05,9.999693632125854492e-01,3.069332888117060065e-05 +5.210000000000000000e+05,9.985476136207580566e-01,1.452405122108757496e-03 +5.215000000000000000e+05,1.208816841244697571e-01,8.791183829307556152e-01 +5.220000000000000000e+05,6.783734541386365891e-03,9.932162165641784668e-01 +5.225000000000000000e+05,8.907739538699388504e-04,9.991092085838317871e-01 +5.230000000000000000e+05,9.803889552131295204e-04,9.990196228027343750e-01 +5.235000000000000000e+05,3.762770676985383034e-03,9.962371587753295898e-01 +5.240000000000000000e+05,9.998761415481567383e-01,1.238770055351778865e-04 +5.245000000000000000e+05,1.000000000000000000e+00,3.402522708029209753e-09 +5.250000000000000000e+05,9.999723434448242188e-01,2.768105150607880205e-05 +5.255000000000000000e+05,2.213733037933707237e-03,9.977862834930419922e-01 +5.260000000000000000e+05,3.661453956738114357e-04,9.996337890625000000e-01 +5.265000000000000000e+05,1.177143491804599762e-02,9.882285594940185547e-01 +5.270000000000000000e+05,3.280977252870798111e-03,9.967190623283386230e-01 +5.275000000000000000e+05,3.917429223656654358e-02,9.608256816864013672e-01 +5.280000000000000000e+05,3.434721846133470535e-03,9.965652823448181152e-01 +5.285000000000000000e+05,2.376115173101425171e-01,7.623885273933410645e-01 +5.290000000000000000e+05,9.658870100975036621e-01,3.411295264959335327e-02 +5.295000000000000000e+05,9.850779771804809570e-01,1.492201443761587143e-02 +5.300000000000000000e+05,8.875037431716918945e-01,1.124962642788887024e-01 +5.305000000000000000e+05,9.993692040443420410e-01,6.308399024419486523e-04 +5.310000000000000000e+05,9.999132156372070312e-01,8.677177538629621267e-05 +5.315000000000000000e+05,9.984702467918395996e-01,1.529716304503381252e-03 +5.320000000000000000e+05,8.534334301948547363e-01,1.465665698051452637e-01 +5.325000000000000000e+05,1.682293415069580078e-01,8.317705988883972168e-01 +5.330000000000000000e+05,1.396597799612209201e-04,9.998602867126464844e-01 +5.335000000000000000e+05,2.641907485667616129e-04,9.997357726097106934e-01 +5.340000000000000000e+05,5.068544996902346611e-04,9.994931221008300781e-01 +5.345000000000000000e+05,3.098754968959838152e-04,9.996901750564575195e-01 +5.350000000000000000e+05,1.470405277359532192e-05,9.999853372573852539e-01 +5.355000000000000000e+05,2.728896834014449269e-05,9.999727010726928711e-01 +5.360000000000000000e+05,5.420322995632886887e-04,9.994580149650573730e-01 +5.365000000000000000e+05,1.444474492018343881e-05,9.999855756759643555e-01 +5.370000000000000000e+05,1.284106075763702393e-02,9.871588945388793945e-01 +5.375000000000000000e+05,7.131284655770286918e-05,9.999287128448486328e-01 +5.380000000000000000e+05,4.432220302987843752e-04,9.995567202568054199e-01 +5.385000000000000000e+05,3.709848679136484861e-04,9.996290206909179688e-01 +5.390000000000000000e+05,7.599494885653257370e-03,9.924005270004272461e-01 +5.395000000000000000e+05,8.504094481468200684e-01,1.495905369520187378e-01 +5.400000000000000000e+05,9.712173342704772949e-01,2.878265269100666046e-02 +5.405000000000000000e+05,8.423482179641723633e-01,1.576518118381500244e-01 +5.410000000000000000e+05,9.873442649841308594e-01,1.265570428222417831e-02 +5.415000000000000000e+05,2.722604840528219938e-04,9.997276663780212402e-01 +5.420000000000000000e+05,1.896148896776139736e-04,9.998103976249694824e-01 +5.425000000000000000e+05,5.027161096222698689e-04,9.994972944259643555e-01 +5.430000000000000000e+05,4.392118155956268311e-01,5.607881546020507812e-01 +5.435000000000000000e+05,3.965277373790740967e-01,6.034722328186035156e-01 +5.440000000000000000e+05,3.638986672740429640e-04,9.996360540390014648e-01 +5.445000000000000000e+05,8.628486990928649902e-01,1.371512711048126221e-01 +5.450000000000000000e+05,9.562169313430786133e-01,4.378312081098556519e-02 +5.455000000000000000e+05,8.843243267619982362e-05,9.999115467071533203e-01 +5.460000000000000000e+05,6.568115204572677612e-04,9.993432164192199707e-01 +5.465000000000000000e+05,6.646537076449021697e-05,9.999334812164306641e-01 +5.470000000000000000e+05,4.740641743410378695e-04,9.995259046554565430e-01 +5.475000000000000000e+05,1.119083844969281927e-05,9.999887943267822266e-01 +5.480000000000000000e+05,6.616308819502592087e-04,9.993383288383483887e-01 +5.485000000000000000e+05,1.320434384979307652e-03,9.986795783042907715e-01 +5.490000000000000000e+05,9.979288578033447266e-01,2.071179915219545364e-03 +5.495000000000000000e+05,9.890418648719787598e-01,1.095816120505332947e-02 +5.500000000000000000e+05,2.477002702653408051e-02,9.752299189567565918e-01 +5.505000000000000000e+05,3.229379281401634216e-02,9.677062630653381348e-01 +5.510000000000000000e+05,1.354307518340647221e-03,9.986457228660583496e-01 +5.515000000000000000e+05,4.664722073357552290e-04,9.995335340499877930e-01 +5.520000000000000000e+05,3.270101547241210938e-02,9.672989249229431152e-01 +5.525000000000000000e+05,9.992062449455261230e-01,7.937899790704250336e-04 +5.530000000000000000e+05,9.962654709815979004e-01,3.734573954716324806e-03 +5.535000000000000000e+05,6.482284516096115112e-03,9.935177564620971680e-01 +5.540000000000000000e+05,1.247348845936357975e-03,9.987525939941406250e-01 +5.545000000000000000e+05,5.854160189628601074e-01,4.145839214324951172e-01 +5.550000000000000000e+05,1.015223562717437744e-01,8.984776735305786133e-01 +5.555000000000000000e+05,2.697295248508453369e-01,7.302704453468322754e-01 +5.560000000000000000e+05,8.369332551956176758e-02,9.163066744804382324e-01 +5.565000000000000000e+05,3.501808969303965569e-03,9.964981079101562500e-01 +5.570000000000000000e+05,4.226579330861568451e-03,9.957733750343322754e-01 +5.575000000000000000e+05,4.401867277920246124e-03,9.955981373786926270e-01 +5.580000000000000000e+05,4.823066294193267822e-03,9.951769113540649414e-01 +5.585000000000000000e+05,9.931502938270568848e-01,6.849746685475111008e-03 +5.590000000000000000e+05,9.691439867019653320e-01,3.085604310035705566e-02 +5.595000000000000000e+05,2.435609139502048492e-02,9.756439328193664551e-01 +5.600000000000000000e+05,1.644343882799148560e-02,9.835565090179443359e-01 +5.605000000000000000e+05,9.200081694871187210e-05,9.999079704284667969e-01 +5.610000000000000000e+05,9.944863915443420410e-01,5.513560026884078979e-03 +5.615000000000000000e+05,9.998249411582946777e-01,1.750832452671602368e-04 +5.620000000000000000e+05,9.843782782554626465e-01,1.562168635427951813e-02 +5.625000000000000000e+05,4.155878559686243534e-04,9.995843768119812012e-01 +5.630000000000000000e+05,1.105425599962472916e-02,9.889457821846008301e-01 +5.635000000000000000e+05,1.387066789902746677e-03,9.986128807067871094e-01 +5.640000000000000000e+05,9.929404258728027344e-01,7.059605792164802551e-03 +5.645000000000000000e+05,9.755747318267822266e-01,2.442533150315284729e-02 +5.650000000000000000e+05,9.139311909675598145e-01,8.606878668069839478e-02 +5.655000000000000000e+05,9.219386265613138676e-04,9.990780353546142578e-01 +5.660000000000000000e+05,9.863672852516174316e-01,1.363275758922100067e-02 +5.665000000000000000e+05,9.853168129920959473e-01,1.468315161764621735e-02 +5.670000000000000000e+05,3.887134138494729996e-03,9.961128234863281250e-01 +5.675000000000000000e+05,5.447618365287780762e-01,4.552381336688995361e-01 +5.680000000000000000e+05,2.152872970327734947e-03,9.978470802307128906e-01 +5.685000000000000000e+05,9.904072880744934082e-01,9.592758491635322571e-03 +5.690000000000000000e+05,9.984209537506103516e-01,1.578999334014952183e-03 +5.695000000000000000e+05,7.525185346603393555e-01,2.474814206361770630e-01 +5.700000000000000000e+05,9.999626874923706055e-01,3.726494833244942129e-05 +5.705000000000000000e+05,9.999974966049194336e-01,2.547943950048647821e-06 +5.710000000000000000e+05,9.999647140502929688e-01,3.527525404933840036e-05 +5.715000000000000000e+05,9.999997615814208984e-01,2.349102743437470053e-07 +5.720000000000000000e+05,9.846128225326538086e-01,1.538716536015272141e-02 +5.725000000000000000e+05,2.368851564824581146e-03,9.976310729980468750e-01 +5.730000000000000000e+05,1.960968220373615623e-04,9.998039603233337402e-01 +5.735000000000000000e+05,2.074312185868620872e-03,9.979256391525268555e-01 +5.740000000000000000e+05,1.421108562499284744e-02,9.857888817787170410e-01 +5.745000000000000000e+05,2.147930208593606949e-03,9.978520870208740234e-01 +5.750000000000000000e+05,9.053351357579231262e-03,9.909466505050659180e-01 +5.755000000000000000e+05,9.218032471835613251e-03,9.907819628715515137e-01 +5.760000000000000000e+05,7.837864686734974384e-04,9.992161989212036133e-01 +5.765000000000000000e+05,8.522645657649263740e-06,9.999915361404418945e-01 +5.770000000000000000e+05,8.991776667244266719e-06,9.999910593032836914e-01 +5.775000000000000000e+05,7.651178748346865177e-05,9.999234676361083984e-01 +5.780000000000000000e+05,4.823556169867515564e-03,9.951764345169067383e-01 +5.785000000000000000e+05,3.116562068462371826e-01,6.883437633514404297e-01 +5.790000000000000000e+05,2.551924809813499451e-02,9.744807481765747070e-01 +5.795000000000000000e+05,7.995649357326328754e-04,9.992004036903381348e-01 +5.800000000000000000e+05,1.596553018316626549e-03,9.984034895896911621e-01 +5.805000000000000000e+05,1.173400887637399137e-04,9.998826980590820312e-01 +5.810000000000000000e+05,6.083513144403696060e-03,9.939164519309997559e-01 +5.815000000000000000e+05,4.506056662648916245e-03,9.954938888549804688e-01 +5.820000000000000000e+05,7.866061059758067131e-04,9.992133378982543945e-01 +5.825000000000000000e+05,3.050438594073057175e-03,9.969496130943298340e-01 +5.830000000000000000e+05,1.787662878632545471e-02,9.821233153343200684e-01 +5.835000000000000000e+05,1.154955825768411160e-03,9.988449811935424805e-01 +5.840000000000000000e+05,3.448518782533938065e-06,9.999965429306030273e-01 +5.845000000000000000e+05,4.443049838300794363e-04,9.995556473731994629e-01 +5.850000000000000000e+05,2.112310176016762853e-04,9.997888207435607910e-01 +5.855000000000000000e+05,7.006400264799594879e-04,9.992992877960205078e-01 +5.860000000000000000e+05,8.931382035370916128e-05,9.999107122421264648e-01 +5.865000000000000000e+05,1.526112464489415288e-04,9.998474121093750000e-01 +5.870000000000000000e+05,4.414147952047642320e-06,9.999955892562866211e-01 +5.875000000000000000e+05,7.449578046798706055e-01,2.550421655178070068e-01 +5.880000000000000000e+05,9.609659314155578613e-01,3.903408721089363098e-02 +5.885000000000000000e+05,7.526258379220962524e-02,9.247373938560485840e-01 +5.890000000000000000e+05,1.357553293928503990e-03,9.986425042152404785e-01 +5.895000000000000000e+05,6.579451728612184525e-04,9.993420243263244629e-01 +5.900000000000000000e+05,3.808766778092831373e-04,9.996191263198852539e-01 +5.905000000000000000e+05,2.679927740246057510e-03,9.973201155662536621e-01 +5.910000000000000000e+05,6.781810661777853966e-04,9.993218183517456055e-01 +5.915000000000000000e+05,2.821605838835239410e-02,9.717839360237121582e-01 +5.920000000000000000e+05,2.561768342275172472e-04,9.997437596321105957e-01 +5.925000000000000000e+05,4.701694124378263950e-04,9.995298385620117188e-01 +5.930000000000000000e+05,1.080311136320233345e-03,9.989197254180908203e-01 +5.935000000000000000e+05,2.400916855549439788e-04,9.997598528861999512e-01 +5.940000000000000000e+05,2.452675253152847290e-04,9.997547268867492676e-01 +5.945000000000000000e+05,1.380260346195427701e-05,9.999861717224121094e-01 +5.950000000000000000e+05,2.267505624331533909e-05,9.999773502349853516e-01 +5.955000000000000000e+05,1.583129924256354570e-04,9.998416900634765625e-01 +5.960000000000000000e+05,4.081143124494701624e-04,9.995918869972229004e-01 +5.965000000000000000e+05,1.131317317485809326e-01,8.868682384490966797e-01 +5.970000000000000000e+05,7.623661309480667114e-02,9.237633347511291504e-01 +5.975000000000000000e+05,7.386151992250233889e-05,9.999260902404785156e-01 +5.980000000000000000e+05,5.125604002387262881e-05,9.999487400054931641e-01 +5.985000000000000000e+05,1.316147972829639912e-03,9.986838698387145996e-01 +5.990000000000000000e+05,6.694569252431392670e-03,9.933053851127624512e-01 +5.995000000000000000e+05,4.666291933972388506e-04,9.995334148406982422e-01 +6.000000000000000000e+05,5.077237263321876526e-03,9.949228167533874512e-01 +6.005000000000000000e+05,6.924982881173491478e-05,9.999307394027709961e-01 +6.010000000000000000e+05,6.845179086667485535e-06,9.999932050704956055e-01 +6.015000000000000000e+05,6.212196312844753265e-04,9.993788003921508789e-01 +6.020000000000000000e+05,2.266565206809900701e-05,9.999773502349853516e-01 +6.025000000000000000e+05,7.727083284407854080e-04,9.992272853851318359e-01 +6.030000000000000000e+05,1.141866319812834263e-03,9.988580942153930664e-01 +6.035000000000000000e+05,5.939229158684611320e-04,9.994060993194580078e-01 +6.040000000000000000e+05,2.222438715398311615e-02,9.777756333351135254e-01 +6.045000000000000000e+05,2.033523742284160107e-05,9.999796152114868164e-01 +6.050000000000000000e+05,8.055323269218206406e-04,9.991944432258605957e-01 +6.055000000000000000e+05,4.133676702622324228e-04,9.995866417884826660e-01 +6.060000000000000000e+05,2.506792545318603516e-03,9.974932670593261719e-01 +6.065000000000000000e+05,1.213810872286558151e-03,9.987861514091491699e-01 +6.070000000000000000e+05,2.505869604647159576e-03,9.974941015243530273e-01 +6.075000000000000000e+05,6.693603354506194592e-04,9.993306398391723633e-01 +6.080000000000000000e+05,1.972201716853305697e-04,9.998027682304382324e-01 +6.085000000000000000e+05,3.022432210855185986e-04,9.996978044509887695e-01 +6.090000000000000000e+05,1.980264787562191486e-04,9.998020529747009277e-01 +6.095000000000000000e+05,1.627314090728759766e-02,9.837268590927124023e-01 +6.100000000000000000e+05,2.556386170908808708e-04,9.997443556785583496e-01 +6.105000000000000000e+05,2.238210290670394897e-04,9.997761845588684082e-01 +6.110000000000000000e+05,1.421342517460288946e-06,9.999985694885253906e-01 +6.115000000000000000e+05,2.028273447649553418e-04,9.997971653938293457e-01 +6.120000000000000000e+05,1.161252293968573213e-04,9.998838901519775391e-01 +6.125000000000000000e+05,5.894977948628365993e-04,9.994105100631713867e-01 +6.130000000000000000e+05,6.991872796788811684e-04,9.993008375167846680e-01 +6.135000000000000000e+05,9.257592319045215845e-05,9.999073743820190430e-01 +6.140000000000000000e+05,1.952795864781364799e-04,9.998047947883605957e-01 +6.145000000000000000e+05,2.024289715336635709e-04,9.997976422309875488e-01 +6.150000000000000000e+05,4.361524479463696480e-04,9.995638728141784668e-01 +6.155000000000000000e+05,3.312353219371289015e-04,9.996687173843383789e-01 +6.160000000000000000e+05,4.201109986752271652e-04,9.995798468589782715e-01 +6.165000000000000000e+05,1.447671093046665192e-02,9.855233430862426758e-01 +6.170000000000000000e+05,7.971958839334547520e-04,9.992027878761291504e-01 +6.175000000000000000e+05,6.886898336233571172e-05,9.999310970306396484e-01 +6.180000000000000000e+05,1.092983176931738853e-03,9.989069700241088867e-01 +6.185000000000000000e+05,9.627114050090312958e-04,9.990372657775878906e-01 +6.190000000000000000e+05,2.000789046287536621e-01,7.999210953712463379e-01 +6.195000000000000000e+05,8.039607200771570206e-05,9.999196529388427734e-01 +6.200000000000000000e+05,2.360813232371583581e-04,9.997639060020446777e-01 +6.205000000000000000e+05,5.279662946122698486e-05,9.999471902847290039e-01 +6.210000000000000000e+05,5.389325669966638088e-04,9.994611144065856934e-01 +6.215000000000000000e+05,1.124508271459490061e-04,9.998875856399536133e-01 +6.220000000000000000e+05,1.486654364271089435e-04,9.998513460159301758e-01 +6.225000000000000000e+05,2.278110041515901685e-04,9.997722506523132324e-01 +6.230000000000000000e+05,4.502784577198326588e-05,9.999549388885498047e-01 +6.235000000000000000e+05,3.238888166379183531e-04,9.996761083602905273e-01 +6.240000000000000000e+05,2.841400215402245522e-03,9.971586465835571289e-01 +6.245000000000000000e+05,1.314017339609563351e-03,9.986860156059265137e-01 +6.250000000000000000e+05,2.747551538050174713e-03,9.972524046897888184e-01 +6.255000000000000000e+05,6.159014301374554634e-04,9.993840456008911133e-01 +6.260000000000000000e+05,6.172860594233497977e-05,9.999382495880126953e-01 +6.265000000000000000e+05,1.830432302085682750e-04,9.998169541358947754e-01 +6.270000000000000000e+05,8.180966251529753208e-04,9.991819262504577637e-01 +6.275000000000000000e+05,2.698432072065770626e-04,9.997301697731018066e-01 +6.280000000000000000e+05,1.034328888636082411e-04,9.998965263366699219e-01 +6.285000000000000000e+05,1.235252566402778029e-04,9.998764991760253906e-01 +6.290000000000000000e+05,9.512885590083897114e-04,9.990486502647399902e-01 +6.295000000000000000e+05,7.537445635534822941e-04,9.992462396621704102e-01 +6.300000000000000000e+05,2.183827891713008285e-04,9.997816681861877441e-01 +6.305000000000000000e+05,3.897831193171441555e-04,9.996102452278137207e-01 +6.310000000000000000e+05,2.632649266161024570e-04,9.997367262840270996e-01 +6.315000000000000000e+05,2.498277574777603149e-01,7.501722574234008789e-01 +6.320000000000000000e+05,4.903686791658401489e-02,9.509630799293518066e-01 +6.325000000000000000e+05,2.015865029534325004e-04,9.997984766960144043e-01 +6.330000000000000000e+05,2.321236068382859230e-03,9.976788163185119629e-01 +6.335000000000000000e+05,9.868765482679009438e-04,9.990130662918090820e-01 +6.340000000000000000e+05,1.135216371039859951e-04,9.998865127563476562e-01 +6.345000000000000000e+05,1.255704660252376925e-06,9.999986886978149414e-01 +6.350000000000000000e+05,1.307262922637164593e-03,9.986928105354309082e-01 +6.355000000000000000e+05,3.117502274108119309e-05,9.999687671661376953e-01 +6.360000000000000000e+05,1.102677633753046393e-04,9.998897314071655273e-01 +6.365000000000000000e+05,9.773422032594680786e-03,9.902265071868896484e-01 +6.370000000000000000e+05,1.025345921516418457e-02,9.897465705871582031e-01 +6.375000000000000000e+05,1.442336360923945904e-03,9.985576272010803223e-01 +6.380000000000000000e+05,2.042818377958610654e-04,9.997957348823547363e-01 +6.385000000000000000e+05,2.169753715861588717e-04,9.997829794883728027e-01 +6.390000000000000000e+05,2.700363053008913994e-03,9.972996115684509277e-01 +6.395000000000000000e+05,1.068696454167366028e-01,8.931303620338439941e-01 +6.400000000000000000e+05,4.035278409719467163e-02,9.596471786499023438e-01 +6.405000000000000000e+05,5.255641008261591196e-05,9.999474287033081055e-01 +6.410000000000000000e+05,2.227897755801677704e-03,9.977720379829406738e-01 +6.415000000000000000e+05,6.929807132109999657e-04,9.993070363998413086e-01 +6.420000000000000000e+05,1.216327678412199020e-02,9.878367781639099121e-01 +6.425000000000000000e+05,1.351299695670604706e-03,9.986487030982971191e-01 +6.430000000000000000e+05,2.325781679246574640e-04,9.997674822807312012e-01 +6.435000000000000000e+05,8.813708089292049408e-03,9.911863207817077637e-01 +6.440000000000000000e+05,3.328985720872879028e-02,9.667101502418518066e-01 +6.445000000000000000e+05,3.264043480157852173e-04,9.996736049652099609e-01 +6.450000000000000000e+05,4.355546552687883377e-03,9.956444501876831055e-01 +6.455000000000000000e+05,1.606901059858500957e-04,9.998393058776855469e-01 +6.460000000000000000e+05,1.024071797728538513e-01,8.975927829742431641e-01 +6.465000000000000000e+05,9.626470506191253662e-02,9.037352800369262695e-01 +6.470000000000000000e+05,2.536918036639690399e-04,9.997462630271911621e-01 +6.475000000000000000e+05,1.203126812470145524e-04,9.998797178268432617e-01 +6.480000000000000000e+05,2.612983109429478645e-03,9.973869919776916504e-01 +6.485000000000000000e+05,1.142749376595020294e-02,9.885724782943725586e-01 +6.490000000000000000e+05,9.970294777303934097e-04,9.990029931068420410e-01 +6.495000000000000000e+05,1.642832066863775253e-03,9.983571171760559082e-01 +6.500000000000000000e+05,6.852838851045817137e-05,9.999314546585083008e-01 +6.505000000000000000e+05,5.901334225200116634e-05,9.999409914016723633e-01 +6.510000000000000000e+05,6.747203413397073746e-03,9.932528138160705566e-01 +6.515000000000000000e+05,1.700991415418684483e-03,9.982990622520446777e-01 +6.520000000000000000e+05,1.918442896567285061e-03,9.980815649032592773e-01 +6.525000000000000000e+05,6.217776332050561905e-03,9.937822222709655762e-01 +6.530000000000000000e+05,1.033973647281527519e-03,9.989659786224365234e-01 +6.535000000000000000e+05,4.055958124808967113e-04,9.995943903923034668e-01 +6.540000000000000000e+05,1.167355570942163467e-03,9.988326430320739746e-01 +6.545000000000000000e+05,9.897391311824321747e-03,9.901026487350463867e-01 +6.550000000000000000e+05,1.458253804594278336e-02,9.854174852371215820e-01 +6.555000000000000000e+05,3.579678479582071304e-03,9.964203834533691406e-01 +6.560000000000000000e+05,5.692658305633813143e-05,9.999430179595947266e-01 +6.565000000000000000e+05,1.775137381628155708e-03,9.982249140739440918e-01 +6.570000000000000000e+05,1.690684002824127674e-03,9.983092546463012695e-01 +6.575000000000000000e+05,7.840183097869157791e-04,9.992159605026245117e-01 +6.580000000000000000e+05,4.885012003796873614e-06,9.999951124191284180e-01 +6.585000000000000000e+05,1.859069988131523132e-02,9.814093112945556641e-01 +6.590000000000000000e+05,9.562557097524404526e-04,9.990437626838684082e-01 +6.595000000000000000e+05,8.730940520763397217e-03,9.912689924240112305e-01 +6.600000000000000000e+05,4.734256363008171320e-04,9.995266199111938477e-01 +6.605000000000000000e+05,2.263627573847770691e-02,9.773637056350708008e-01 +6.610000000000000000e+05,9.993048906326293945e-01,6.950740935280919075e-04 +6.615000000000000000e+05,9.991408586502075195e-01,8.591482182964682579e-04 +6.620000000000000000e+05,9.630151391029357910e-01,3.698485344648361206e-02 +6.625000000000000000e+05,4.809084348380565643e-03,9.951909780502319336e-01 +6.630000000000000000e+05,9.913041591644287109e-01,8.695808239281177521e-03 +6.635000000000000000e+05,9.986206293106079102e-01,1.379393390379846096e-03 +6.640000000000000000e+05,6.353929638862609863e-01,3.646070361137390137e-01 +6.645000000000000000e+05,6.464538723230361938e-02,9.353545904159545898e-01 +6.650000000000000000e+05,4.093083739280700684e-02,9.590691924095153809e-01 +6.655000000000000000e+05,9.737521409988403320e-01,2.624781616032123566e-02 +6.660000000000000000e+05,6.769832968711853027e-01,3.230167031288146973e-01 +6.665000000000000000e+05,9.988526105880737305e-01,1.147360075265169144e-03 +6.670000000000000000e+05,9.993909597396850586e-01,6.090108654461801052e-04 +6.675000000000000000e+05,9.976601600646972656e-01,2.339794067665934563e-03 +6.680000000000000000e+05,3.468113839626312256e-01,6.531885862350463867e-01 +6.685000000000000000e+05,4.327523112297058105e-01,5.672476887702941895e-01 +6.690000000000000000e+05,7.813374395482242107e-04,9.992187023162841797e-01 +6.695000000000000000e+05,5.192323587834835052e-04,9.994807839393615723e-01 +6.700000000000000000e+05,3.952757716178894043e-01,6.047242879867553711e-01 +6.705000000000000000e+05,6.964887492358684540e-03,9.930351972579956055e-01 +6.710000000000000000e+05,4.404389474075287580e-04,9.995595812797546387e-01 +6.715000000000000000e+05,9.585720486938953400e-03,9.904143214225769043e-01 +6.720000000000000000e+05,2.369932532310485840e-01,7.630068063735961914e-01 +6.725000000000000000e+05,2.689971355721354485e-03,9.973100423812866211e-01 +6.730000000000000000e+05,9.306654101237654686e-04,9.990693926811218262e-01 +6.735000000000000000e+05,4.770644754171371460e-02,9.522935152053833008e-01 +6.740000000000000000e+05,9.978524446487426758e-01,2.147581661120057106e-03 +6.745000000000000000e+05,6.918871998786926270e-01,3.081128001213073730e-01 +6.750000000000000000e+05,9.383943080902099609e-01,6.160569936037063599e-02 +6.755000000000000000e+05,9.989241957664489746e-01,1.075793872587382793e-03 +6.760000000000000000e+05,9.999978542327880859e-01,2.102763346556457691e-06 +6.765000000000000000e+05,9.996135830879211426e-01,3.864410391543060541e-04 +6.770000000000000000e+05,8.065032958984375000e-02,9.193496704101562500e-01 +6.775000000000000000e+05,2.209247089922428131e-02,9.779075384140014648e-01 +6.780000000000000000e+05,1.699104905128479004e-02,9.830089211463928223e-01 +6.785000000000000000e+05,9.720202684402465820e-01,2.797974273562431335e-02 +6.790000000000000000e+05,9.567800164222717285e-01,4.321996495127677917e-02 +6.795000000000000000e+05,4.446796476840972900e-01,5.553203225135803223e-01 +6.800000000000000000e+05,2.731832861900329590e-02,9.726816415786743164e-01 +6.805000000000000000e+05,9.147412143647670746e-03,9.908526539802551270e-01 +6.810000000000000000e+05,1.082324539311230183e-03,9.989176988601684570e-01 +6.815000000000000000e+05,9.995535016059875488e-01,4.464938829187303782e-04 +6.820000000000000000e+05,1.000000000000000000e+00,2.751257177635579865e-08 +6.825000000000000000e+05,9.999556541442871094e-01,4.434381844475865364e-05 +6.830000000000000000e+05,2.844902575016021729e-01,7.155097126960754395e-01 +6.835000000000000000e+05,8.191840606741607189e-04,9.991808533668518066e-01 +6.840000000000000000e+05,5.741587756347144023e-06,9.999942779541015625e-01 +6.845000000000000000e+05,5.891448745387606323e-05,9.999411106109619141e-01 +6.850000000000000000e+05,3.379122018814086914e-01,6.620877981185913086e-01 +6.855000000000000000e+05,7.225472927093505859e-01,2.774527370929718018e-01 +6.860000000000000000e+05,1.214984357357025146e-01,8.785015940666198730e-01 +6.865000000000000000e+05,2.154339337721467018e-03,9.978456497192382812e-01 +6.870000000000000000e+05,2.583301393315196037e-03,9.974167346954345703e-01 +6.875000000000000000e+05,1.223162747919559479e-03,9.987768530845642090e-01 +6.880000000000000000e+05,1.483014086261391640e-03,9.985169768333435059e-01 +6.885000000000000000e+05,1.247414294630289078e-02,9.875259399414062500e-01 +6.890000000000000000e+05,4.894713638350367546e-04,9.995105266571044922e-01 +6.895000000000000000e+05,5.527504254132509232e-04,9.994472861289978027e-01 +6.900000000000000000e+05,4.420226905494928360e-04,9.995579123497009277e-01 +6.905000000000000000e+05,4.597684528562240303e-05,9.999539852142333984e-01 +6.910000000000000000e+05,4.558787622954696417e-04,9.995440840721130371e-01 +6.915000000000000000e+05,6.348614115267992020e-04,9.993651509284973145e-01 +6.920000000000000000e+05,7.295478135347366333e-04,9.992704987525939941e-01 +6.925000000000000000e+05,1.574512716615572572e-04,9.998425245285034180e-01 +6.930000000000000000e+05,6.261718226596713066e-04,9.993738532066345215e-01 +6.935000000000000000e+05,1.283571618841961026e-04,9.998716115951538086e-01 +6.940000000000000000e+05,4.355676646810024977e-04,9.995644688606262207e-01 +6.945000000000000000e+05,9.946868419647216797e-01,5.313198547810316086e-03 +6.950000000000000000e+05,9.999895095825195312e-01,1.048708236339734867e-05 +6.955000000000000000e+05,9.987726807594299316e-01,1.227243687026202679e-03 +6.960000000000000000e+05,3.768633306026458740e-01,6.231366991996765137e-01 +6.965000000000000000e+05,9.982277750968933105e-01,1.772187300957739353e-03 +6.970000000000000000e+05,9.830442070960998535e-01,1.695578172802925110e-02 +6.975000000000000000e+05,7.290251255035400391e-01,2.709748148918151855e-01 +6.980000000000000000e+05,8.525780285708606243e-04,9.991474151611328125e-01 +6.985000000000000000e+05,6.639001076109707355e-04,9.993360638618469238e-01 +6.990000000000000000e+05,2.530182013288140297e-03,9.974697828292846680e-01 +6.995000000000000000e+05,9.998247027397155762e-01,1.752764801494777203e-04 +7.000000000000000000e+05,9.997829794883728027e-01,2.170134393963962793e-04 +7.005000000000000000e+05,9.707121253013610840e-01,2.928783372044563293e-02 +7.010000000000000000e+05,1.289960928261280060e-03,9.987100362777709961e-01 +7.015000000000000000e+05,2.545365532569121569e-05,9.999744892120361328e-01 +7.020000000000000000e+05,9.793657955015078187e-05,9.999020099639892578e-01 +7.025000000000000000e+05,5.691436305642127991e-02,9.430856108665466309e-01 +7.030000000000000000e+05,5.461041256785392761e-02,9.453895688056945801e-01 +7.035000000000000000e+05,9.071321692317724228e-04,9.990929365158081055e-01 +7.040000000000000000e+05,3.978048916906118393e-03,9.960219264030456543e-01 +7.045000000000000000e+05,1.943076401948928833e-02,9.805692434310913086e-01 +7.050000000000000000e+05,1.033681182889267802e-04,9.998966455459594727e-01 +7.055000000000000000e+05,9.575457079336047173e-04,9.990424513816833496e-01 +7.060000000000000000e+05,6.206905469298362732e-03,9.937931299209594727e-01 +7.065000000000000000e+05,5.790477152913808823e-04,9.994210004806518555e-01 +7.070000000000000000e+05,5.065231816843152046e-04,9.994934797286987305e-01 +7.075000000000000000e+05,2.711603010538965464e-04,9.997288584709167480e-01 +7.080000000000000000e+05,2.880213105527218431e-05,9.999711513519287109e-01 +7.085000000000000000e+05,2.941113198176026344e-03,9.970588684082031250e-01 +7.090000000000000000e+05,2.823579357936978340e-03,9.971764087677001953e-01 +7.095000000000000000e+05,9.960263967514038086e-01,3.973611630499362946e-03 +7.100000000000000000e+05,9.979819059371948242e-01,2.018120838329195976e-03 +7.105000000000000000e+05,1.473795622587203979e-03,9.985262155532836914e-01 +7.110000000000000000e+05,9.055803418159484863e-01,9.441968798637390137e-02 +7.115000000000000000e+05,9.998301267623901367e-01,1.698796986602246761e-04 +7.120000000000000000e+05,9.071497917175292969e-01,9.285018593072891235e-02 +7.125000000000000000e+05,1.687966473400592804e-02,9.831203222274780273e-01 +7.130000000000000000e+05,1.575920172035694122e-02,9.842408299446105957e-01 +7.135000000000000000e+05,3.247440617997199297e-04,9.996752738952636719e-01 +7.140000000000000000e+05,4.098963108845055103e-04,9.995900988578796387e-01 +7.145000000000000000e+05,8.155128452926874161e-04,9.991844296455383301e-01 +7.150000000000000000e+05,9.765432476997375488e-01,2.345675975084304810e-02 +7.155000000000000000e+05,9.735311269760131836e-01,2.646886371076107025e-02 +7.160000000000000000e+05,3.242249786853790283e-01,6.757749915122985840e-01 +7.165000000000000000e+05,9.890180826187133789e-01,1.098197046667337418e-02 +7.170000000000000000e+05,4.200755953788757324e-01,5.799244046211242676e-01 +7.175000000000000000e+05,2.456898428499698639e-02,9.754309654235839844e-01 +7.180000000000000000e+05,9.957928657531738281e-01,4.207077436149120331e-03 +7.185000000000000000e+05,9.999657869338989258e-01,3.422423833399079740e-05 +7.190000000000000000e+05,4.893600344657897949e-01,5.106400251388549805e-01 +7.195000000000000000e+05,2.954589726869016886e-04,9.997045397758483887e-01 +7.200000000000000000e+05,6.998313963413238525e-02,9.300168752670288086e-01 +7.205000000000000000e+05,9.536085128784179688e-01,4.639146104454994202e-02 +7.210000000000000000e+05,4.405703395605087280e-02,9.559429883956909180e-01 +7.215000000000000000e+05,2.116451505571603775e-03,9.978834986686706543e-01 +7.220000000000000000e+05,1.273652771487832069e-03,9.987263083457946777e-01 +7.225000000000000000e+05,4.316008707974106073e-04,9.995684027671813965e-01 +7.230000000000000000e+05,8.074801880866289139e-04,9.991925358772277832e-01 +7.235000000000000000e+05,4.575636088848114014e-01,5.424364209175109863e-01 +7.240000000000000000e+05,9.999998807907104492e-01,1.363689960953706759e-07 +7.245000000000000000e+05,1.000000000000000000e+00,2.095214002295620048e-09 +7.250000000000000000e+05,9.948799610137939453e-01,5.120045505464076996e-03 +7.255000000000000000e+05,9.675528854131698608e-03,9.903244972229003906e-01 +7.260000000000000000e+05,9.939839243888854980e-01,6.016066297888755798e-03 +7.265000000000000000e+05,9.780615568161010742e-01,2.193848229944705963e-02 +7.270000000000000000e+05,4.339556617196649313e-04,9.995660185813903809e-01 +7.275000000000000000e+05,2.396353520452976227e-03,9.976037144660949707e-01 +7.280000000000000000e+05,4.303586669266223907e-03,9.956964254379272461e-01 +7.285000000000000000e+05,6.630301475524902344e-01,3.369699120521545410e-01 +7.290000000000000000e+05,9.929357171058654785e-01,7.064226083457469940e-03 +7.295000000000000000e+05,9.802787303924560547e-01,1.972124911844730377e-02 +7.300000000000000000e+05,8.523876667022705078e-01,1.476122736930847168e-01 +7.305000000000000000e+05,5.724359303712844849e-03,9.942756295204162598e-01 +7.310000000000000000e+05,9.304774999618530273e-01,6.952257454395294189e-02 +7.315000000000000000e+05,9.535906314849853516e-01,4.640939831733703613e-02 +7.320000000000000000e+05,9.782007932662963867e-01,2.179921418428421021e-02 +7.325000000000000000e+05,9.228014945983886719e-01,7.719854265451431274e-02 +7.330000000000000000e+05,6.912988424301147461e-01,3.087011873722076416e-01 +7.335000000000000000e+05,9.370020707137882710e-04,9.990629553794860840e-01 +7.340000000000000000e+05,5.284074693918228149e-03,9.947158694267272949e-01 +7.345000000000000000e+05,9.716914064483717084e-05,9.999028444290161133e-01 +7.350000000000000000e+05,9.871696233749389648e-01,1.283034216612577438e-02 +7.355000000000000000e+05,9.964092373847961426e-01,3.590801730751991272e-03 +7.360000000000000000e+05,7.595012187957763672e-01,2.404988259077072144e-01 +7.365000000000000000e+05,3.429957723710685968e-04,9.996570348739624023e-01 +7.370000000000000000e+05,1.637136447243392467e-04,9.998362064361572266e-01 +7.375000000000000000e+05,9.916478395462036133e-02,9.008352160453796387e-01 +7.380000000000000000e+05,9.903048276901245117e-01,9.695089422166347504e-03 +7.385000000000000000e+05,9.766601324081420898e-01,2.333987690508365631e-02 +7.390000000000000000e+05,8.948951959609985352e-01,1.051047965884208679e-01 +7.395000000000000000e+05,8.623391985893249512e-01,1.376608163118362427e-01 +7.400000000000000000e+05,9.725801944732666016e-01,2.741977944970130920e-02 +7.405000000000000000e+05,9.982490539550781250e-01,1.750996685586869717e-03 +7.410000000000000000e+05,9.479482173919677734e-01,5.205182731151580811e-02 +7.415000000000000000e+05,1.677613181527704000e-04,9.998321533203125000e-01 +7.420000000000000000e+05,2.476037666201591492e-02,9.752395749092102051e-01 +7.425000000000000000e+05,7.566502690315246582e-02,9.243350028991699219e-01 +7.430000000000000000e+05,4.972738679498434067e-05,9.999502897262573242e-01 +7.435000000000000000e+05,1.302743330597877502e-04,9.998697042465209961e-01 +7.440000000000000000e+05,1.721485459711402655e-04,9.998278617858886719e-01 +7.445000000000000000e+05,1.104532784665934741e-04,9.998894929885864258e-01 +7.450000000000000000e+05,1.801028556656092405e-04,9.998199343681335449e-01 +7.455000000000000000e+05,1.344944350421428680e-03,9.986550807952880859e-01 +7.460000000000000000e+05,3.387999022379517555e-03,9.966120123863220215e-01 +7.465000000000000000e+05,4.250181913375854492e-01,5.749817490577697754e-01 +7.470000000000000000e+05,7.924222350120544434e-01,2.075777649879455566e-01 +7.475000000000000000e+05,8.596427440643310547e-01,1.403572559356689453e-01 +7.480000000000000000e+05,9.682108759880065918e-01,3.178909793496131897e-02 +7.485000000000000000e+05,8.942428827285766602e-01,1.057571545243263245e-01 +7.490000000000000000e+05,9.985992312431335449e-01,1.400777022354304790e-03 +7.495000000000000000e+05,9.119814634323120117e-01,8.801851421594619751e-02 +7.500000000000000000e+05,1.346167549490928650e-02,9.865382909774780273e-01 +7.505000000000000000e+05,9.835211038589477539e-01,1.647884398698806763e-02 +7.510000000000000000e+05,9.959732890129089355e-01,4.026670940220355988e-03 +7.515000000000000000e+05,2.284026741981506348e-01,7.715973258018493652e-01 +7.520000000000000000e+05,8.733345270156860352e-01,1.266654133796691895e-01 +7.525000000000000000e+05,2.104863233398646116e-04,9.997895359992980957e-01 +7.530000000000000000e+05,4.673117771744728088e-03,9.953268766403198242e-01 +7.535000000000000000e+05,7.553661125712096691e-04,9.992446899414062500e-01 +7.540000000000000000e+05,2.553127123974263668e-04,9.997447133064270020e-01 +7.545000000000000000e+05,2.409822940826416016e-01,7.590177655220031738e-01 +7.550000000000000000e+05,2.446904079988598824e-03,9.975531697273254395e-01 +7.555000000000000000e+05,4.314501769840717316e-03,9.956855773925781250e-01 +7.560000000000000000e+05,1.520876120775938034e-02,9.847912192344665527e-01 +7.565000000000000000e+05,6.400450947694480419e-04,9.993599057197570801e-01 +7.570000000000000000e+05,8.367820382118225098e-01,1.632179170846939087e-01 +7.575000000000000000e+05,8.530686497688293457e-01,1.469313353300094604e-01 +7.580000000000000000e+05,1.073991064913570881e-03,9.989259839057922363e-01 +7.585000000000000000e+05,9.586270607542246580e-06,9.999904632568359375e-01 +7.590000000000000000e+05,1.722573651932179928e-03,9.982774257659912109e-01 +7.595000000000000000e+05,3.424334106966853142e-03,9.965756535530090332e-01 +7.600000000000000000e+05,6.992915295995771885e-04,9.993007183074951172e-01 +7.605000000000000000e+05,9.815816879272460938e-01,1.841832697391510010e-02 +7.610000000000000000e+05,9.902310967445373535e-01,9.768900461494922638e-03 +7.615000000000000000e+05,9.922702312469482422e-01,7.729753851890563965e-03 +7.620000000000000000e+05,9.577537775039672852e-01,4.224626719951629639e-02 +7.625000000000000000e+05,7.275474071502685547e-01,2.724525630474090576e-01 +7.630000000000000000e+05,1.361246854066848755e-01,8.638753294944763184e-01 +7.635000000000000000e+05,1.383539056405425072e-04,9.998615980148315430e-01 +7.640000000000000000e+05,5.015735223423689604e-05,9.999498128890991211e-01 +7.645000000000000000e+05,1.921034068800508976e-03,9.980789422988891602e-01 +7.650000000000000000e+05,5.820112419314682484e-04,9.994180202484130859e-01 +7.655000000000000000e+05,1.153068369603715837e-04,9.998847246170043945e-01 +7.660000000000000000e+05,3.581957891583442688e-03,9.964179992675781250e-01 +7.665000000000000000e+05,4.974826704710721970e-03,9.950250983238220215e-01 +7.670000000000000000e+05,9.933578372001647949e-01,6.642210762947797775e-03 +7.675000000000000000e+05,9.967507123947143555e-01,3.249260596930980682e-03 +7.680000000000000000e+05,9.593943953514099121e-01,4.060557484626770020e-02 +7.685000000000000000e+05,2.583442488685250282e-03,9.974164962768554688e-01 +7.690000000000000000e+05,1.126319053582847118e-03,9.988736510276794434e-01 +7.695000000000000000e+05,5.302634555846452713e-03,9.946973323822021484e-01 +7.700000000000000000e+05,1.134781166911125183e-03,9.988652467727661133e-01 +7.705000000000000000e+05,3.087861114181578159e-04,9.996912479400634766e-01 +7.710000000000000000e+05,4.936413373798131943e-03,9.950636029243469238e-01 +7.715000000000000000e+05,9.494499564170837402e-01,5.055002123117446899e-02 +7.720000000000000000e+05,9.781606793403625488e-01,2.183928899466991425e-02 +7.725000000000000000e+05,9.940041899681091309e-01,5.995832383632659912e-03 +7.730000000000000000e+05,3.535045087337493896e-01,6.464954614639282227e-01 +7.735000000000000000e+05,3.291406482458114624e-02,9.670859575271606445e-01 +7.740000000000000000e+05,3.572518180590122938e-04,9.996427297592163086e-01 +7.745000000000000000e+05,2.671262773219496012e-04,9.997329115867614746e-01 +7.750000000000000000e+05,2.549760683905333281e-04,9.997450709342956543e-01 +7.755000000000000000e+05,2.446542493999004364e-02,9.755345582962036133e-01 +7.760000000000000000e+05,2.874657884240150452e-02,9.712533950805664062e-01 +7.765000000000000000e+05,8.698762976564466953e-04,9.991301894187927246e-01 +7.770000000000000000e+05,4.829923156648874283e-03,9.951700568199157715e-01 +7.775000000000000000e+05,6.176737952046096325e-04,9.993822574615478516e-01 +7.780000000000000000e+05,6.177483871579170227e-02,9.382251501083374023e-01 +7.785000000000000000e+05,9.981952309608459473e-01,1.804765081033110619e-03 +7.790000000000000000e+05,9.991973042488098145e-01,8.027067524380981922e-04 +7.795000000000000000e+05,9.784966707229614258e-01,2.150336652994155884e-02 +7.800000000000000000e+05,1.308962237089872360e-03,9.986910223960876465e-01 +7.805000000000000000e+05,9.998751878738403320e-01,1.247503823833540082e-04 +7.810000000000000000e+05,1.000000000000000000e+00,4.890998184237105306e-08 +7.815000000000000000e+05,9.999665021896362305e-01,3.346191806485876441e-05 +7.820000000000000000e+05,1.166636502603068948e-04,9.998832941055297852e-01 +7.825000000000000000e+05,2.135600661858916283e-04,9.997864365577697754e-01 +7.830000000000000000e+05,5.219419836066663265e-05,9.999477863311767578e-01 +7.835000000000000000e+05,1.078608096577227116e-03,9.989213943481445312e-01 +7.840000000000000000e+05,7.274063591466983780e-06,9.999927282333374023e-01 +7.845000000000000000e+05,1.749370421748608351e-04,9.998250603675842285e-01 +7.850000000000000000e+05,3.277143090963363647e-02,9.672285914421081543e-01 +7.855000000000000000e+05,9.985717535018920898e-01,1.428183750249445438e-03 +7.860000000000000000e+05,9.983896017074584961e-01,1.610417151823639870e-03 +7.865000000000000000e+05,9.257189631462097168e-01,7.428102940320968628e-02 +7.870000000000000000e+05,9.999197721481323242e-01,8.022313704714179039e-05 +7.875000000000000000e+05,9.988309741020202637e-01,1.169001217931509018e-03 +7.880000000000000000e+05,2.923470735549926758e-01,7.076528668403625488e-01 +7.885000000000000000e+05,9.987548589706420898e-01,1.245145802386105061e-03 +7.890000000000000000e+05,9.885107278823852539e-01,1.148922275751829147e-02 +7.895000000000000000e+05,9.987022876739501953e-01,1.297721872106194496e-03 +7.900000000000000000e+05,9.879724383354187012e-01,1.202763151377439499e-02 +7.905000000000000000e+05,9.838109612464904785e-01,1.618902012705802917e-02 +7.910000000000000000e+05,9.937841892242431641e-01,6.215817295014858246e-03 +7.915000000000000000e+05,9.941952824592590332e-01,5.804744083434343338e-03 +7.920000000000000000e+05,1.544881425797939301e-02,9.845511317253112793e-01 +7.925000000000000000e+05,8.073337994574103504e-06,9.999918937683105469e-01 +7.930000000000000000e+05,6.657388992607593536e-03,9.933425784111022949e-01 +7.935000000000000000e+05,9.412065744400024414e-01,5.879338458180427551e-02 +7.940000000000000000e+05,9.732262492179870605e-01,2.677368000149726868e-02 +7.945000000000000000e+05,9.946856498718261719e-01,5.314314737915992737e-03 +7.950000000000000000e+05,9.685245156288146973e-01,3.147540986537933350e-02 +7.955000000000000000e+05,4.289637207984924316e-01,5.710363388061523438e-01 +7.960000000000000000e+05,1.129794036387465894e-04,9.998869895935058594e-01 +7.965000000000000000e+05,8.920205291360616684e-04,9.991080164909362793e-01 +7.970000000000000000e+05,9.347380995750427246e-01,6.526190787553787231e-02 +7.975000000000000000e+05,9.999995231628417969e-01,4.746466402139049023e-07 +7.980000000000000000e+05,9.999955892562866211e-01,4.398670625960221514e-06 +7.985000000000000000e+05,9.998919963836669922e-01,1.079378198483027518e-04 +7.990000000000000000e+05,9.987967014312744141e-01,1.203317428007721901e-03 +7.995000000000000000e+05,9.963176250457763672e-01,3.682309994474053383e-03 +8.000000000000000000e+05,9.838988184928894043e-01,1.610125973820686340e-02 +8.005000000000000000e+05,9.495295286178588867e-01,5.047053098678588867e-02 +8.010000000000000000e+05,9.692329168319702148e-01,3.076712973415851593e-02 +8.015000000000000000e+05,6.129258871078491211e-01,3.870740830898284912e-01 +8.020000000000000000e+05,9.886843562126159668e-01,1.131567452102899551e-02 +8.025000000000000000e+05,9.951471686363220215e-01,4.852865356951951981e-03 +8.030000000000000000e+05,1.557528879493474960e-02,9.844247102737426758e-01 +8.035000000000000000e+05,1.151329488493502140e-03,9.988486766815185547e-01 +8.040000000000000000e+05,1.869332918431609869e-04,9.998131394386291504e-01 +8.045000000000000000e+05,4.893057048320770264e-03,9.951069355010986328e-01 +8.050000000000000000e+05,1.004757476039230824e-03,9.989952445030212402e-01 +8.055000000000000000e+05,7.571932655991986394e-05,9.999243021011352539e-01 +8.060000000000000000e+05,8.184969192370772362e-04,9.991814494132995605e-01 +8.065000000000000000e+05,5.741302371025085449e-01,4.258697330951690674e-01 +8.070000000000000000e+05,9.888440370559692383e-01,1.115601137280464172e-02 +8.075000000000000000e+05,9.132331609725952148e-01,8.676683902740478516e-02 +8.080000000000000000e+05,7.085374090820550919e-05,9.999291896820068359e-01 +8.085000000000000000e+05,8.755182352615520358e-05,9.999123811721801758e-01 +8.090000000000000000e+05,2.301862696185708046e-03,9.976981282234191895e-01 +8.095000000000000000e+05,1.060600113123655319e-02,9.893940091133117676e-01 +8.100000000000000000e+05,1.420647837221622467e-03,9.985793828964233398e-01 +8.105000000000000000e+05,1.626771518203895539e-05,9.999837875366210938e-01 +8.110000000000000000e+05,1.422286877641454339e-04,9.998577833175659180e-01 +8.115000000000000000e+05,1.827112282626330853e-03,9.981728792190551758e-01 +8.120000000000000000e+05,3.617248730733990669e-03,9.963827133178710938e-01 +8.125000000000000000e+05,8.758388757705688477e-01,1.241610646247863770e-01 +8.130000000000000000e+05,9.999908208847045898e-01,9.121657967625651509e-06 +8.135000000000000000e+05,9.999510049819946289e-01,4.905194873572327197e-05 +8.140000000000000000e+05,9.999696016311645508e-01,3.041050695173908025e-05 +8.145000000000000000e+05,9.976777434349060059e-01,2.322274260222911835e-03 +8.150000000000000000e+05,2.073625475168228149e-01,7.926374077796936035e-01 +8.155000000000000000e+05,4.449187964200973511e-02,9.555080533027648926e-01 +8.160000000000000000e+05,1.331246457993984222e-02,9.866874814033508301e-01 +8.165000000000000000e+05,9.999171495437622070e-01,8.290188270621001720e-05 +8.170000000000000000e+05,9.999834299087524414e-01,1.653268373047467321e-05 +8.175000000000000000e+05,9.994200468063354492e-01,5.800272920168936253e-04 +8.180000000000000000e+05,7.449164986610412598e-02,9.255083799362182617e-01 +8.185000000000000000e+05,1.193577336380258203e-04,9.998806715011596680e-01 +8.190000000000000000e+05,1.221712008118629456e-01,8.778288364410400391e-01 +8.195000000000000000e+05,1.176953222602605820e-02,9.882304668426513672e-01 +8.200000000000000000e+05,2.006134837865829468e-01,7.993865609169006348e-01 +8.205000000000000000e+05,3.210743367671966553e-01,6.789256334304809570e-01 +8.210000000000000000e+05,2.650691382586956024e-02,9.734930396080017090e-01 +8.215000000000000000e+05,1.866730302572250366e-01,8.133269548416137695e-01 +8.220000000000000000e+05,1.916174590587615967e-02,9.808382391929626465e-01 +8.225000000000000000e+05,1.072690938599407673e-03,9.989272952079772949e-01 +8.230000000000000000e+05,8.975231321528553963e-05,9.999102354049682617e-01 +8.235000000000000000e+05,1.017917093122377992e-04,9.998981952667236328e-01 +8.240000000000000000e+05,1.843816367909312248e-03,9.981561303138732910e-01 +8.245000000000000000e+05,4.248724319040775299e-03,9.957512617111206055e-01 +8.250000000000000000e+05,2.702282276004552841e-03,9.972977042198181152e-01 +8.255000000000000000e+05,6.995526142418384552e-03,9.930045008659362793e-01 +8.260000000000000000e+05,4.016463935840874910e-04,9.995983242988586426e-01 +8.265000000000000000e+05,3.962796836276538670e-05,9.999604225158691406e-01 +8.270000000000000000e+05,1.220887154340744019e-01,8.779112696647644043e-01 +8.275000000000000000e+05,8.663648623041808605e-04,9.991336464881896973e-01 +8.280000000000000000e+05,9.231102885678410530e-04,9.990768432617187500e-01 +8.285000000000000000e+05,9.707853794097900391e-01,2.921462804079055786e-02 +8.290000000000000000e+05,9.999842643737792969e-01,1.572371547808870673e-05 +8.295000000000000000e+05,9.997543692588806152e-01,2.456609217915683985e-04 +8.300000000000000000e+05,4.083146750926971436e-01,5.916852951049804688e-01 +8.305000000000000000e+05,1.218039840459823608e-01,8.781960010528564453e-01 +8.310000000000000000e+05,9.939584732055664062e-01,6.041560787707567215e-03 +8.315000000000000000e+05,9.984247684478759766e-01,1.575300004333257675e-03 +8.320000000000000000e+05,9.293782711029052734e-01,7.062175869941711426e-02 +8.325000000000000000e+05,1.971978927031159401e-03,9.980279803276062012e-01 +8.330000000000000000e+05,3.914125263690948486e-04,9.996085762977600098e-01 +8.335000000000000000e+05,3.198075294494628906e-02,9.680192470550537109e-01 +8.340000000000000000e+05,1.268613268621265888e-04,9.998731613159179688e-01 +8.345000000000000000e+05,9.998554596677422523e-04,9.990001320838928223e-01 +8.350000000000000000e+05,3.140565007925033569e-02,9.685943126678466797e-01 +8.355000000000000000e+05,1.314986380748450756e-03,9.986850619316101074e-01 +8.360000000000000000e+05,1.359369798592524603e-05,9.999864101409912109e-01 +8.365000000000000000e+05,1.124399015679955482e-03,9.988755583763122559e-01 +8.370000000000000000e+05,1.398407272063195705e-03,9.986016154289245605e-01 +8.375000000000000000e+05,5.147673655301332474e-03,9.948523640632629395e-01 +8.380000000000000000e+05,3.717225510627031326e-03,9.962827563285827637e-01 +8.385000000000000000e+05,1.737801067065447569e-04,9.998262524604797363e-01 +8.390000000000000000e+05,1.262710546143352985e-03,9.987372756004333496e-01 +8.395000000000000000e+05,1.069132471457123756e-03,9.989308714866638184e-01 +8.400000000000000000e+05,1.292468397878110409e-03,9.987075328826904297e-01 +8.405000000000000000e+05,5.368291749618947506e-04,9.994631409645080566e-01 +8.410000000000000000e+05,3.966392832808196545e-04,9.996033310890197754e-01 +8.415000000000000000e+05,1.514442992629483342e-04,9.998484849929809570e-01 +8.420000000000000000e+05,9.966003298759460449e-01,3.399701556190848351e-03 +8.425000000000000000e+05,9.989445805549621582e-01,1.055421656928956509e-03 +8.430000000000000000e+05,1.876143738627433777e-02,9.812384843826293945e-01 +8.435000000000000000e+05,5.132867590873502195e-05,9.999486207962036133e-01 +8.440000000000000000e+05,1.836174749769270420e-03,9.981638789176940918e-01 +8.445000000000000000e+05,1.380400732159614563e-03,9.986195564270019531e-01 +8.450000000000000000e+05,3.290953347459435463e-03,9.967089891433715820e-01 +8.455000000000000000e+05,5.153929232619702816e-04,9.994845390319824219e-01 +8.460000000000000000e+05,8.415810298174619675e-04,9.991583824157714844e-01 +8.465000000000000000e+05,1.103178504854440689e-02,9.889681935310363770e-01 +8.470000000000000000e+05,5.663775664288550615e-05,9.999433755874633789e-01 +8.475000000000000000e+05,5.325286765582859516e-04,9.994674324989318848e-01 +8.480000000000000000e+05,2.222179318778216839e-04,9.997777342796325684e-01 +8.485000000000000000e+05,8.237513829953968525e-04,9.991762042045593262e-01 +8.490000000000000000e+05,7.810205817222595215e-01,2.189793735742568970e-01 +8.495000000000000000e+05,9.979609251022338867e-01,2.039037179201841354e-03 +8.500000000000000000e+05,9.097414016723632812e-01,9.025863558053970337e-02 +8.505000000000000000e+05,1.413815625710412860e-04,9.998586177825927734e-01 +8.510000000000000000e+05,1.650862250244244933e-04,9.998348951339721680e-01 +8.515000000000000000e+05,8.262914419174194336e-01,1.737085431814193726e-01 +8.520000000000000000e+05,1.520463451743125916e-02,9.847953319549560547e-01 +8.525000000000000000e+05,3.338232263922691345e-02,9.666177034378051758e-01 +8.530000000000000000e+05,8.126138709485530853e-03,9.918739199638366699e-01 +8.535000000000000000e+05,9.507803770247846842e-05,9.999048709869384766e-01 +8.540000000000000000e+05,5.274462164379656315e-04,9.994725584983825684e-01 +8.545000000000000000e+05,6.668567657470703125e-04,9.993330836296081543e-01 +8.550000000000000000e+05,1.107657095417380333e-03,9.988923668861389160e-01 +8.555000000000000000e+05,9.214763343334197998e-02,9.078522920608520508e-01 +8.560000000000000000e+05,2.163235247135162354e-01,7.836765050888061523e-01 +8.565000000000000000e+05,8.600807632319629192e-04,9.991399049758911133e-01 +8.570000000000000000e+05,1.580945914611220360e-03,9.984190464019775391e-01 +8.575000000000000000e+05,8.147548069246113300e-04,9.991852641105651855e-01 +8.580000000000000000e+05,7.938432972878217697e-04,9.992061257362365723e-01 +8.585000000000000000e+05,2.494659856893122196e-04,9.997505545616149902e-01 +8.590000000000000000e+05,7.420802721753716469e-04,9.992578625679016113e-01 +8.595000000000000000e+05,2.000999171286821365e-03,9.979990124702453613e-01 +8.600000000000000000e+05,1.897594047477468848e-04,9.998102784156799316e-01 +8.605000000000000000e+05,7.875739829614758492e-04,9.992123842239379883e-01 +8.610000000000000000e+05,1.292848173761740327e-04,9.998706579208374023e-01 +8.615000000000000000e+05,2.330916177015751600e-04,9.997668862342834473e-01 +8.620000000000000000e+05,8.254553191363811493e-03,9.917454123497009277e-01 +8.625000000000000000e+05,6.297748186625540257e-04,9.993702769279479980e-01 +8.630000000000000000e+05,1.929711550474166870e-03,9.980702996253967285e-01 +8.635000000000000000e+05,1.109554315917193890e-03,9.988904595375061035e-01 +8.640000000000000000e+05,2.515717060305178165e-04,9.997484087944030762e-01 +8.645000000000000000e+05,3.266741987317800522e-05,9.999673366546630859e-01 +8.650000000000000000e+05,5.615400732494890690e-05,9.999438524246215820e-01 +8.655000000000000000e+05,3.904120996594429016e-03,9.960959553718566895e-01 +8.660000000000000000e+05,4.686778083851095289e-06,9.999953508377075195e-01 +8.665000000000000000e+05,1.226197582582244650e-05,9.999877214431762695e-01 +8.670000000000000000e+05,2.488377736881375313e-03,9.975116252899169922e-01 +8.675000000000000000e+05,9.302327525801956654e-04,9.990697503089904785e-01 +8.680000000000000000e+05,1.012430366245098412e-04,9.998987913131713867e-01 +8.685000000000000000e+05,1.001571654342114925e-03,9.989984631538391113e-01 +8.690000000000000000e+05,8.824561722576618195e-03,9.911754131317138672e-01 +8.695000000000000000e+05,5.700467154383659363e-04,9.994298815727233887e-01 +8.700000000000000000e+05,5.030933767557144165e-02,9.496906399726867676e-01 +8.705000000000000000e+05,9.984017014503479004e-01,1.598296570591628551e-03 +8.710000000000000000e+05,9.944092631340026855e-01,5.590757355093955994e-03 +8.715000000000000000e+05,1.266607199795544147e-03,9.987333416938781738e-01 +8.720000000000000000e+05,9.995828270912170410e-01,4.171787295490503311e-04 +8.725000000000000000e+05,9.992895126342773438e-01,7.105177501216530800e-04 +8.730000000000000000e+05,9.894884228706359863e-01,1.051160413771867752e-02 +8.735000000000000000e+05,9.325066804885864258e-01,6.749334186315536499e-02 +8.740000000000000000e+05,1.870032399892807007e-02,9.812996983528137207e-01 +8.745000000000000000e+05,1.576612889766693115e-01,8.423387408256530762e-01 +8.750000000000000000e+05,3.860903671011328697e-03,9.961391091346740723e-01 +8.755000000000000000e+05,1.475187018513679504e-03,9.985248446464538574e-01 +8.760000000000000000e+05,5.208344664424657822e-03,9.947916269302368164e-01 +8.765000000000000000e+05,1.115849736379459500e-04,9.998884201049804688e-01 +8.770000000000000000e+05,1.641466369619593024e-04,9.998358488082885742e-01 +8.775000000000000000e+05,6.328918039798736572e-02,9.367108345031738281e-01 +8.780000000000000000e+05,6.384631991386413574e-01,3.615367710590362549e-01 +8.785000000000000000e+05,7.528777350671589375e-04,9.992471933364868164e-01 +8.790000000000000000e+05,2.975335228256881237e-04,9.997023940086364746e-01 +8.795000000000000000e+05,9.554067850112915039e-01,4.459324851632118225e-02 +8.800000000000000000e+05,2.009853124618530273e-01,7.990146875381469727e-01 +8.805000000000000000e+05,2.562054432928562164e-04,9.997437596321105957e-01 +8.810000000000000000e+05,2.173957618651911616e-04,9.997826218605041504e-01 +8.815000000000000000e+05,6.964231724850833416e-04,9.993035793304443359e-01 +8.820000000000000000e+05,5.653941771015524864e-04,9.994345307350158691e-01 +8.825000000000000000e+05,8.608993084635585546e-05,9.999139308929443359e-01 +8.830000000000000000e+05,1.402409397996962070e-04,9.998596906661987305e-01 +8.835000000000000000e+05,1.152551631093956530e-04,9.998847246170043945e-01 +8.840000000000000000e+05,3.119569737464189529e-04,9.996880292892456055e-01 +8.845000000000000000e+05,1.276625320315361023e-03,9.987233281135559082e-01 +8.850000000000000000e+05,1.798433135263621807e-03,9.982014894485473633e-01 +8.855000000000000000e+05,8.408503606915473938e-03,9.915914535522460938e-01 +8.860000000000000000e+05,3.305185586214065552e-03,9.966948032379150391e-01 +8.865000000000000000e+05,1.401042682118713856e-03,9.985989928245544434e-01 +8.870000000000000000e+05,4.810103564523160458e-04,9.995189905166625977e-01 +8.875000000000000000e+05,1.687233895063400269e-02,9.831277132034301758e-01 +8.880000000000000000e+05,6.099915504455566406e-02,9.390008449554443359e-01 +8.885000000000000000e+05,9.196587800979614258e-01,8.034122735261917114e-02 +8.890000000000000000e+05,8.867021203041076660e-01,1.132978573441505432e-01 +8.895000000000000000e+05,3.909462597221136093e-03,9.960904717445373535e-01 +8.900000000000000000e+05,2.660480095073580742e-03,9.973395466804504395e-01 +8.905000000000000000e+05,2.229482051916420460e-04,9.997770190238952637e-01 +8.910000000000000000e+05,3.890946100000292063e-04,9.996109604835510254e-01 +8.915000000000000000e+05,4.432254005223512650e-03,9.955677390098571777e-01 +8.920000000000000000e+05,2.555986284278333187e-04,9.997443556785583496e-01 +8.925000000000000000e+05,9.709357400424778461e-04,9.990290403366088867e-01 +8.930000000000000000e+05,7.019673357717692852e-04,9.992979764938354492e-01 +8.935000000000000000e+05,1.186047960072755814e-02,9.881395101547241211e-01 +8.940000000000000000e+05,9.516491889953613281e-01,4.835080355405807495e-02 +8.945000000000000000e+05,2.471807529218494892e-04,9.997528195381164551e-01 +8.950000000000000000e+05,5.173064419068396091e-04,9.994826316833496094e-01 +8.955000000000000000e+05,1.252442318946123123e-02,9.874755740165710449e-01 +8.960000000000000000e+05,2.037291042506694794e-02,9.796270132064819336e-01 +8.965000000000000000e+05,9.914644360542297363e-01,8.535602129995822906e-03 +8.970000000000000000e+05,9.956358075141906738e-01,4.364211577922105789e-03 +8.975000000000000000e+05,1.763730198144912720e-01,8.236269950866699219e-01 +8.980000000000000000e+05,9.615378379821777344e-01,3.846217319369316101e-02 +8.985000000000000000e+05,2.745080888271331787e-01,7.254919409751892090e-01 diff --git a/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.mat b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.mat new file mode 100644 index 00000000..1f28e529 Binary files /dev/null and b/src/test/resources/rawDeepLearningClassifier/Koogu/blue_whale_24/rawScores_20190527_190000.mat differ diff --git a/src/tethys/CollectionHandler.java b/src/tethys/CollectionHandler.java new file mode 100644 index 00000000..66208fa0 --- /dev/null +++ b/src/tethys/CollectionHandler.java @@ -0,0 +1,38 @@ +package tethys; + +abstract public class CollectionHandler { + + private Collection collection; + + protected TethysControl tethysControl; + + /** + * @param tethysControl + * @param collection + */ + public CollectionHandler(TethysControl tethysControl, Collection collection) { + this.tethysControl = tethysControl; + this.collection = collection; + } + + public String collectionName() { + return collection.collectionName(); + } + + /** + * @return the collection + */ + public Collection getCollection() { + return collection; + } + + /** + * @return the tethysControl + */ + public TethysControl getTethysControl() { + return tethysControl; + } + + public abstract String getHelpPoint(); + +} diff --git a/src/tethys/TethysControl.java b/src/tethys/TethysControl.java index 83e445b7..42889f45 100644 --- a/src/tethys/TethysControl.java +++ b/src/tethys/TethysControl.java @@ -32,6 +32,8 @@ import PamController.PamSettingManager; import PamController.PamSettings; import PamUtils.PamFileChooser; import PamUtils.PamFileFilter; +import PamUtils.worker.PamWorkWrapper; +import PamUtils.worker.PamWorker; import PamView.PamTabPanel; import PamView.dialog.warn.WarnOnce; import PamguardMVC.PamDataBlock; @@ -104,7 +106,9 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet serverCheckTimer = new Timer(10000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - checkServer(); + ServerStatus serverState = checkServer(); + // check less often when it's OK to generate less debug output. + serverCheckTimer.setDelay(serverState.ok ? 600000 : 5000); } }); serverCheckTimer.setInitialDelay(0); @@ -469,6 +473,8 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet // case PamControllerInterface.INITIALIZATION_COMPLETE: initializationStuff(); break; + case PamControllerInterface.HYDROPHONE_ARRAY_CHANGED: + sendStateUpdate(new TethysState(StateType.UPDATEMETADATA)); } } @@ -525,7 +531,32 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet } } - private void countProjectDetections() { +// /** +// * Count project detections in a worker thread; +// */ +// public void countProjectDetectionsT() { +// sendStateUpdate(new TethysState(StateType.EXPORTRDATA, Collection.Detections)); +// PamWorker worker = new PamWorker(new PamWorkWrapper() { +// +// @Override +// public String runBackgroundTask(PamWorker pamWorker) { +// countProjectDetections(); +// return null; +// } +// +// @Override +// public void taskFinished(String result) { +// sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); +// +// } +// }, getGuiFrame(), 0, "Updating detections counts"); +// worker.start(); +// } + + /** + * Count project detections in the current thread. + */ + private synchronized void countProjectDetections() { if (dataBlockSynchInfos == null) { return; } @@ -549,12 +580,6 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet i++; } -// int[] counts = dbxmlQueries.countDataForProject(deplData.getProject(), dataPrefixes); -// if (counts != null) { -// for ( i = 0; i < counts.length; i++ ) { -// dataBlockSynchInfos.get(i).setDataCount(counts[i]); -// } -// } } /** @@ -688,11 +713,9 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet * @param dataBlock */ public void exportedDetections(PamDataBlock dataBlock) { - sendStateUpdate(new TethysState(StateType.EXPORTRDATA, Collection.Detections)); countProjectDetections(); - sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); +// sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); } - /** * @return the calibrationHandler */ diff --git a/src/tethys/TethysTimeFuncs.java b/src/tethys/TethysTimeFuncs.java index 70d11704..e238fb22 100644 --- a/src/tethys/TethysTimeFuncs.java +++ b/src/tethys/TethysTimeFuncs.java @@ -12,6 +12,7 @@ import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import PamUtils.PamCalendar; +import nilus.Helper; public class TethysTimeFuncs { @@ -54,6 +55,18 @@ public class TethysTimeFuncs { * @return */ public static XMLGregorianCalendar fromGregorianXML(String gregorianString) { + + try { + XMLGregorianCalendar xmlCal = Helper.timestamp(gregorianString); + return xmlCal; + } catch (DatatypeConfigurationException e1) { + // TODO Auto-generated catch block +// e1.printStackTrace(); + } + /** + * Above should work just fine. If it doesn't use my own code below... + */ + // typical string is 2018-10-20T00:00:00Z if (gregorianString == null) { return null; diff --git a/src/tethys/calibration/CalibrationHandler.java b/src/tethys/calibration/CalibrationHandler.java index 37dc186e..2aff00d8 100644 --- a/src/tethys/calibration/CalibrationHandler.java +++ b/src/tethys/calibration/CalibrationHandler.java @@ -1,6 +1,5 @@ package tethys.calibration; -import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -37,6 +36,7 @@ import nilus.MetadataInfo; import nilus.QualityValueBasic; import nilus.ResponsibleParty; import tethys.Collection; +import tethys.CollectionHandler; import tethys.DocumentInfo; import tethys.DocumentNilusObject; import tethys.TethysControl; @@ -52,9 +52,7 @@ import tethys.niluswraps.NilusUnpacker; import tethys.pamdata.AutoTethysProvider; import tethys.reporter.TethysReporter; -public class CalibrationHandler implements TethysStateObserver { - - private TethysControl tethysControl; +public class CalibrationHandler extends CollectionHandler implements TethysStateObserver { private ArrayList> calibrationsList; @@ -65,10 +63,14 @@ public class CalibrationHandler implements TethysStateObserver { public static final String[] qaTypes = {"unverified", "valid", "invalid"}; private Helper nilusHelper; + + public static final String helpPoint = "utilities.tethys.docs.calibrations"; + /** * @param tethysControl */ public CalibrationHandler(TethysControl tethysControl) { + super(tethysControl, Collection.Calibrations); this.tethysControl = tethysControl; calibrationsList = new ArrayList(); tethysControl.addStateObserver(this); try { @@ -504,7 +506,7 @@ public class CalibrationHandler implements TethysStateObserver { } String seachPattern = makeChannelNamePart(iChan); for (int i = 0; i < calibrationsList.size(); i++) { - String docName = calibrationsList.get(i).getDocumentName(); + String docName = calibrationsList.get(i).getDocumentId(); if (docName.endsWith(seachPattern)) { return true; } @@ -570,4 +572,9 @@ public class CalibrationHandler implements TethysStateObserver { } return theseCals; } + + @Override + public String getHelpPoint() { + return helpPoint; + } } diff --git a/src/tethys/calibration/swing/CalibrationsContactCard.java b/src/tethys/calibration/swing/CalibrationsContactCard.java index 2dc6e581..0ad03c36 100644 --- a/src/tethys/calibration/swing/CalibrationsContactCard.java +++ b/src/tethys/calibration/swing/CalibrationsContactCard.java @@ -5,6 +5,7 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.Date; import javax.swing.BoxLayout; @@ -18,6 +19,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import org.jdesktop.swingx.JXDatePicker; +import Array.ArrayManager; +import Array.Hydrophone; +import Array.PamArray; import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; import PamView.panel.WestAlignedPanel; @@ -43,6 +47,8 @@ public class CalibrationsContactCard extends CalibrationsCard { private JButton copyDown, copyUp; + private JComboBox hydrophoneSelection; + public CalibrationsContactCard(PamWizard pamWizard) { super(pamWizard, "Contact Details"); // TODO Auto-generated constructor stub @@ -57,17 +63,26 @@ public class CalibrationsContactCard extends CalibrationsCard { JPanel datePanel = new JPanel(new GridBagLayout()); JPanel lp = new WestAlignedPanel(datePanel); - lp.setBorder(new TitledBorder("Calibration date")); + lp.setBorder(new TitledBorder("Date and hydrophones")); GridBagConstraints c = new PamGridBagContraints(); datePanel.add(new JLabel("Calibration date: ", JLabel.RIGHT), c); datePicker = new JXDatePicker(); c.gridx++; datePanel.add(datePicker, c); - c.gridx = 0; - c.gridy++; - datePanel.add(new JLabel("Update Frequency", JLabel.RIGHT), c); +// c.gridx = 0; + c.gridx++; + datePanel.add(new JLabel(" Update Frequency ", JLabel.RIGHT), c); c.gridx++; datePanel.add(updateInterval, c); + c.gridx = 0; + c.gridy++; + datePanel.add(new JLabel(" Hydrophones ", JLabel.RIGHT), c); + c.gridwidth = 5; + c.gridx++; + hydrophoneSelection = new JComboBox<>(); + datePanel.add(hydrophoneSelection, c); + hydrophoneSelection.setToolTipText("Select which hydrophone calibrations to export"); + calibrator = new ResponsiblePartyPanel("Technical Person"); dataManager = new ResponsiblePartyPanel("Data Manager"); @@ -202,6 +217,16 @@ public class CalibrationsContactCard extends CalibrationsCard { datePicker.setDate(new Date(TethysTimeFuncs.millisFromGregorianXML(ts))); } + hydrophoneSelection.removeAllItems(); + hydrophoneSelection.addItem("All hydrophones"); + PamArray array = ArrayManager.getArrayManager().getCurrentArray(); + ArrayList phones = array.getHydrophoneArray(); + int i = 0; + for (Hydrophone phone : phones) { + String txt = String.format("Hydrophone %d, %s, %3.1f dBre1\u00B5Pa", i, phone.getType(), phone.getSensitivity()); + hydrophoneSelection.addItem(txt); + i++; + } } diff --git a/src/tethys/calibration/swing/CalibrationsExportWizard.java b/src/tethys/calibration/swing/CalibrationsExportWizard.java index 74bb356a..1ab7bc6e 100644 --- a/src/tethys/calibration/swing/CalibrationsExportWizard.java +++ b/src/tethys/calibration/swing/CalibrationsExportWizard.java @@ -5,6 +5,7 @@ import java.awt.Window; import PamView.wizard.PamWizard; import PamView.wizard.PamWizardCard; import nilus.Calibration; +import tethys.calibration.CalibrationHandler; public class CalibrationsExportWizard extends PamWizard { @@ -13,8 +14,9 @@ public class CalibrationsExportWizard extends PamWizard { private CalibrationsExportWizard(Window parentFrame, Calibration sampleDocument) { super(parentFrame, "Calibrations Export"); this.sampleDocument = sampleDocument; - addCard(new CalibrationProcessCard(this)); addCard(new CalibrationsContactCard(this)); + addCard(new CalibrationProcessCard(this)); + setHelpPoint(CalibrationHandler.helpPoint); } public static Calibration showWizard(Window parentFrame, Calibration sampleDocument) { diff --git a/src/tethys/calibration/swing/CalibrationsMainPanel.java b/src/tethys/calibration/swing/CalibrationsMainPanel.java index e13365ef..cd1275f7 100644 --- a/src/tethys/calibration/swing/CalibrationsMainPanel.java +++ b/src/tethys/calibration/swing/CalibrationsMainPanel.java @@ -15,53 +15,47 @@ import PamView.panel.PamPanel; import tethys.TethysControl; import tethys.TethysState; import tethys.calibration.CalibrationHandler; +import tethys.deployment.PInstrument; +import tethys.swing.TethysExportPanel; import tethys.swing.TethysGUIPanel; +import tethys.swing.TippedButton; -public class CalibrationsMainPanel extends TethysGUIPanel { +public class CalibrationsMainPanel extends TethysExportPanel { private CalibrationHandler calibrationHandler; private CalibrationsTable calibrationsTable; - private JPanel mainPanel; - - private JPanel ctrlPanel; - - private JButton exportButton; - - private JLabel warning; +// private JPanel mainPanel; +// +// private JPanel ctrlPanel; +// +// private TippedButton exportButton; +// +// private JLabel warning; public CalibrationsMainPanel(TethysControl tethysControl, CalibrationHandler calibrationHandler) { - super(tethysControl); + super(tethysControl, calibrationHandler, false); this.calibrationHandler = calibrationHandler; - mainPanel = new PamPanel(new BorderLayout()); + JPanel mainPanel = getMainPanel(); +// mainPanel = new PamPanel(new BorderLayout()); mainPanel.setBorder(new TitledBorder("Instrument calibration information")); calibrationsTable = new CalibrationsTable(tethysControl, calibrationHandler); mainPanel.add(BorderLayout.CENTER, calibrationsTable.getComponent()); - ctrlPanel = new PamPanel(new BorderLayout()); - exportButton = new JButton("Export ..."); - ctrlPanel.add(BorderLayout.WEST, exportButton); - warning = new JLabel(); - ctrlPanel.add(BorderLayout.CENTER, warning); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); - exportButton.setToolTipText("Export calibration data to database"); - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportCalibrations(); - } - }); - } - - protected void exportCalibrations() { - calibrationHandler.exportAllCalibrations(); - } - - @Override - public JComponent getComponent() { - return mainPanel; +// ctrlPanel = new PamPanel(new BorderLayout()); +// exportButton = new TippedButton("Export ...", "Export calibration data to database"); +// ctrlPanel.add(BorderLayout.WEST, exportButton); +// warning = new JLabel(); +// ctrlPanel.add(BorderLayout.CENTER, warning); +// mainPanel.add(BorderLayout.NORTH, ctrlPanel); +// exportButton.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// exportCalibrations(); +// } +// }); } @Override @@ -71,7 +65,45 @@ public class CalibrationsMainPanel extends TethysGUIPanel { } private void enableControls() { - exportButton.setEnabled(getTethysControl().isServerOk()); + if (getTethysControl().isServerOk() == false) { + disableExport("Tethys Server not running"); + return; + } + if (isHydrophoneNamed() == false) { + disableExport("Can't export calibrations until the Hydrophone array has been correctly named"); + return; + }; + enableExport(true); + } + + /** + * Check to see if hydrophone is named correctly. + * @return + */ + private boolean isHydrophoneNamed() { + PInstrument currentInstrument = getTethysControl().getDeploymentHandler().getCurrentArrayInstrument(); + if (currentInstrument == null) { + return false; + } + if (currentInstrument.instrumentId == null || currentInstrument.instrumentType == null) { + return false; + } + + if (currentInstrument.instrumentId.length() == 0 || currentInstrument.instrumentType.length() == 0) { + return false; + } + return true; + } + + @Override + protected void exportButtonPressed(ActionEvent e) { + calibrationHandler.exportAllCalibrations(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + // TODO Auto-generated method stub + } } diff --git a/src/tethys/calibration/swing/CalibrationsTable.java b/src/tethys/calibration/swing/CalibrationsTable.java index 45b91f18..c9405f1a 100644 --- a/src/tethys/calibration/swing/CalibrationsTable.java +++ b/src/tethys/calibration/swing/CalibrationsTable.java @@ -120,6 +120,7 @@ public class CalibrationsTable extends TethysGUIPanel { }); popMenu.add(menuItem); } + popMenu.addSeparator(); if (n > 1) { menuItem = new JMenuItem("Delete selected documents"); menuItem.addActionListener(new ActionListener() { diff --git a/src/tethys/dbxml/DBXMLConnect.java b/src/tethys/dbxml/DBXMLConnect.java index 9b12ecd5..b534703b 100644 --- a/src/tethys/dbxml/DBXMLConnect.java +++ b/src/tethys/dbxml/DBXMLConnect.java @@ -3,14 +3,17 @@ package tethys.dbxml; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.file.Files; import java.util.ArrayList; +import java.util.zip.GZIPOutputStream; import javax.xml.bind.JAXBException; @@ -67,7 +70,7 @@ public class DBXMLConnect { } return true; } - + /** * Get the client. The client will only be recreated if the url changes @@ -113,9 +116,9 @@ public class DBXMLConnect { } return file; } - + /** - * Create a temporary nilus file. + * Create a temporary nilus file. * @param nilusObject * @return * @throws TethysException @@ -124,20 +127,20 @@ public class DBXMLConnect { String tempName = getTempFileName(nilusObject); tempName = tempDirectory.getAbsolutePath() + File.separator + tempName + ".xml"; File tempFile = new File(tempName); - File retFile = createXMLDocument(nilusObject, tempFile); + File retFile = createXMLDocument(nilusObject, tempFile); retFile.deleteOnExit(); return retFile; } - - + + public boolean postAndLog(Object nilusObject) throws TethysException { return postAndLog(nilusObject, null); } - + /** * I don't think this should ever be used since everything goes a bit pear - * shaped if the documentName isn't the same as the Id. However, for Calibration + * shaped if the documentName isn't the same as the Id. However, for Calibration * documents this is no longer the case, since a Calibration can have multiple * entries on different dates, so allow it ! * @param nilusObject @@ -145,11 +148,11 @@ public class DBXMLConnect { * @return * @throws TethysException */ - public boolean postAndLog(Object nilusObject, String documentName) throws TethysException - { + public boolean postAndLog(Object nilusObject, String documentName) throws TethysException + { boolean ok = NilusChecker.warnEmptyFields(tethysControl.getGuiFrame(), nilusObject); - - + + TethysException e = null; boolean success = false; try { @@ -168,13 +171,13 @@ public class DBXMLConnect { } return success; } - + /** * take a nilus object loaded with PamGuard data and post it to the Tethys database * * @param pamGuardObjs a nilus object loaded with PamGuard data * @return error string, null string means there are no errors - * @throws TethysException + * @throws TethysException */ private boolean postToTethys(Object nilusObject, String documentName) throws TethysException { @@ -192,12 +195,35 @@ public class DBXMLConnect { MarshalXML marshal = new MarshalXML(); marshal.createInstance(objClass); marshal.marshal(nilusObject, tempFile.toString()); + // above lines have made a file. Are now going to gzip it before sending to Tethys + File zipFile = null; +// try { +// zipFile = zipOutputFile(tempFile); +// } +// catch (FileNotFoundException e1){ +// System.out.println(e1.getMessage()); +// } +// catch (IOException e2) { +// System.out.println(e2.getMessage()); +// } + String finalName; + if (zipFile == null) { + finalName = bodgeName; + } + else { + finalName = zipFile.toString(); + } + + // tempFile = stripXMLHeader(tempFile); importReturn = Importer.ImportFiles(params.getFullServerName(), collection.collectionName(), - new String[] { bodgeName }, "", "", false); + new String[] { finalName }, "", "", false); tempFile.deleteOnExit(); + if (zipFile != null) { + zipFile.deleteOnExit(); + } } catch(IllegalArgumentException e) { throw new TethysException("IllegalArgumentException posting to Tethys: " + e.getMessage(), null); } catch (IOException e) { @@ -207,21 +233,49 @@ public class DBXMLConnect { } /* - * The returned string consists of the file name, then an XML report. - * Quite hard to see much common structure in this, so just look for + * The returned string consists of the file name, then an XML report. + * Quite hard to see much common structure in this, so just look for * two words, and */ boolean error = importReturn.contains(""); String name = tempFile.getName(); TethysReporter.getTethysReporter().addReport(new TethysReport(success, collection, name, name)); -// error = !success; might be a better options. +// error = !success; might be a better options. if (error) { throw new TethysException("Error posting to Tethys", importReturn); } return success; } + /** + * Zip an xml file (or any file) into a gz file with a new end + * @param xmlFile + * @return + * @throws FileNotFoundException + * @throws IOException + */ + private File zipOutputFile(File xmlFile) throws FileNotFoundException, IOException { + String zipName = xmlFile.toString() + "-temp-.gz"; + File zipFile = new File(zipName); + GZIPOutputStream opStream = new GZIPOutputStream(new FileOutputStream(zipFile)); + InputStream fis = new FileInputStream(xmlFile); + int chunkSize = 100*1024; + byte[] buffer = new byte[chunkSize]; +// ZipEntry zipEntry = new ZipEntry(xmlFile.getName()); + int bytesRead; + while ((bytesRead = fis.read(buffer)) >= 0) { + opStream.write(buffer, 0, bytesRead); + } + + opStream.close(); + + fis.close(); + + + return zipFile; + } + /** * Update a document within Tethys. We're assuming that a * document with the same name in the same collection already @@ -229,7 +283,7 @@ public class DBXMLConnect { * the removedocument function * @param nilusDocument * @return - * @throws TethysException + * @throws TethysException */ public boolean updateDocument(Object nilusDocument) throws TethysException { deleteDocument(nilusDocument); @@ -242,7 +296,7 @@ public class DBXMLConnect { * class to identify the correct collection. * @param nilusDocument * @return - * @throws TethysException + * @throws TethysException */ public boolean deleteDocument(Object nilusDocument) throws TethysException { @@ -259,7 +313,7 @@ public class DBXMLConnect { ['ECoastNARW0'] -An error will throw an exception. +An error will throw an exception. */ } catch (Exception e) { @@ -275,7 +329,7 @@ An error will throw an exception. * Delete a Deploymnet and any contained Detections document. Doesn't work ! * @param deploymentId * @return - * @throws TethysException + * @throws TethysException */ public boolean deleteDeployment(String deploymentId) throws TethysException { ArrayList detDocNames = tethysControl.getDbxmlQueries().getDetectionsDocuments(deploymentId); @@ -292,8 +346,8 @@ An error will throw an exception. } /** - * Remove a document based on a collection name and a cdocument Id. - * @param collection collection name. + * Remove a document based on a collection name and a cdocument Id. + * @param collection collection name. * @param documentName document name (not the internal Document Id) * @return * @throws TethysException @@ -303,8 +357,8 @@ An error will throw an exception. } /** - * Remove a document based on a collection name and a document namw. - * @param collectionName collection name. + * Remove a document based on a collection name and a document namw. + * @param collectionName collection name. * @param documentName document name (not the internal Document Id) * @return * @throws TethysException @@ -319,7 +373,7 @@ An error will throw an exception. ['ECoastNARW0'] - An error will throw an exception. + An error will throw an exception. */ } catch (Exception e) { @@ -330,14 +384,14 @@ An error will throw an exception. } /** - * check the return string from importFiles and if it's an - * error, throw an exception. Otherwise do nothing. + * check the return string from importFiles and if it's an + * error, throw an exception. Otherwise do nothing. * @param fileError */ private void checkReturnString(String fileError) { /** * Example good string is - * + * C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xml: 7360 bytes @@ -367,7 +421,7 @@ C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xmlnot: 0 b /** * Seems we have to get rid of the line - * which is being put there by the marshaller ? + * which is being put there by the marshaller ? * @param tempFile */ private File stripXMLHeader(File tempFile) { @@ -377,7 +431,7 @@ C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xmlnot: 0 b try { BufferedReader reader = new BufferedReader(new FileReader(tempFile)); BufferedWriter writer = new BufferedWriter(new FileWriter(tempTemp)); - String line = reader.readLine(); + String line = reader.readLine(); while (line != null) { // see if the line has any unicode in it int len = line.length(); diff --git a/src/tethys/dbxml/DBXMLQueries.java b/src/tethys/dbxml/DBXMLQueries.java index 460fbc66..d4c0be9a 100644 --- a/src/tethys/dbxml/DBXMLQueries.java +++ b/src/tethys/dbxml/DBXMLQueries.java @@ -515,6 +515,9 @@ public class DBXMLQueries { * first query for Detections documents associated with this deployment and datablock. * updated May 23 */ + if (dataBlock == null) { + return null; + } String queryNoDepl = "{\"species\":{\"query\":{\"op\":\"lib:completename2tsn\",\"optype\":\"function\",\"operands\":[\"%s\"]},\"return\":{\"op\":\"lib:tsn2completename\",\"optype\":\"function\",\"operands\":[\"%s\"]}},\"return\":[\"Detections/Id\"],\"select\":[{\"op\":\"=\",\"operands\":[\"Detections/Algorithm/Software\",\"LongDataName\"],\"optype\":\"binary\"}],\"enclose\":1}"; String queryWithDepl = "{\"species\":{\"query\":{\"op\":\"lib:completename2tsn\",\"optype\":\"function\",\"operands\":[\"%s\"]},\"return\":{\"op\":\"lib:tsn2completename\",\"optype\":\"function\",\"operands\":[\"%s\"]}},\"return\":[\"Detections/Id\"],\"select\":[{\"op\":\"=\",\"operands\":[\"Detections/DataSource/DeploymentId\",\"TheDeploymentId\"],\"optype\":\"binary\"},{\"op\":\"=\",\"operands\":[\"Detections/Algorithm/Software\",\"LongDataName\"],\"optype\":\"binary\"}],\"enclose\":1}"; String query; diff --git a/src/tethys/deployment/DeploymentHandler.java b/src/tethys/deployment/DeploymentHandler.java index 0c435fc0..8d82ac48 100644 --- a/src/tethys/deployment/DeploymentHandler.java +++ b/src/tethys/deployment/DeploymentHandler.java @@ -73,6 +73,7 @@ import nilus.UnknownSensor; import pamMaths.PamVector; import pamMaths.STD; import tethys.Collection; +import tethys.CollectionHandler; import tethys.TethysControl; import tethys.TethysLocationFuncs; import tethys.TethysState; @@ -83,6 +84,7 @@ import tethys.TethysState.StateType; import tethys.dbxml.DBXMLConnect; import tethys.dbxml.TethysException; import tethys.deployment.swing.DeploymentWizard; +import tethys.deployment.swing.EffortProblemDialog; import tethys.deployment.swing.RecordingGapDialog; import tethys.niluswraps.PDeployment; import tethys.output.TethysExportParams; @@ -98,16 +100,9 @@ import tethys.swing.DeploymentTableObserver; * @author dg50 * */ -public class DeploymentHandler implements TethysStateObserver, DeploymentTableObserver { +public class DeploymentHandler extends CollectionHandler implements TethysStateObserver, DeploymentTableObserver { - private TethysControl tethysControl; - - /** - * @return the tethysControl - */ - public TethysControl getTethysControl() { - return tethysControl; - } +// private TethysControl tethysControl; private EffortFunctions effortFunctions; @@ -119,8 +114,11 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb private DeploymentExportOpts deploymentExportOptions = new DeploymentExportOpts(); + public static final String helpPoint = "utilities.tethys.docs.deployments"; + public DeploymentHandler(TethysControl tethysControl) { - super(); + + super(tethysControl, Collection.Deployments); this.tethysControl = tethysControl; @@ -368,8 +366,39 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb public void createPamguardOverview() { deploymentOverview = effortFunctions.makeRecordingOverview(); + + checkDeploymentOverview(deploymentOverview); + updateProjectDeployments(); matchPamguard2Tethys(deploymentOverview, projectDeployments); + + tethysControl.sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION)); + } + + /** + * Check the deployment overview for consistency.
    + * Take the raw audio information and the binary information and check they are similar. + * if not, ask the user what to do. + * @param deploymentOverview + */ + private void checkDeploymentOverview(DeploymentOverview overview) { + RecordingList rawList = overview.getRawDataList(); + RecordingList binList = overview.getBinaryDataList(); + if (rawList == null || binList == null) { + return; // nothing to do + } + double similarity = rawList.getSimilarity(binList); + if (similarity > 0.95) { + return; + } + /* + * if we get here, it seems like the two lists are very different, so + * show a dialog to ask the user what to do. + */ + RecordingList selList = EffortProblemDialog.showDialog(tethysControl.getGuiFrame(), overview); + if (selList != null) { + tethysControl.getTethysExportParams().setEffortSourceName(selList.getSourceName()); + } } /** @@ -381,22 +410,22 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (exportOptions != null) { this.deploymentExportOptions = exportOptions; deploymentOverview = getDeploymentOverview(); - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + RecordingList allPeriods = deploymentOverview.getMasterList(getTethysControl()); exportDeployments(allPeriods); } } /** * Export deployments docs. Playing with a couple of different ways of doing this. - * @param selectedDeployments + * @param allPeriods */ - public void exportDeployments(ArrayList selectedDeployments) { + public void exportDeployments(RecordingList allPeriods) { TethysReporter.getTethysReporter().clear(); if (deploymentExportOptions.separateDeployments) { - exportSeparateDeployments(selectedDeployments); + exportSeparateDeployments(allPeriods); } else { - exportOneDeploymnet(selectedDeployments); + exportOneDeploymnet(allPeriods); } TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); } @@ -404,7 +433,7 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb /** * Make one big deployment document with all the recording periods in it. */ - private void exportOneDeploymnet(ArrayList selectedDeployments) { + private void exportOneDeploymnet(RecordingList recordingList) { // do the lot, whatever ... Float sampleRate = null; AcquisitionControl daq = (AcquisitionControl) PamController.getInstance().findControlledUnit(AcquisitionControl.class, null); @@ -414,10 +443,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb sampleRate = daqParams.sampleRate; } - selectedDeployments = getDeploymentOverview().getRecordingPeriods(); int freeId = getTethysControl().getDeploymentHandler().getFirstFreeDeploymentId(); - RecordingPeriod onePeriod = new RecordingPeriod(selectedDeployments.get(0).getRecordStart(), - selectedDeployments.get(selectedDeployments.size()-1).getRecordStop()); + RecordingPeriod onePeriod = new RecordingPeriod(recordingList.getStart(), + recordingList.getEnd()); TethysExportParams exportParams = tethysControl.getTethysExportParams(); String id = String.format("%s_%s", exportParams.getDatasetName(), "all"); Deployment deployment = createDeploymentDocument(freeId, onePeriod, id); @@ -425,7 +453,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb Deployment globalMeta = getTethysControl().getGlobalDeplopymentData(); deployment.setCruise(globalMeta.getCruise()); deployment.setSite(globalMeta.getSite()); - if (selectedDeployments.size() > 1) { + ArrayList effortPeriods = recordingList.getEffortPeriods(); + if (recordingList.size() > 1) { // // now need to remove the sampling details - don't though, add invalid periods instead. // SamplingDetails samplingDetails = deployment.getSamplingDetails(); // samplingDetails.getChannel().clear(); @@ -440,9 +469,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb deployment.setQualityAssurance(qa = new AcousticDataQAType()); } List qualityList = qa.getQuality(); - for (int i = 1; i < selectedDeployments.size(); i++) { - long end = selectedDeployments.get(i-1).getRecordStop(); - long start = selectedDeployments.get(i).getRecordStart(); + for (int i = 1; i < recordingList.size(); i++) { + long end = effortPeriods.get(i-1).getRecordStop(); + long start = effortPeriods.get(i).getRecordStart(); Quality q = new Quality(); q.setStart(TethysTimeFuncs.xmlGregCalFromMillis(end)); q.setEnd(TethysTimeFuncs.xmlGregCalFromMillis(start)); @@ -479,14 +508,15 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb /** * Make a separate deployment document for every recording period. */ - private void exportSeparateDeployments(ArrayList selectedDeployments) { + private void exportSeparateDeployments(RecordingList recordingList) { int freeId = getTethysControl().getDeploymentHandler().getFirstFreeDeploymentId(); // fill in a few things from here Deployment globalMeta = getTethysControl().getGlobalDeplopymentData(); TethysExportParams exportParams = tethysControl.getTethysExportParams(); - for (int i = 0; i < selectedDeployments.size(); i++) { - RecordingPeriod recordPeriod = selectedDeployments.get(i); + ArrayList effortPeriods = recordingList.getEffortPeriods(); + for (int i = 0; i < recordingList.size(); i++) { + RecordingPeriod recordPeriod = effortPeriods.get(i); PDeployment exDeploymnet = recordPeriod.getMatchedTethysDeployment(); Deployment deployment = null; String id = String.format("%s_%d", exportParams.getDatasetName(), i); @@ -532,8 +562,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (deployments == null || deploymentOverview == null) { return; } - ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); - for (RecordingPeriod aPeriod : recordingPeriods) { + RecordingList recordingList = deploymentOverview.getMasterList(getTethysControl()); + ArrayList effortPeriods = recordingList.getEffortPeriods(); + for (RecordingPeriod aPeriod : effortPeriods) { PDeployment closestDeployment = findClosestDeployment(aPeriod, deployments); aPeriod.setMatchedTethysDeployment(closestDeployment); if (closestDeployment != null) { @@ -592,7 +623,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (deploymentOverview == null) { return matched; } - for (RecordingPeriod period : deploymentOverview.getRecordingPeriods()) { + ArrayList effortPeriods = deploymentOverview.getMasterList(getTethysControl()).getEffortPeriods(); + for (RecordingPeriod period : effortPeriods) { PDeployment deployment = period.getMatchedTethysDeployment(); if (deployment != null) { if (matched.contains(deployment) == false) { @@ -1230,7 +1262,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb } regimens.add(regimen); - DutyCycleInfo dutyCycleInf = deploymentOverview.getDutyCycleInfo(); + RecordingList recordingList = deploymentOverview.getMasterList(getTethysControl()); + DutyCycleInfo dutyCycleInf = recordingList.assessDutyCycle(); boolean isDS = dutyCycleInf != null && dutyCycleInf.isDutyCycled; if (isDS) { DutyCycle dutyCycle = new DutyCycle(); @@ -1307,4 +1340,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb return deploymentExportOptions; } + @Override + public String getHelpPoint() { + return helpPoint; + } + } diff --git a/src/tethys/deployment/DeploymentOverview.java b/src/tethys/deployment/DeploymentOverview.java index f296df8e..3f39c653 100644 --- a/src/tethys/deployment/DeploymentOverview.java +++ b/src/tethys/deployment/DeploymentOverview.java @@ -1,16 +1,6 @@ package tethys.deployment; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.ListIterator; - -import Acquisition.AcquisitionControl; -import Acquisition.AcquisitionParameters; -import Acquisition.DaqStatusDataUnit; -import PamController.PamControlledUnit; -import PamController.PamController; -import PamguardMVC.PamDataBlock; +import tethys.TethysControl; /** * Class to give a general overview of all the effort in PAMGuard which will form the @@ -22,59 +12,83 @@ import PamguardMVC.PamDataBlock; */ public class DeploymentOverview { - private ArrayList recordingPeriods = new ArrayList<>(); + private RecordingList rawDataList; - private DutyCycleInfo dutyCycleInfo; + private RecordingList binaryDataList; + +// private DutyCycleInfo dutyCycleInfo; - public DeploymentOverview(DutyCycleInfo dutyCycleInfo) { - super(); - this.dutyCycleInfo = dutyCycleInfo; - } +// public DeploymentOverview(DutyCycleInfo dutyCycleInfo) { +// super(); +// this.dutyCycleInfo = dutyCycleInfo; +// } - public DeploymentOverview(DutyCycleInfo dutyCycleInfo, ArrayList tempPeriods) { - this.dutyCycleInfo = dutyCycleInfo; - this.recordingPeriods = tempPeriods; - } - - - public void addRecordingPeriod(long start, long stop) { - addRecordingPeriod(new RecordingPeriod(start, stop)); + public DeploymentOverview(DutyCycleInfo dutyCycleInfo, RecordingList rawDataList, RecordingList binaryDataList) { +// this.dutyCycleInfo = dutyCycleInfo; + this.rawDataList = rawDataList; + this.binaryDataList = binaryDataList; } - private void addRecordingPeriod(RecordingPeriod recordingPeriod) { - recordingPeriods.add(recordingPeriod); + /** + * @return the rawDataList + */ + public RecordingList getRawDataList() { + return rawDataList; } - public ArrayList getRecordingPeriods() { - return recordingPeriods; + /** + * @return the binaryDataList + */ + public RecordingList getBinaryDataList() { + return binaryDataList; } - public DutyCycleInfo getDutyCycleInfo() { - return dutyCycleInfo; +// /** +// * @return the dutyCycleInfo +// */ +// public DutyCycleInfo getDutyCycleInfo() { +// return dutyCycleInfo; +// } + + public RecordingList getMasterList(TethysControl tethysControl) { + return getMasterList(tethysControl.getTethysExportParams().getEffortSourceName()); + } + + public RecordingList getMasterList(String effortSourceName) { + if (effortSourceName == null) { + return getLongestList(); + } + if (binaryDataList != null & binaryDataList.getSourceName().equals(effortSourceName)) { + return binaryDataList; + } + if (rawDataList != null & rawDataList.getSourceName().equals(effortSourceName)) { + return rawDataList; + } + return getLongestList(); } /** - * Get the start time of the first recording + * Get the recording list with the greatest duration (start to end) + * not looking at coverage between those times. * @return */ - public Long getFirstStart() { - if (recordingPeriods.size() > 0) { - return recordingPeriods.get(0).getRecordStart(); + public RecordingList getLongestList() { + if (binaryDataList == null) { + return rawDataList; } - return null; - } - - /** - * Get the end time of the last recording - * @return - */ - public Long getLastEnd() { - if (recordingPeriods.size() > 0) { - return recordingPeriods.get(recordingPeriods.size()-1).getRecordStop(); + if (rawDataList == null) { + return binaryDataList; + } + long lRaw = rawDataList.duration(); + long lBin = binaryDataList.duration(); + + if (lRaw > lBin) { + return rawDataList; + } + else { + return binaryDataList; } - return null; } - - + } diff --git a/src/tethys/deployment/EffortFunctions.java b/src/tethys/deployment/EffortFunctions.java index 2fd8f383..b7e5b260 100644 --- a/src/tethys/deployment/EffortFunctions.java +++ b/src/tethys/deployment/EffortFunctions.java @@ -41,36 +41,40 @@ public class EffortFunctions { this.tethysControl = tethysControl; } - private DeploymentOverview createOverview(RecordingList tempPeriods) { - - DutyCycleInfo dutyCycleinfo = assessDutyCycle(tempPeriods); - if (dutyCycleinfo == null) { - return null; - } - - // if it's duty cycles, then we only want a single entry. - RecordingList deploymentPeriods; - if (dutyCycleinfo.isDutyCycled == false) { - deploymentPeriods = tempPeriods; - } - else { - deploymentPeriods = new RecordingList(); - deploymentPeriods.add(new RecordingPeriod(tempPeriods.get(0).getRecordStart(), tempPeriods.get(tempPeriods.size()-1).getRecordStop())); - } - /* - * do another sort of the deploymentPeriods. The start stops were in the order they went into the - * database in the hope that pairs were the right way round. Now check all data are/ - */ - Collections.sort(deploymentPeriods, new Comparator() { - @Override - public int compare(RecordingPeriod o1, RecordingPeriod o2) { - return (int) (o1.getRecordStart()-o2.getRecordStart()); - } - }); - - DeploymentOverview deploymentOverview = new DeploymentOverview(dutyCycleinfo, deploymentPeriods); - return deploymentOverview; - } +// private DeploymentOverview createOverview(RecordingList tempPeriods) { +// +// tempPeriods.sort(); +// +// DutyCycleInfo dutyCycleinfo = assessDutyCycle(tempPeriods); +// if (dutyCycleinfo == null) { +// return null; +// } +// +// +// // if it's duty cycles, then we only want a single entry. +// RecordingList deploymentPeriods; +// if (dutyCycleinfo.isDutyCycled == false) { +// deploymentPeriods = tempPeriods; +// } +// else { +// deploymentPeriods = new RecordingList(tempPeriods.getSourceName()); +// deploymentPeriods.add(new RecordingPeriod(tempPeriods.get(0).getRecordStart(), tempPeriods.get(tempPeriods.size()-1).getRecordStop())); +// } +// /* +// * do another sort of the deploymentPeriods. The start stops were in the order they went into the +// * database in the hope that pairs were the right way round. Now check all data are/ +// */ +// deploymentPeriods.sort(); +//// Collections.sort(deploymentPeriods, new Comparator() { +//// @Override +//// public int compare(RecordingPeriod o1, RecordingPeriod o2) { +//// return (int) (o1.getRecordStart()-o2.getRecordStart()); +//// } +//// }); +// +// DeploymentOverview deploymentOverview = new DeploymentOverview(dutyCycleinfo, deploymentPeriods); +// return deploymentOverview; +// } public DeploymentOverview makeRecordingOverview() { @@ -79,13 +83,18 @@ public class EffortFunctions { RecordingList binaryPeriods = listBinaryFiles(); - long l1 = listDuration(recordingPeriods); - long l2 = listDuration(binaryPeriods); - if (listDuration(binaryPeriods) > listDuration(recordingPeriods)) { - recordingPeriods = binaryPeriods; - } + // see what the similarity is between them +// double sim = recordingPeriods.getSimilarity(binaryPeriods); +// double testSim = recordingPeriods.getSimilarity(recordingPeriods); - DeploymentOverview deploymentOverview = createOverview(recordingPeriods); +// long l1 = listDuration(recordingPeriods); +// long l2 = listDuration(binaryPeriods); +// if (listDuration(binaryPeriods) > listDuration(recordingPeriods)) { +// recordingPeriods = binaryPeriods; +// } +// +// DeploymentOverview deploymentOverview = createOverview(recordingPeriods); + DeploymentOverview deploymentOverview = new DeploymentOverview(null, recordingPeriods, binaryPeriods); return deploymentOverview; } @@ -128,7 +137,8 @@ public class EffortFunctions { } } } - bestList = mergeRecordings(bestList); + DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); + bestList.mergeRecordingPeriods(exportOptions.maxRecordingGapSeconds*1000); return bestList; } @@ -138,7 +148,7 @@ public class EffortFunctions { if (mapPoints == null) { return null; } - RecordingList periods = new RecordingList(); + RecordingList periods = new RecordingList(dataMap.getDataMapName()); for (OfflineDataMapPoint mapPoint : mapPoints) { periods.add(new RecordingPeriod(mapPoint.getStartTime(), mapPoint.getEndTime())); } @@ -219,117 +229,60 @@ public class EffortFunctions { // PamCalendar.formatDBDateTime(aP.getRecordStop())); // } - tempPeriods = mergeRecordings(tempPeriods); - - return tempPeriods; - } - - /** - * Merge close recordings and discard ones that are too short. - * @param tempPeriods all recording periods, may be from consecutive files. - * @return merged list. - */ - private RecordingList mergeRecordings(RecordingList tempPeriods) { - // now go through those and merge into longer periods where there is no gap between files. - if (tempPeriods == null) { - return null; - } - + tempPeriods.sort(); DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); - - ListIterator iterator = tempPeriods.listIterator(); - RecordingPeriod prevPeriod = null; - while (iterator.hasNext()) { - RecordingPeriod nextPeriod = iterator.next(); - long nextDur = nextPeriod.getRecordStop()-nextPeriod.getRecordStart(); - if (nextDur == 0) { - continue; - } - if (prevPeriod != null) { - long gap = nextPeriod.getRecordStart() - prevPeriod.getRecordStop(); - long prevDur = prevPeriod.getRecordStop()-prevPeriod.getRecordStart(); - if (gap < exportOptions.maxRecordingGapSeconds*1000) { - // ignoring up to 3s gap or a sample error < 2%.Dunno if this is sensible or not. - prevPeriod.setRecordStop(nextPeriod.getRecordStop()); - iterator.remove(); - nextPeriod = prevPeriod; - } - } - prevPeriod = nextPeriod; - } - // now remove ones which are too short even after merging. - iterator = tempPeriods.listIterator(); - while (iterator.hasNext()) { - RecordingPeriod nextPeriod = iterator.next(); - long duration = nextPeriod.getDuration(); - if (duration < exportOptions.minRecordingLengthSeconds*1000L) { - iterator.remove(); - } - } + tempPeriods.mergeRecordingPeriods(exportOptions.maxRecordingGapSeconds*1000); return tempPeriods; } - /** - * Work out whether or not the data are evenly duty cycled by testing the - * distributions of on and off times. - * @param tempPeriods - * @return - */ - private DutyCycleInfo assessDutyCycle(RecordingList tempPeriods) { - if (tempPeriods == null) { - return null; - } - int n = tempPeriods.size(); - if (n < 2) { - return new DutyCycleInfo(false, 0,0,n); - } - double[] ons = new double[n-1]; // ignore the last one since it may be artificially shortened which is OK - double[] gaps = new double[n-1]; - for (int i = 0; i < n-1; i++) { - ons[i] = tempPeriods.get(i).getDuration()/1000.; - gaps[i] = (tempPeriods.get(i+1).getRecordStart()-tempPeriods.get(i).getRecordStop())/1000.; - } - /* now look at how consistent those values are - * But some data gets messed by small gaps, so want to - * remove outliers and concentrate on say 80% of the data. - */ - ons = getDistributionCentre(ons, 80); - gaps = getDistributionCentre(gaps, 80); - Arrays.sort(gaps); +// /** +// * Merge close recordings and discard ones that are too short. +// * @param tempPeriods all recording periods, may be from consecutive files. +// * @return merged list. +// */ +// private RecordingList mergeRecordings(RecordingList tempPeriods) { +// // now go through those and merge into longer periods where there is no gap between files. +// if (tempPeriods == null) { +// return null; +// } +// +// DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); +// +// ListIterator iterator = tempPeriods.listIterator(); +// RecordingPeriod prevPeriod = null; +// while (iterator.hasNext()) { +// RecordingPeriod nextPeriod = iterator.next(); +// long nextDur = nextPeriod.getRecordStop()-nextPeriod.getRecordStart(); +// if (nextDur == 0) { +// continue; +// } +// if (prevPeriod != null) { +// long gap = nextPeriod.getRecordStart() - prevPeriod.getRecordStop(); +// long prevDur = prevPeriod.getRecordStop()-prevPeriod.getRecordStart(); +// if (gap < exportOptions.maxRecordingGapSeconds*1000) { +// // ignoring up to 3s gap or a sample error < 2%.Dunno if this is sensible or not. +// prevPeriod.setRecordStop(nextPeriod.getRecordStop()); +// iterator.remove(); +// nextPeriod = prevPeriod; +// } +// } +// prevPeriod = nextPeriod; +// } +// // now remove ones which are too short even after merging. +// iterator = tempPeriods.listIterator(); +// while (iterator.hasNext()) { +// RecordingPeriod nextPeriod = iterator.next(); +// long duration = nextPeriod.getDuration(); +// if (duration < exportOptions.minRecordingLengthSeconds*1000L) { +// iterator.remove(); +// } +// } +// +// return tempPeriods; +// } - STD std = new STD(); - double onsMean = std.getMean(ons); - double onsSTD = std.getSTD(ons); - double gapsMean = std.getMean(gaps); - double gapsSTD = std.getSTD(gaps); - boolean dutyCycle = onsSTD/onsMean < .05 && gapsSTD/gapsMean < 0.05; - DutyCycleInfo cycleInfo = new DutyCycleInfo(dutyCycle, onsMean, gapsMean, tempPeriods.size()); - return cycleInfo; - } - - /** - * Get the central part of a distribution without any outliers so - * that we can get a better assessment of duty cycle. - * @param data unsorted distribution data. - * @param percent percentage to include (half this removed from top and bottom) - * @return - */ - private double[] getDistributionCentre(double[] data, double percent) { - if (data == null) { - return null; - } - Arrays.sort(data); - int nRem = (int) Math.round(data.length * (100-percent)/200); - int newLen = data.length-nRem*2; - double[] subdata = Arrays.copyOfRange(data, nRem, data.length-2*nRem); - if (subdata.length < 2) { - return data; - } - return subdata; - } - /** * Get data times from any other datamap, since this will generally match the acquisition anyway @@ -360,16 +313,17 @@ public class EffortFunctions { return null; } // get the times out of it. - RecordingList recPeriods = new RecordingList(); + RecordingList recPeriods = new RecordingList(bestMap.getDataMapName()); List mapPoints = bestMap.getMapPoints(); for (OfflineDataMapPoint mapPoint : mapPoints) { recPeriods.add(new RecordingPeriod(mapPoint.getStartTime(), mapPoint.getEndTime())); + recPeriods.add(mapPoint.getStartTime(), mapPoint.getEndTime()); } return recPeriods; } private RecordingList extractTimesFromStatus(ArrayList allStatusData) { - RecordingList tempPeriods = new RecordingList(); + RecordingList tempPeriods = new RecordingList("Data acquisition status"); long dataStart = Long.MAX_VALUE; long dataEnd = Long.MIN_VALUE; Long lastStart = null; diff --git a/src/tethys/deployment/RecordingList.java b/src/tethys/deployment/RecordingList.java index 34b41c1d..038c0e69 100644 --- a/src/tethys/deployment/RecordingList.java +++ b/src/tethys/deployment/RecordingList.java @@ -1,12 +1,43 @@ package tethys.deployment; +import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; -public class RecordingList extends ArrayList { +import PamUtils.PamCalendar; +import pamMaths.STD; + +/** + * Information about periods of effort that might come from either the raw data recordings or + * an analysis of binary data maps. + * @author dg50 + * + */ +public class RecordingList implements Serializable { private static final long serialVersionUID = 1L; + + private ArrayList effortPeriods = new ArrayList(); + + /** + * Name / source of this list. + */ + private String sourceName; + + /** + * @param sourceName + */ + public RecordingList(String sourceName) { + this.sourceName = sourceName; + } + + public RecordingList(String sourceName, ArrayList selectedDeployments) { + this.sourceName = sourceName; + this.effortPeriods = selectedDeployments; + } /** * Get the duration of the recording periods from start to end. @@ -21,27 +52,27 @@ public class RecordingList extends ArrayList { * @return */ public long getStart() { - if (size() == 0) { + if (effortPeriods.size() == 0) { return 0; } - return get(0).getRecordStart(); + return effortPeriods.get(0).getRecordStart(); } /** * get the end of the last in the list. */ public long getEnd() { - if (size() == 0) { + if (effortPeriods.size() == 0) { return 0; } - return get(size()-1).getRecordStop(); + return effortPeriods.get(effortPeriods.size()-1).getRecordStop(); } /** * Sort the list in ascending order. */ public void sort() { - Collections.sort(this, new Comparator() { + Collections.sort(effortPeriods, new Comparator() { @Override public int compare(RecordingPeriod o1, RecordingPeriod o2) { @@ -49,4 +80,172 @@ public class RecordingList extends ArrayList { } }); } + + /** + * Get the coverage as a fraction. This is the sum of the individual periods divided + * by the start to end times + * @return + */ + public double getCoverage() { + long cov = 0; + long durTot = 0; + if (effortPeriods.size() == 0) { + return 0; + } + Iterator it = effortPeriods.iterator(); + while (it.hasNext()) { + RecordingPeriod rp = it.next(); + cov += rp.getDuration(); + } + durTot = getEnd()-getStart(); + return (double) cov / (double) durTot; + } + + /** + * Merge recording periods, with a max gap between periods in milliseconds. + * @param maxGap + * @return the number of periods removed. + */ + public int mergeRecordingPeriods(long maxGap) { + if (effortPeriods.size() < 2) { + return 0; + } + Iterator it = effortPeriods.iterator(); + RecordingPeriod prev = it.next(); + int removed = 0; + while (it.hasNext()) { + RecordingPeriod curr = it.next(); + if (curr.getRecordStart() - prev.getRecordStop() <= maxGap) { + prev.setRecordStop(curr.getRecordStop()); + it.remove(); + removed++; + } + else { + prev = curr; + } + } + return removed; + } + + /** + * Work out whether or not the data are evenly duty cycled by testing the + * distributions of on and off times. + * @param tempPeriods + * @return + */ + public DutyCycleInfo assessDutyCycle() { + if (effortPeriods == null) { + return null; + } + int n = effortPeriods.size(); + if (n < 2) { + return new DutyCycleInfo(false, 0,0,n); + } + double[] ons = new double[n-1]; // ignore the last one since it may be artificially shortened which is OK + double[] gaps = new double[n-1]; + for (int i = 0; i < n-1; i++) { + ons[i] = effortPeriods.get(i).getDuration()/1000.; + gaps[i] = (effortPeriods.get(i+1).getRecordStart()-effortPeriods.get(i).getRecordStop())/1000.; + } + /* now look at how consistent those values are + * But some data gets messed by small gaps, so want to + * remove outliers and concentrate on say 80% of the data. + */ + ons = getDistributionCentre(ons, 80); + gaps = getDistributionCentre(gaps, 80); + Arrays.sort(gaps); + + STD std = new STD(); + double onsMean = std.getMean(ons); + double onsSTD = std.getSTD(ons); + double gapsMean = std.getMean(gaps); + double gapsSTD = std.getSTD(gaps); + boolean dutyCycle = onsSTD/onsMean < .05 && gapsSTD/gapsMean < 0.05; + DutyCycleInfo cycleInfo = new DutyCycleInfo(dutyCycle, onsMean, gapsMean, effortPeriods.size()); + return cycleInfo; + } + /** + * Get the central part of a distribution without any outliers so + * that we can get a better assessment of duty cycle. + * @param data unsorted distribution data. + * @param percent percentage to include (half this removed from top and bottom) + * @return + */ + private double[] getDistributionCentre(double[] data, double percent) { + if (data == null) { + return null; + } + Arrays.sort(data); + int nRem = (int) Math.round(data.length * (100-percent)/200); + int newLen = data.length-nRem*2; + double[] subdata = Arrays.copyOfRange(data, nRem, data.length-2*nRem); + if (subdata.length < 2) { + return data; + } + return subdata; + } + + /** + * @return the sourceName + */ + public String getSourceName() { + return sourceName; + } + + @Override + public String toString() { + if (effortPeriods.size() == 0) { + return "Empty recording list"; + } + String str = String.format("%s: %s to %s, %3.1f%% coverage", getSourceName(), + PamCalendar.formatDBDateTime(getStart()), + PamCalendar.formatDBDateTime(getEnd()), getCoverage()*100); + return str; + } + + /** + * Get similarity to another recording list. 1 = identical, 0 means not even overlapping. + * @param other other recording list. + * @return measure of similarity. + */ + public double getSimilarity(RecordingList other) { + double sim1 = (double) other.duration() / (double) this.duration(); + if (sim1 > 1) { + sim1 = 1./sim1; + } + long overlap = Math.min(other.getEnd(), this.getEnd()) - Math.max(other.getStart(), this.getStart()); + overlap = Math.max(0, overlap); + long longest = Math.max(other.duration(), this.duration()); + double sim2 = (double) overlap / (double) longest; + + return Math.min(sim1, sim2); + } + + /** + * Add a recording period to the list. + * @param recordingPeriod + */ + public void add(RecordingPeriod recordingPeriod) { + effortPeriods.add(recordingPeriod); + } + + /** + * Add a recording period to the list. + * @param startTime + * @param endTime + */ + public void add(long startTime, long endTime) { + add (new RecordingPeriod(startTime, endTime)); + } + + public int size() { + return effortPeriods.size(); + } + + /** + * @return the effortPeriods + */ + public ArrayList getEffortPeriods() { + return effortPeriods; + } } diff --git a/src/tethys/deployment/RecordingPeriod.java b/src/tethys/deployment/RecordingPeriod.java index ad010827..ae985347 100644 --- a/src/tethys/deployment/RecordingPeriod.java +++ b/src/tethys/deployment/RecordingPeriod.java @@ -1,5 +1,6 @@ package tethys.deployment; +import PamUtils.PamCalendar; import tethys.niluswraps.PDeployment; public class RecordingPeriod { @@ -71,5 +72,11 @@ public class RecordingPeriod { selected = !selected; return selected; } + + @Override + public String toString() { + return String.format("%s to %s, %s", PamCalendar.formatDBDateTime(recordStart), + PamCalendar.formatDBDateTime(recordStop), PamCalendar.formatDuration(getDuration())); + } } diff --git a/src/tethys/deployment/swing/EffortProblemDialog.java b/src/tethys/deployment/swing/EffortProblemDialog.java new file mode 100644 index 00000000..5fafa71b --- /dev/null +++ b/src/tethys/deployment/swing/EffortProblemDialog.java @@ -0,0 +1,163 @@ +package tethys.deployment.swing; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Window; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.border.TitledBorder; + +import PamUtils.PamCalendar; +import PamView.dialog.PamDialog; +import PamView.dialog.PamGridBagContraints; +import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; + +/** + * Handle problems when binary and raw effort don't add up + * @author dg50 + * + */ +public class EffortProblemDialog extends PamDialog { + + private JRadioButton useRaw, useBinary, useNeither; + + private JLabel generalInfo; + + private InfoSet[] infoSets = new InfoSet[2]; + + private RecordingList chosenList; + + private DeploymentOverview deploymentOverview; + + private static EffortProblemDialog singleInstance; + + private static final String[] setNames = {"Raw data", "Binary data"}; + + private EffortProblemDialog(Window parentFrame) { + super(parentFrame, "Deployment Effort", false); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.setBorder(new TitledBorder("Effort information")); + String info = "There is a mismatch between the time period covered by the raw
    " + + "data recordings and the time covered in the binary data.
    " + + "Select the one you wish to use, or Cancel and sort out your data
    " + + "prior to restarting the Tethys export process"; + generalInfo = new JLabel(info); +// generalInfo.setBorder(new TitledBorder("General")); + mainPanel.add(generalInfo, BorderLayout.NORTH); + JPanel botPanel = new JPanel(new GridLayout(2, 1)); + mainPanel.add(botPanel, BorderLayout.CENTER); + ButtonGroup bg = new ButtonGroup(); + for (int i = 0; i < 2; i++) { + GridBagConstraints c = new PamGridBagContraints(); + JPanel subPanel = new JPanel(new GridBagLayout()); + botPanel.add(subPanel); + infoSets[i] = new InfoSet(setNames[i]); + c.gridwidth = 2; + subPanel.add(infoSets[i].name, c); + c.gridx += c.gridwidth; + subPanel.add(infoSets[i].select, c); + c.gridx = 0; + c.gridy++; + c.gridwidth = 1; + subPanel.add(new JLabel("Start: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].start, c); + c.gridx++; + subPanel.add(new JLabel("End: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].end, c); + c.gridy++; + c.gridx = 0; + subPanel.add(new JLabel("Duration: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].duration, c); + c.gridx++; + subPanel.add(new JLabel("Coverage: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].occupancy, c); + + bg.add(infoSets[i].select); + } + + setDialogComponent(mainPanel); + setResizable(true); + } + + public static RecordingList showDialog(Window parentFrame, DeploymentOverview deploymentOverview) { + singleInstance = new EffortProblemDialog(parentFrame); + singleInstance.setData(deploymentOverview); + singleInstance.setVisible(true); + return singleInstance.chosenList; + } + + private void setData(DeploymentOverview deploymentOverview) { + this.deploymentOverview = deploymentOverview; + RecordingList rl; + for (int i = 0; i < 2; i++) { + if (i == 0) { + rl = deploymentOverview.getRawDataList(); + } + else { + rl = deploymentOverview.getBinaryDataList(); + } + infoSets[i].start.setText(PamCalendar.formatDBDateTime(rl.getStart())); + infoSets[i].end.setText(PamCalendar.formatDBDateTime(rl.getEnd())); + infoSets[i].duration.setText(PamCalendar.formatDuration(rl.duration())); + infoSets[i].occupancy.setText(String.format("%3.0f%%", rl.getCoverage()*100.)); + } + invalidate(); + pack(); + } + + @Override + public boolean getParams() { + if (infoSets[0].select.isSelected()) { + chosenList = deploymentOverview.getRawDataList(); + return true; + } + if (infoSets[1].select.isSelected()) { + chosenList = deploymentOverview.getBinaryDataList(); + return true; + } + return false; + } + + @Override + public void cancelButtonPressed() { + // TODO Auto-generated method stub + + } + + @Override + public void restoreDefaultSettings() { + // TODO Auto-generated method stub + + } + + private class InfoSet { + JLabel name, start, end, duration, occupancy; + JCheckBox select; + /** + * + */ + public InfoSet(String name) { + super(); + this.name = new JLabel(name); + this.start = new JLabel(" "); + this.end = new JLabel(" "); + this.select = new JCheckBox("Select " + name); + duration = new JLabel(" "); + occupancy = new JLabel(" "); + } + } + +} diff --git a/src/tethys/deployment/swing/ProjectInformationPanel.java b/src/tethys/deployment/swing/ProjectInformationPanel.java index 1ced05b1..12a88a9a 100644 --- a/src/tethys/deployment/swing/ProjectInformationPanel.java +++ b/src/tethys/deployment/swing/ProjectInformationPanel.java @@ -23,6 +23,7 @@ import nilus.Deployment; import tethys.TethysControl; import tethys.swing.NewProjectDialog; import tethys.swing.SelectProjectDialog; +import tethys.tooltips.TethysTips; /** * Panel for entering project information @@ -109,6 +110,12 @@ public class ProjectInformationPanel { }); } + project.setToolTipText(TethysTips.findTip(Deployment.class, "Project")); + cruise.setToolTipText(TethysTips.findTip(Deployment.class, "Cruise")); + region.setToolTipText(TethysTips.findTip(Deployment.class, "Region")); + site.setToolTipText(TethysTips.findTip(Deployment.class, "Site")); + + } /** diff --git a/src/tethys/deployment/swing/RecordingGapDialog.java b/src/tethys/deployment/swing/RecordingGapDialog.java index 399eb3bc..9915a0e4 100644 --- a/src/tethys/deployment/swing/RecordingGapDialog.java +++ b/src/tethys/deployment/swing/RecordingGapDialog.java @@ -36,7 +36,7 @@ public class RecordingGapDialog extends PamDialog { c.gridx++; mainPanel.add(new JLabel(" seconds", JLabel.RIGHT), c); - maxGap.setToolTipText("Maximum gap between recording periods. Periods with a gap less than this will be counted as one"); + maxGap.setToolTipText("Maximum gap between recording periods. Sequential periods with a gap less than this will be counted as one"); minLength.setToolTipText("Minimum recording length. Recording sections shorter than this will be ignored"); setDialogComponent(mainPanel); @@ -80,6 +80,7 @@ public class RecordingGapDialog extends PamDialog { @Override public void restoreDefaultSettings() { DeploymentExportOpts defaults = new DeploymentExportOpts(); + setParams(defaults); } } diff --git a/src/tethys/detection/BinnedGranularityHandler.java b/src/tethys/detection/BinnedGranularityHandler.java index db984b27..13b92d58 100644 --- a/src/tethys/detection/BinnedGranularityHandler.java +++ b/src/tethys/detection/BinnedGranularityHandler.java @@ -6,11 +6,16 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; + import java.util.Set; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import nilus.SpeciesIDType; import tethys.TethysControl; import tethys.TethysTimeFuncs; @@ -54,6 +59,8 @@ public class BinnedGranularityHandler extends GranularityHandler { public void prepare(long timeMillis) { // long binStart = DetectionsHandler.roundDownBinStart(timeMillis, binDurationMillis); // startBin(binStart); +// startBin(timeMillis); + currentDetections.clear(); } // private void startBin(long timeMillis) { @@ -169,4 +176,9 @@ public class BinnedGranularityHandler extends GranularityHandler { return closeBins(timeMillis); } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return contractDetection(detections, det); + } + } diff --git a/src/tethys/detection/CallGranularityHandler.java b/src/tethys/detection/CallGranularityHandler.java index 4ff9a888..03397596 100644 --- a/src/tethys/detection/CallGranularityHandler.java +++ b/src/tethys/detection/CallGranularityHandler.java @@ -3,6 +3,7 @@ package tethys.detection; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import tethys.TethysControl; import tethys.output.StreamExportParams; import tethys.output.TethysExportParams; @@ -37,4 +38,9 @@ public class CallGranularityHandler extends GranularityHandler { return null; } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return expandEffort(detections, det); + } + } diff --git a/src/tethys/detection/DetectionExportProgress.java b/src/tethys/detection/DetectionExportProgress.java index abfa74b8..24a982a5 100644 --- a/src/tethys/detection/DetectionExportProgress.java +++ b/src/tethys/detection/DetectionExportProgress.java @@ -6,10 +6,11 @@ import tethys.niluswraps.PDeployment; public class DetectionExportProgress { public static final int STATE_GATHERING = 1; - public static final int STATE_CANCELED = 2; - public static final int STATE_COMPLETE = 3; - public static final int STATE_WRITING = 4; - public static final int STATE_COUNTING = 5; + public static final int STATE_COUNTING = 2; + public static final int STATE_WRITING = 3; + public static final int STATE_CANCELED = 4; + public static final int STATE_COMPLETE = 5; + public PDeployment currentDeployment; public Detections currentDetections; public long lastUnitTime; @@ -17,12 +18,18 @@ public class DetectionExportProgress { public int exportCount; public int skipCount; public int state; + public int totalDeployments, deploymentsDone; + public int nMapPoints; + public int doneMapPoints; - public DetectionExportProgress(PDeployment currentDeployment, Detections currentDetections, long lastUnitTime, + public DetectionExportProgress(PDeployment currentDeployment, Detections currentDetections, int nMapPoints, int doneMapPoints, + long lastUnitTime, long totalUnits, int exportCount, int skipCount, int state) { super(); this.currentDeployment = currentDeployment; this.currentDetections = currentDetections; + this.nMapPoints = nMapPoints; + this.doneMapPoints = doneMapPoints; this.lastUnitTime = lastUnitTime; this.totalUnits = totalUnits; this.exportCount = exportCount; diff --git a/src/tethys/detection/DetectionsHandler.java b/src/tethys/detection/DetectionsHandler.java index f797c8ee..cd995bbb 100644 --- a/src/tethys/detection/DetectionsHandler.java +++ b/src/tethys/detection/DetectionsHandler.java @@ -1,9 +1,13 @@ package tethys.detection; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.ListIterator; import javax.swing.SwingWorker; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; import PamController.PamControlledUnit; import PamController.PamController; @@ -32,6 +36,7 @@ import nilus.Detections; import nilus.GranularityEnumType; import nilus.Helper; import tethys.Collection; +import tethys.CollectionHandler; import tethys.TethysControl; import tethys.TethysTimeFuncs; import tethys.dbxml.DBXMLConnect; @@ -39,7 +44,6 @@ import tethys.dbxml.TethysException; import tethys.deployment.DeploymentHandler; import tethys.niluswraps.PDeployment; import tethys.niluswraps.PDetections; -import tethys.output.DatablockSynchInfo; import tethys.output.StreamExportParams; import tethys.output.TethysExportParams; import tethys.pamdata.TethysDataProvider; @@ -54,9 +58,7 @@ import tethys.swing.export.DetectionsExportWizard; * @author dg50 * */ -public class DetectionsHandler { - - private TethysControl tethysControl; +public class DetectionsHandler extends CollectionHandler { public int uniqueDetectionsId=1; public int uniqueDetectionId; @@ -65,12 +67,14 @@ public class DetectionsHandler { private ExportWorker exportWorker; + public static final String helpPoint = "utilities.tethys.docs.detect_localize"; + /** * * @param tethysControl */ public DetectionsHandler(TethysControl tethysControl) { - super(); + super(tethysControl, Collection.Detections); this.tethysControl = tethysControl; } @@ -296,9 +300,14 @@ public class DetectionsHandler { viewerLoadPolicy = ViewerLoadPolicy.LOAD_UTCNORMAL; } GranularityHandler granularityHandler = GranularityHandler.getHandler(streamExportParams.granularity, tethysControl, dataBlock, exportParams, streamExportParams); + int totalMaps = 0; + int totalMappedPoints = 0; + int totalLoadedDatas = 0; + int totalMapPoints = dataMap.getNumMapPoints(); + int doneMapPoints = 0; for (PDeployment deployment : deployments) { int documentCount = 0; - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null, totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); granularityHandler.prepare(deployment.getAudioStart()); @@ -308,9 +317,10 @@ public class DetectionsHandler { for (OfflineDataMapPoint mapPoint : mapPoints) { if (!activeExport) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_CANCELED); exportObserver.update(prog); + break; } if (mapPoint.getEndTime() < deployment.getAudioStart()) { @@ -319,10 +329,13 @@ public class DetectionsHandler { if (mapPoint.getStartTime() >= deployment.getAudioEnd()) { break; } + totalMaps ++; + totalMappedPoints += mapPoint.getNDatas(); dataBlock.loadViewerData(mapPoint.getStartTime(), mapPoint.getEndTime(), null); ArrayList dataCopy = dataBlock.getDataCopy(deployment.getAudioStart(), deployment.getAudioEnd(), true, dataSelector); -// System.out.printf("%d loaded from %s to %s %d kept\n", dataBlock.getUnitsCount(), PamCalendar.formatDateTime(mapPoint.getStartTime()), -// PamCalendar.formatDateTime(mapPoint.getEndTime()), dataCopy.size()); + totalLoadedDatas += dataCopy.size(); + System.out.printf("%d loaded from %s to %s %d kept\n", dataBlock.getUnitsCount(), PamCalendar.formatDateTime(mapPoint.getStartTime()), + PamCalendar.formatDateTime(mapPoint.getEndTime()), dataCopy.size()); skipCount += dataBlock.getUnitsCount() - dataCopy.size(); for (PamDataUnit dataUnit : dataCopy) { /* @@ -334,7 +347,7 @@ public class DetectionsHandler { documentCount+=dets.length; if (exportCount % 100 == 0) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); } @@ -345,15 +358,17 @@ public class DetectionsHandler { // onEffort.getDetection().add(det); lastUnitTime = dataUnit.getTimeMilliseconds(); } - - prog = new DetectionExportProgress(deployment, null, + doneMapPoints++; + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); if (viewerLoadPolicy == ViewerLoadPolicy.LOAD_ALWAYS_EVERYTHING) { break; } - + if (!activeExport) { + return 0; + } } Detection dets[] = granularityHandler.cleanup(deployment.getAudioEnd()); if (dets != null) { @@ -397,28 +412,33 @@ public class DetectionsHandler { if (viewerLoadPolicy == null) { viewerLoadPolicy = ViewerLoadPolicy.LOAD_UTCNORMAL; } + int totalMapPoints = dataMap.getNumMapPoints(); + int doneMapPoints = 0; GranularityHandler granularityHandler = GranularityHandler.getHandler(streamExportParams.granularity, tethysControl, dataBlock, exportParams, streamExportParams); for (PDeployment deployment : deployments) { int documentCount = 0; - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); granularityHandler.prepare(deployment.getAudioStart()); - if (currentDetections == null) { - currentDetections = startDetectionsDocument(deployment, dataBlock, streamExportParams); - currentDetections.getEffort().setStart(TethysTimeFuncs.xmlGregCalFromMillis(deployment.getAudioStart())); - } // export everything in that deployment. // need to loop through all map points in this interval. List mapPoints = dataMap.getMapPoints(); for (OfflineDataMapPoint mapPoint : mapPoints) { if (!activeExport) { - prog = new DetectionExportProgress(deployment, currentDetections, + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_CANCELED); exportObserver.update(prog); + break; } + if (currentDetections == null) { + // needed in inner loop in case doc gets written at 500000. + currentDetections = startDetectionsDocument(deployment, dataBlock, streamExportParams); + currentDetections.getEffort().setStart(TethysTimeFuncs.xmlGregCalFromMillis(deployment.getAudioStart())); + } + if (mapPoint.getEndTime() < deployment.getAudioStart()) { continue; } @@ -427,6 +447,7 @@ public class DetectionsHandler { } dataBlock.loadViewerData(mapPoint.getStartTime(), mapPoint.getEndTime(), null); ArrayList dataCopy = dataBlock.getDataCopy(deployment.getAudioStart(), deployment.getAudioEnd(), true, dataSelector); + Collections.sort(dataCopy); skipCount += dataBlock.getUnitsCount() - dataCopy.size(); DetectionGroup onEffort = currentDetections.getOnEffort(); for (PamDataUnit dataUnit : dataCopy) { @@ -442,24 +463,27 @@ public class DetectionsHandler { } } if (exportCount % 100 == 0) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, currentDetections, totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_GATHERING); exportObserver.update(prog); } lastUnitTime = dataUnit.getTimeMilliseconds(); } - prog = new DetectionExportProgress(deployment, currentDetections, + doneMapPoints ++; + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_GATHERING); exportObserver.update(prog); - if (documentCount > 500000 && mapPoint != dataMap.getLastMapPoint()) { - prog = new DetectionExportProgress(deployment, currentDetections, + if (documentCount > 50000000 && mapPoint != dataMap.getLastMapPoint()) { + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_WRITING); exportObserver.update(prog); closeDetectionsDocument(currentDetections, mapPoint.getEndTime()); try { - dbxmlConnect.postAndLog(currentDetections); + if (checkDetectionsDocument(currentDetections, granularityHandler)) { + dbxmlConnect.postAndLog(currentDetections); + } } catch (TethysException e) { tethysControl.showException(e); } @@ -469,8 +493,14 @@ public class DetectionsHandler { if (viewerLoadPolicy == ViewerLoadPolicy.LOAD_ALWAYS_EVERYTHING) { break; } + if (!activeExport) { + break; + } + } + + if (!activeExport) { + return DetectionExportProgress.STATE_CANCELED; } - if (currentDetections != null) { Detection dets[] = granularityHandler.cleanup(deployment.getAudioEnd()); @@ -481,11 +511,13 @@ public class DetectionsHandler { currentDetections.getOnEffort().getDetection().add(dets[dd]); } } - prog = new DetectionExportProgress(deployment, currentDetections, + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_WRITING); closeDetectionsDocument(currentDetections, deployment.getAudioEnd()); try { - dbxmlConnect.postAndLog(currentDetections); + if (checkDetectionsDocument(currentDetections, granularityHandler)) { + dbxmlConnect.postAndLog(currentDetections); + } } catch (TethysException e) { tethysControl.showException(e); } @@ -493,7 +525,7 @@ public class DetectionsHandler { } } - prog = new DetectionExportProgress(null, null, + prog = new DetectionExportProgress(null, null,totalMapPoints, totalMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COMPLETE); exportObserver.update(prog); return DetectionExportProgress.STATE_COMPLETE; @@ -552,7 +584,7 @@ public class DetectionsHandler { supportSoft.setVersion(getSupportSoftwareVersion(dataBlock)); supSoft.add(supportSoft); detections.setAlgorithm(algorithm); - detections.setUserId("Unknown user"); + detections.setUserId("PAMGuard user"); detections.setEffort(getDetectorEffort(deployment, dataBlock, exportParams)); return detections; @@ -567,6 +599,50 @@ public class DetectionsHandler { private void closeDetectionsDocument(Detections detections, Long audioEnd) { detections.getEffort().setEnd(TethysTimeFuncs.xmlGregCalFromMillis(audioEnd)); } + + /** + * Run some checks on the Detections document prior to submission.
    + * Currently, is is just a check that the detections are within the effort times. + * @param detections Detections document + * @return false if there is an outstanding problem. + */ + private boolean checkDetectionsDocument(Detections detections, GranularityHandler granularityHandler) { + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + DetectionGroup dets = detections.getOnEffort(); + List detList = dets.getDetection(); + ListIterator detIt = detList.listIterator(); + while (detIt.hasNext()) { + Detection det = detIt.next(); + XMLGregorianCalendar detS = det.getStart(); + XMLGregorianCalendar detE = det.getEnd(); + if (effStart.compare(detS) == DatatypeConstants.GREATER) { + if (granularityHandler.autoEffortFix(detections, det)) { + continue; + } + String str = String.format("A Detection at %s starts before the document effort start at %s
    " + + "Do you want to adjust the effort start time or abort export ?", detS, effStart); + int ans = WarnOnce.showNamedWarning("TETHYSDETNOTINEFFORT", tethysControl.getGuiFrame(), "Detection Document Warning", str, WarnOnce.OK_CANCEL_OPTION); + if (ans == WarnOnce.CANCEL_OPTION) { + return false; + } + detections.getEffort().setStart(detS); + } + if (effEnd.compare(detE) == DatatypeConstants.LESSER) { + if (granularityHandler.autoEffortFix(detections, det)) { + continue; + } + String str = String.format("A Detection at %s-%s ends
    after the document effort end at %s
    " + + "Do you want to adjust the effort end time or abort export ?", detS, detE, effStart); + int ans = WarnOnce.showNamedWarning("TETHYSDETNOTINEFFORT", tethysControl.getGuiFrame(), "Detection Document Warning", str, WarnOnce.OK_CANCEL_OPTION); + if (ans == WarnOnce.CANCEL_OPTION) { + return false; + } + detections.getEffort().setEnd(detE); + } + } + return true; + } /** * Worker thread for exporting detections. @@ -599,13 +675,16 @@ public class DetectionsHandler { protected Integer doInBackground() throws Exception { Integer ans = null; try { - int count = countDetections(dataBlock, exportParams, exportObserver); - String msg = String.format("Do you want to go ahead and output %d %s detections to Tethys?", - count, exportParams.granularity); - int doit = WarnOnce.showWarning("Tethys Detections Export", msg, WarnOnce.OK_CANCEL_OPTION); - if (doit == WarnOnce.OK_OPTION) { +// int count = countDetections(dataBlock, exportParams, exportObserver); +// if (activeExport == false) { +// return 0; +// } +// String msg = String.format("Do you want to go ahead and output %d %s detections to Tethys?", +// count, exportParams.granularity); +// int doit = WarnOnce.showWarning("Tethys Detections Export", msg, WarnOnce.OK_CANCEL_OPTION); +// if (doit == WarnOnce.OK_OPTION) { ans = exportDetections(dataBlock, exportParams, this); - } +// } } catch (Exception e) { e.printStackTrace(); @@ -616,7 +695,7 @@ public class DetectionsHandler { @Override protected void done() { // this. - DetectionExportProgress prog = new DetectionExportProgress(null, null, 0, 0, 0, 0, DetectionExportProgress.STATE_COMPLETE); + DetectionExportProgress prog = new DetectionExportProgress(null, null, 0, 0, 0, 0, 0, 0, DetectionExportProgress.STATE_COMPLETE); tethysControl.exportedDetections(dataBlock); exportObserver.update(prog); TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); @@ -661,4 +740,10 @@ public class DetectionsHandler { DetectionsExportWizard.showDialog(tethysControl.getGuiFrame(), tethysControl, dataBlock); } + + + @Override + public String getHelpPoint() { + return helpPoint; + } } diff --git a/src/tethys/detection/EncounterGranularityHandler.java b/src/tethys/detection/EncounterGranularityHandler.java index 57dd8757..54e70db5 100644 --- a/src/tethys/detection/EncounterGranularityHandler.java +++ b/src/tethys/detection/EncounterGranularityHandler.java @@ -11,6 +11,7 @@ import java.util.Map.Entry; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import nilus.SpeciesIDType; import tethys.TethysControl; import tethys.TethysTimeFuncs; @@ -48,7 +49,7 @@ public class EncounterGranularityHandler extends GranularityHandler { @Override public void prepare(long timeMillis) { - + currentDetections.clear(); } @Override @@ -77,6 +78,7 @@ public class EncounterGranularityHandler extends GranularityHandler { currentDetections.put(groupName, det); } else { + // add to current detection. Set new end time and increment count det.setEnd(TethysTimeFuncs.xmlGregCalFromMillis(dataUnit.getEndTimeInMilliseconds())); int count = det.getCount().intValue() + 1; @@ -122,34 +124,17 @@ public class EncounterGranularityHandler extends GranularityHandler { } } - // private Detection[] checkCurrentEncounters(long timeMilliseconds) { - // if (currentDetections == null || currentDetections.size() == 0) { - // return null; - // } - // int nGood = 0; - // Detection[] newDetections = new Detection[currentDetections.size()]; - // Iterator detIt = currentDetections.iterator(); - // while (detIt.hasNext()) { - // Detection aDet = detIt.next(); - // Long detEnd = TethysTimeFuncs.millisFromGregorianXML(aDet.getEnd()); - // if (timeMilliseconds-detEnd > maxGapMillis) { - // detIt.remove(); - // newDetections[nGood++] = aDet; - // } - // } - // - // if (nGood == 0) { - // return null; - // } - // else { - // return Arrays.copyOf(newDetections, nGood); - // } - // } @Override public Detection[] cleanup(long timeMillis) { // get everything still on the go. - return checkCurrentEncounters(timeMillis + maxGapMillis); + return checkCurrentEncounters(timeMillis + maxGapMillis*10); } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return expandEffort(detections, det); + } + + } diff --git a/src/tethys/detection/GranularityHandler.java b/src/tethys/detection/GranularityHandler.java index d172405e..47ef87e3 100644 --- a/src/tethys/detection/GranularityHandler.java +++ b/src/tethys/detection/GranularityHandler.java @@ -1,8 +1,16 @@ package tethys.detection; +import java.util.List; +import java.util.ListIterator; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; + import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.DetectionGroup; +import nilus.Detections; import nilus.GranularityEnumType; import tethys.TethysControl; import tethys.output.StreamExportParams; @@ -118,4 +126,93 @@ public abstract class GranularityHandler { } return null; } + + /** + * Automatically fix mismatches between effort and detections. This will be called if a + * detection or part of a detection is outside of the start and end defined by the effort. If it's a + * small difference, i.e. if the detection at least overlaps the effort then it can be automatically + * fixed by truncating the detection (for binned types) or by a small extension to the effort (for encounter + * and call types). + * @param detections nilus Detections object + * @param det a single detection + * @return true if it was fixed automatically. False otherwise. + */ + protected abstract boolean autoEffortFix(Detections detections, Detection det); + + /** + * Check that the detection at least overlaps the effort period. + * @param detections nilus Detections object + * @param det a single detection + * @return true if the overlap + */ + protected boolean effortOverlap(Detections detections, Detection det) { + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + if (effStart.compare(detEnd) == DatatypeConstants.GREATER) { + return false; + } + if (effEnd.compare(detStart) == DatatypeConstants.LESSER) { + return false; + } + return true; + } + + /** + * Fix effort / detection problem but contracting the start / end times of the detection + * @param detections nilus Detections object + * @param det a single detection + * @return true if fixed automatically + */ + protected boolean contractDetection(Detections detections, Detection det) { + if (effortOverlap(detections, det) == false) { + return false; + } + // at least some overlap, so fix it. + // going to fix it my shortening the detection, and leave the effort alone. + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + + + if (effStart.compare(detStart) == DatatypeConstants.GREATER) { + System.out.printf("Fix Detections change detection start from %s to %s\n", detStart, effStart); + det.setStart(effStart); + } + if (effEnd.compare(detEnd) == DatatypeConstants.LESSER) { + System.out.printf("Fix Detections change detection end from %s to %s\n", detEnd, effEnd); + det.setEnd(effEnd); + } + return true; + } + + /** + * Fix effort / detection problem but expanding the start / end times of the effort + * @param detections nilus Detections object + * @param det a single detection + * @return true if fixed automatically + */ + protected boolean expandEffort(Detections detections, Detection det) { + if (effortOverlap(detections, det) == false) { + return false; + } + // at least some overlap, so fix it. + // going to fix it my shortening the detection, and leave the effort alone. + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + + if (effStart.compare(detStart) == DatatypeConstants.GREATER) { + System.out.printf("Fix Detections change effort start from %s to %s\n", effStart, detStart); + detections.getEffort().setStart(detStart); + } + if (effEnd.compare(detEnd) == DatatypeConstants.LESSER) { + System.out.printf("Fix Detections change effort end from %s to %s\n", effEnd, detEnd); + detections.getEffort().setEnd(detEnd); + } + return true; + } } diff --git a/src/tethys/localization/LocalizationHandler.java b/src/tethys/localization/LocalizationHandler.java new file mode 100644 index 00000000..3751df93 --- /dev/null +++ b/src/tethys/localization/LocalizationHandler.java @@ -0,0 +1,17 @@ +package tethys.localization; + +//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; +// } +} diff --git a/src/tethys/niluswraps/NilusChecker.java b/src/tethys/niluswraps/NilusChecker.java index 43a53e97..e9107540 100644 --- a/src/tethys/niluswraps/NilusChecker.java +++ b/src/tethys/niluswraps/NilusChecker.java @@ -56,7 +56,7 @@ public class NilusChecker { for (Field f : emptyFields) { msg += String.format("
    Field %s in object %s", f.getName(), f.getDeclaringClass().getName()); } - msg += "

    It is likely that this document will fail to write to the Tethys database."; + msg += "

    It is possible that this document will fail to write to the Tethys database."; String tit = "Incomplete Tethys data"; WarnOnce.showWarning(owner, tit, msg, WarnOnce.WARNING_MESSAGE); return false; diff --git a/src/tethys/niluswraps/NilusSettingsWrapper.java b/src/tethys/niluswraps/NilusSettingsWrapper.java index 68fa9628..6cb3f0ce 100644 --- a/src/tethys/niluswraps/NilusSettingsWrapper.java +++ b/src/tethys/niluswraps/NilusSettingsWrapper.java @@ -129,7 +129,9 @@ public class NilusSettingsWrapper implements Serializable, Clo Document doc = builder.parse(new InputSource(new StringReader(xmlString))); return doc; } catch (Exception e) { - e.printStackTrace(); + System.out.println(e.getMessage()); + System.out.println("Nilus Settings wrapper - Error parsing string\n" + xmlString); +// e.printStackTrace(); } return null; } diff --git a/src/tethys/output/StreamExportParams.java b/src/tethys/output/StreamExportParams.java index aabbff2d..ca4b18ed 100644 --- a/src/tethys/output/StreamExportParams.java +++ b/src/tethys/output/StreamExportParams.java @@ -98,7 +98,11 @@ public class StreamExportParams implements Serializable { * @return */ public DescriptionType getNilusDetectionDescription() { - return getDetectionDescription().getDescription(); + WrappedDescriptionType desc = getDetectionDescription(); + if (desc == null) { + return null; + } + return desc.getDescription(); } } diff --git a/src/tethys/output/TethysExportParams.java b/src/tethys/output/TethysExportParams.java index 5eb129cb..a0ff683b 100644 --- a/src/tethys/output/TethysExportParams.java +++ b/src/tethys/output/TethysExportParams.java @@ -45,6 +45,8 @@ public class TethysExportParams implements Serializable, Cloneable{ private String datasetName; public boolean listDocsInPamguard; + + private String effortSourceName; /** @@ -121,6 +123,22 @@ public class TethysExportParams implements Serializable, Cloneable{ return streamParamsMap.get(longDataName); } + /** + * Source name for type of effort. + * @param sourceName + */ + public void setEffortSourceName(String sourceName) { + this.effortSourceName = sourceName; + } + + /** + * Source name for type of effort. + * @return the effortSourceName + */ + public String getEffortSourceName() { + return effortSourceName; + } + } diff --git a/src/tethys/pamdata/AutoTethysProvider.java b/src/tethys/pamdata/AutoTethysProvider.java index 1c31671e..8c40e41b 100644 --- a/src/tethys/pamdata/AutoTethysProvider.java +++ b/src/tethys/pamdata/AutoTethysProvider.java @@ -286,6 +286,7 @@ abstract public class AutoTethysProvider implements TethysDataProvider { detParams.setMaxFreqHz(freqs[1]); } double ampli = dataUnit.getAmplitudeDB(); + ampli = roundDecimalPlaces(ampli, 1); detParams.setReceivedLevelDB(ampli); // DataUnitBaseData basicData = dataUnit.getBasicData(); gotTonalContour(dataUnit, detParams); @@ -497,5 +498,12 @@ abstract public class AutoTethysProvider implements TethysDataProvider { return true; } + /** + * @return the tethysControl + */ + public TethysControl getTethysControl() { + return tethysControl; + } + } diff --git a/src/tethys/species/DataBlockSpeciesManager.java b/src/tethys/species/DataBlockSpeciesManager.java index 7817cf82..28c89a23 100644 --- a/src/tethys/species/DataBlockSpeciesManager.java +++ b/src/tethys/species/DataBlockSpeciesManager.java @@ -97,7 +97,10 @@ abstract public class DataBlockSpeciesManager { public SpeciesMapItem getSpeciesItem(T dataUnit) { String speciesString = getSpeciesCode(dataUnit); if (speciesString == null) { - return getDefaultDefaultSpecies(); + SpeciesMapItem def = getDefaultDefaultSpecies(); + if (def != null) { + speciesString = def.getPamguardName(); + } } DataBlockSpeciesMap speciesMap = getDatablockSpeciesMap(); if (speciesMap == null) { @@ -127,7 +130,26 @@ abstract public class DataBlockSpeciesManager { if (allCodes.size() == 0) { allCodes.add("Unknown"); } - return allCodes; + return makeUniqueList(allCodes); + } + + /** + * Make sure all entries in an array list are unique. + * @param list + * @return updated list. + */ + public ArrayList makeUniqueList(ArrayList list) { + if (list == null) { + return null; + } + ArrayList newList = new ArrayList(); + for (String aStr : list) { + if (newList.contains(aStr)) { + continue; + } + newList.add(aStr); + } + return newList; } public DataBlockSpeciesMap getDatablockSpeciesMap() { diff --git a/src/tethys/species/ITISFunctions.java b/src/tethys/species/ITISFunctions.java index 763c99f3..252e3bb8 100644 --- a/src/tethys/species/ITISFunctions.java +++ b/src/tethys/species/ITISFunctions.java @@ -70,7 +70,7 @@ public class ITISFunctions { // PAMGuardXMLPreview xmlPreview = new PAMGuardXMLPreview(null, "returned", qResult.queryResult) PamguardXMLWriter pamXMLWriter = PamguardXMLWriter.getXMLWriter(); String fDoc = pamXMLWriter.getAsString(doc, true); - System.out.println(fDoc); +// System.out.println(fDoc); String tsn = dbQueries.getElementData(docEl, "tsn"); if (tsn == null) { @@ -91,7 +91,46 @@ public class ITISFunctions { return new TethysITISResult(itisCode, taxunit, latin, vernacular); } + /** + * Search species codes. If the search term is a valid Integer number + * then it's assumed to be an ITIS code and the function should + * return a single map item. If it's non-integer, it's assumed to + * be a common or latin name search + * @param searchTerm + * @return array list of possible matches. + */ public ArrayList searchSpecies(String searchTerm) { + Integer intVal = null; + try { + intVal = Integer.valueOf(searchTerm); + } + catch (NumberFormatException e) { + intVal = null; + } + if (intVal != null) { + return searchCodes(intVal); + } + else { // assume name search + return searchNames(searchTerm); + } + } + + private ArrayList searchCodes(Integer intCode) { + ArrayList mapItems = new ArrayList(); + TethysITISResult result = getITISInformation(intCode); + if (result != null) { + mapItems.add(new SpeciesMapItem(intCode, "", "", result.getLatin(), result.getVernacular())); + } + return mapItems; + } + + /** + * Search common and latin names for partial matched of the search term + * and return an array list of all possible matches. + * @param searchTerm + * @return + */ + public ArrayList searchNames(String searchTerm) { ArrayList items = new ArrayList(); String xQ = "let $target := \"thespeciessearchterm\" \r\n" + "return\r\n" diff --git a/src/tethys/species/ITISTypes.java b/src/tethys/species/ITISTypes.java index 771b180d..f1503c2e 100644 --- a/src/tethys/species/ITISTypes.java +++ b/src/tethys/species/ITISTypes.java @@ -7,8 +7,8 @@ package tethys.species; */ public class ITISTypes { - public static final int OTHER = 0; - public static final int ANTHROPOGENIC = 1; + public static final int OTHER = -10; + public static final int ANTHROPOGENIC = 1758; public static final String getName(int code) { switch (code) { diff --git a/src/tethys/species/SpeciesMapItem.java b/src/tethys/species/SpeciesMapItem.java index f1595a7a..82edd9b4 100644 --- a/src/tethys/species/SpeciesMapItem.java +++ b/src/tethys/species/SpeciesMapItem.java @@ -41,6 +41,14 @@ public class SpeciesMapItem implements Serializable, Cloneable { */ private String callType; + /** + * + * @param itisCode + * @param callType + * @param pamguardName + * @param latinName + * @param commonName + */ public SpeciesMapItem(int itisCode, String callType, String pamguardName, String latinName, String commonName) { super(); this.itisCode = itisCode; @@ -50,6 +58,12 @@ public class SpeciesMapItem implements Serializable, Cloneable { this.commonName = commonName; } + /** + * + * @param itisCode + * @param callType + * @param pamguardName + */ public SpeciesMapItem(int itisCode, String callType, String pamguardName) { super(); this.itisCode = itisCode; diff --git a/src/tethys/species/swing/DataBlockSpeciesDialog.java b/src/tethys/species/swing/DataBlockSpeciesDialog.java index 3181493f..5c6ed347 100644 --- a/src/tethys/species/swing/DataBlockSpeciesDialog.java +++ b/src/tethys/species/swing/DataBlockSpeciesDialog.java @@ -49,16 +49,7 @@ public class DataBlockSpeciesDialog extends PamDialog { + "\"Other Phenomena\" (-10). " + "
    When known, a call or sound type should " + "be specified (see help for more information)."; - nPanel.add(BorderLayout.CENTER, new JLabel(otherMsg , JLabel.LEFT)); -// JPanel nwBit = new JPanel(new FlowLayout()); -// JButton exportButton = new JButton("Export"); -// exportButton.addActionListener(SpeciesMapManager.getInstance().getExportAction(parentFrame)); -// nwBit.add(exportButton); -// JButton importButton = new JButton("Import"); -// importButton.addActionListener(SpeciesMapManager.getInstance().getImportAction(parentFrame)); -// nwBit.add(importButton); -// nPanel.add(BorderLayout.WEST, nwBit); - + nPanel.add(BorderLayout.CENTER, new JLabel(otherMsg , JLabel.LEFT)); mainPanel.add(BorderLayout.NORTH, nPanel); setDialogComponent(mainPanel); diff --git a/src/tethys/species/swing/SpeciesSearchDialog.java b/src/tethys/species/swing/SpeciesSearchDialog.java index 78d430e3..01dcfacb 100644 --- a/src/tethys/species/swing/SpeciesSearchDialog.java +++ b/src/tethys/species/swing/SpeciesSearchDialog.java @@ -104,11 +104,11 @@ public class SpeciesSearchDialog extends PamDialog { setResizable(true); setDialogComponent(mainPanel); } - public static SpeciesMapItem showDialog(Window parentFrame, TethysControl tethysControl) { + public static SpeciesMapItem showDialog(Window parentFrame, TethysControl tethysControl, Integer currentCode) { if (singleInstance == null) { singleInstance = new SpeciesSearchDialog(parentFrame, tethysControl); } - singleInstance.setParams(); + singleInstance.setParams(currentCode); singleInstance.setVisible(true); return singleInstance.selectedItem; } @@ -131,6 +131,9 @@ public class SpeciesSearchDialog extends PamDialog { public void setMapItems(ArrayList newMapItems) { this.speciesMapItems = newMapItems; + if (newMapItems != null && newMapItems.size() == 1) { + setSelectedItem(newMapItems.get(0)); + } tableModel.fireTableDataChanged(); } @@ -188,14 +191,20 @@ public class SpeciesSearchDialog extends PamDialog { } - private void setParams() { - searchText.setText(null); - clearResults(); + private void setParams(Integer currentCode) { + if (currentCode == null) { + searchText.setText(null); + clearResults(); + } + else { + searchText.setText(currentCode.toString()); + searchTethys(); + } } private void clearResults() { speciesMapItems = null; - selectedItem = null; + setSelectedItem(null); } @Override public boolean getParams() { @@ -216,6 +225,10 @@ public class SpeciesSearchDialog extends PamDialog { } + private void enableControls() { + getOkButton().setEnabled(selectedItem != null); + } + private class TableMouse extends MouseAdapter { @Override @@ -225,12 +238,18 @@ public class SpeciesSearchDialog extends PamDialog { } int selectedRow = resultTable.getSelectedRow(); if (selectedRow >= 0 && selectedRow < speciesMapItems.size()) { - selectedItem = speciesMapItems.get(selectedRow); + setSelectedItem(speciesMapItems.get(selectedRow)); } tableModel.fireTableDataChanged(); } } + + private void setSelectedItem(SpeciesMapItem selItem) { + this.selectedItem = selItem; + enableControls(); + } + private class DataModel extends AbstractTableModel { private String[] colNames = {"Select", "TSN", "Name", "Common Name"}; diff --git a/src/tethys/species/swing/SpeciesSubPanel.java b/src/tethys/species/swing/SpeciesSubPanel.java index f55919f4..4b25d71f 100644 --- a/src/tethys/species/swing/SpeciesSubPanel.java +++ b/src/tethys/species/swing/SpeciesSubPanel.java @@ -114,12 +114,12 @@ public class SpeciesSubPanel { } ITISFunctions itisFunctions = tethysControl.getItisFunctions(); String itisString = this.itisCode.getText(); - if (itisString == null || itisString.length() == 0) { +// if (itisString == null || itisString.length() == 0) { searchForCode(tethysControl, itisFunctions); - } - else { - getCodeInformation(tethysControl, itisFunctions, itisString); - } +// } +// else { +// getCodeInformation(tethysControl, itisFunctions, itisString); +// } // System.out.println(itisInfo); } @@ -144,7 +144,15 @@ public class SpeciesSubPanel { } private void searchForCode(TethysControl tethysControl, ITISFunctions itisFunctions) { - SpeciesMapItem speciesItem = SpeciesSearchDialog.showDialog(tethysControl.getGuiFrame(), tethysControl); + Integer currentCode = null; + try { + currentCode = Integer.valueOf(itisCode.getText()); + } + catch (NumberFormatException e) { + + } + + SpeciesMapItem speciesItem = SpeciesSearchDialog.showDialog(tethysControl.getGuiFrame(), tethysControl, currentCode); if (speciesItem != null) { itisCode.setText(String.format("%d", speciesItem.getItisCode())); latinName.setText(speciesItem.getLatinName()); diff --git a/src/tethys/swing/DatablockDetectionsPanel.java b/src/tethys/swing/DatablockDetectionsPanel.java index b5b75397..05f05507 100644 --- a/src/tethys/swing/DatablockDetectionsPanel.java +++ b/src/tethys/swing/DatablockDetectionsPanel.java @@ -5,6 +5,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; import java.util.List; import javax.swing.JComponent; @@ -19,6 +20,8 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.JTableHeader; import javax.xml.datatype.XMLGregorianCalendar; +import PamUtils.worker.PamWorkWrapper; +import PamUtils.worker.PamWorker; import PamView.PamGui; import PamView.dialog.warn.WarnOnce; import PamView.tables.SwingTableColumnWidths; @@ -42,7 +45,7 @@ import tethys.niluswraps.PDetections; * @author dg50 * */ -public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTableObserver { +public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTableObserver, PamWorkWrapper { private JPanel mainPanel; @@ -118,15 +121,38 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa @Override public void selectDataBlock(PamDataBlock dataBlock) { + if (this.dataBlock == dataBlock) { + return; // stops lots of requerying, which matters when database is large. + } this.dataBlock = dataBlock; - dataBlockName.setText(dataBlock.getLongDataName()); - streamDetectionsSummary = getTethysControl().getDetectionsHandler().getStreamDetections(dataBlock); + if (dataBlock == null) { + dataBlockName.setText("Select data in panel on the left"); + return; + } + else { + dataBlockName.setText(dataBlock.getLongDataName()); + } + // need to re-thread this to stop user panicing that nothing is happening. + PamWorker w = new PamWorker(this, getTethysControl().getGuiFrame(), 0, "Searching database for " + dataBlock.getDataName()); + w.start(); + } + + @Override + public void taskFinished(String result) { tableModel.fireTableDataChanged(); } + @Override + public String runBackgroundTask(PamWorker pamWorker) { + streamDetectionsSummary = getTethysControl().getDetectionsHandler().getStreamDetections(dataBlock); + return null; + } + @Override public void updateState(TethysState tethysState) { if (dataBlock != null) { + PamDataBlock currBlock = dataBlock; + selectDataBlock(null); selectDataBlock(dataBlock); } } @@ -164,16 +190,9 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa JPopupMenu popMenu = new JPopupMenu(); + JMenuItem menuItem; if (rows.length == 1) { - JMenuItem menuItem = new JMenuItem("Delete document " + pDets.detections.getId()); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - deleteDocument(pDets); - } - }); - popMenu.add(menuItem); - + menuItem = new JMenuItem("Display document " + pDets.detections.getId()); menuItem.addActionListener(new ActionListener() { @Override @@ -191,9 +210,19 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa } }); popMenu.add(menuItem); + + popMenu.addSeparator(); + menuItem = new JMenuItem("Delete document " + pDets.detections.getId()); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + deleteDocument(pDets); + } + }); + popMenu.add(menuItem); } else if (rows.length > 0){ - JMenuItem menuItem = new JMenuItem("Delete multiple Detections documents"); + menuItem = new JMenuItem("Delete multiple Detections documents"); menuItem.addActionListener(new ActionListener() { @Override @@ -217,20 +246,50 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa if (ans != WarnOnce.OK_OPTION) { return; } + + ArrayList toDelete = new ArrayList(); + for (int i = 0; i < rows.length; i++) { int row = rows[i]; PDetections pDets = detectionsForRow(row); if (pDets == null) { continue; } - try { - getTethysControl().getDbxmlConnect().deleteDocument(pDets.detections); - } catch (TethysException e) { - getTethysControl().showException(e); - } + toDelete.add(pDets.detections); } - getTethysControl().exportedDetections(dataBlock); - selectDataBlock(dataBlock); // force table update. + DeleteDocs dd = new DeleteDocs(toDelete); + PamWorker worker = new PamWorker(dd, getTethysControl().getGuiFrame(), 1, "Deleting Detections documents"); + worker.start(); + + } + + private class DeleteDocs implements PamWorkWrapper { + + private ArrayList toDelete; + + public DeleteDocs(ArrayList toDelete) { + this.toDelete = toDelete; + } + + @Override + public Integer runBackgroundTask(PamWorker pamWorker) { + for (Detections dets : toDelete) { + try { + + getTethysControl().getDbxmlConnect().deleteDocument(dets); + } catch (TethysException e) { + getTethysControl().showException(e); + } + } + return toDelete.size(); + } + + @Override + public void taskFinished(Integer result) { + getTethysControl().exportedDetections(dataBlock); + selectDataBlock(dataBlock); // force table update. + } + } protected void deleteDocument(PDetections pDets) { diff --git a/src/tethys/swing/DatablockSynchPanel.java b/src/tethys/swing/DatablockSynchPanel.java index e23e308f..54985d64 100644 --- a/src/tethys/swing/DatablockSynchPanel.java +++ b/src/tethys/swing/DatablockSynchPanel.java @@ -1,6 +1,7 @@ package tethys.swing; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; @@ -13,6 +14,7 @@ import java.util.ArrayList; import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -36,9 +38,9 @@ import tethys.niluswraps.PDeployment; import tethys.output.DatablockSynchInfo; import tethys.species.DataBlockSpeciesManager; -public class DatablockSynchPanel extends TethysGUIPanel { +public class DatablockSynchPanel extends TethysExportPanel { - public JPanel mainPanel; +// public JPanel mainPanel; private JTable synchTable; @@ -48,39 +50,46 @@ public class DatablockSynchPanel extends TethysGUIPanel { private ArrayList tableObservers = new ArrayList<>(); - private JButton exportButton; +// private TippedButton exportButton; +// private JLabel exportWarning; + + public DatablockSynchPanel(TethysControl tethysControl) { - super(tethysControl); - mainPanel = new PamPanel(new BorderLayout()); + super(tethysControl, tethysControl.getDetectionsHandler(), false); +// mainPanel = new PamPanel(new BorderLayout()); + JPanel mainPanel = getMainPanel(); mainPanel.setBorder(new TitledBorder("PAMGuard data blocks")); synchTableModel = new SynchTableModel(); synchTable = new JTable(synchTableModel); new SwingTableColumnWidths(tethysControl.getUnitName()+"SynchTable", synchTable); JScrollPane scrollPane = new JScrollPane(synchTable); mainPanel.add(BorderLayout.CENTER, scrollPane); - PamPanel ctrlPanel = new PamPanel(new BorderLayout()); - exportButton = new JButton("Export ..."); - ctrlPanel.add(BorderLayout.WEST, exportButton); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); +// PamPanel ctrlPanel = new PamPanel(new BorderLayout()); +// exportButton = new TippedButton("Export ...", "Export Detections document"); +// exportWarning = new JLabel(" "); +// exportWarning.setForeground(Color.RED); +// ctrlPanel.add(BorderLayout.WEST, exportButton); +// ctrlPanel.add(BorderLayout.CENTER, exportWarning); +// mainPanel.add(BorderLayout.NORTH, ctrlPanel); synchTable.addMouseListener(new MouseActions()); synchTable.addKeyListener(new KeyActions()); - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportData(); - } - }); +// exportButton.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// exportData(); +// } +// }); enableExportButton(); } - @Override - public JComponent getComponent() { - return mainPanel; - } +// @Override +// public JComponent getComponent() { +// return mainPanel; +// } private class KeyActions extends KeyAdapter { @Override @@ -141,13 +150,50 @@ public class DatablockSynchPanel extends TethysGUIPanel { } private void enableExportButton() { + if (!getTethysControl().isServerOk()) { + disableExport("Tethys Server not running"); + return; + } int[] rows = synchTable.getSelectedRows(); - boolean en = rows != null && rows.length == 1; ArrayList deployments = getTethysControl().getDeploymentHandler().getMatchedDeployments(); if (deployments == null || deployments.size() == 0) { - en = false; + disableExport("No Deployment document(s). Export Deployments prior to exporting Detections"); + return; } - exportButton.setEnabled(getTethysControl().isServerOk() & en); + boolean en = rows != null && rows.length == 1; + if (!en) { + disableExport("No PAMGuard datablock selected (click a row on the table below)"); + return; + } + + PamDataBlock dataBlock = dataBlockSynchInfo.get(rows[0]).getDataBlock(); + String mapError = checkSpeciesManager(dataBlock); + if (mapError != null) { + disableExport("Unable to export due to species map error: " + mapError + ". Right click table row to edit species list"); + return; + } + + enableExport(true); + } + +// public void disableExport(String reason) { +// if (reason == null) { +// exportButton.setEnabled(true); +// exportWarning.setText(null); +// } +// else { +// exportButton.disable(reason); +// exportWarning.setText(" " + reason); +// } +// } + + private String checkSpeciesManager(PamDataBlock dataBlock) { + DataBlockSpeciesManager spManager = dataBlock.getDatablockSpeciesManager(); + if (spManager == null) { + return "No species manager"; + } + String error = spManager.checkSpeciesMapError(); + return error; } public void showPopup(MouseEvent e, int row) { @@ -259,4 +305,15 @@ public class DatablockSynchPanel extends TethysGUIPanel { } } + + @Override + protected void exportButtonPressed(ActionEvent e) { + exportData(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + // TODO Auto-generated method stub + + } } diff --git a/src/tethys/swing/DeploymentExportPanel.java b/src/tethys/swing/DeploymentExportPanel.java index 4147cb38..fd299636 100644 --- a/src/tethys/swing/DeploymentExportPanel.java +++ b/src/tethys/swing/DeploymentExportPanel.java @@ -34,6 +34,7 @@ import tethys.TethysState; import tethys.TethysState.StateType; import tethys.dbxml.DBXMLConnect; import tethys.deployment.DeploymentHandler; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; import tethys.niluswraps.PDeployment; @@ -241,7 +242,9 @@ public class DeploymentExportPanel extends TethysGUIPanel implements DeploymentT if (selectedDeployments == null || selectedDeployments.size() == 0) { return; }; - getTethysControl().getDeploymentHandler().exportDeployments(selectedDeployments); + // need to turn that list back into a RecordingList object. + RecordingList tempList = new RecordingList("eport list", selectedDeployments); + getTethysControl().getDeploymentHandler().exportDeployments(tempList); } diff --git a/src/tethys/swing/DeploymentsPanel.java b/src/tethys/swing/DeploymentsPanel.java index 7ebb6dec..4dc277f6 100644 --- a/src/tethys/swing/DeploymentsPanel.java +++ b/src/tethys/swing/DeploymentsPanel.java @@ -16,81 +16,38 @@ import javax.swing.border.TitledBorder; import PamView.panel.PamPanel; import tethys.TethysControl; import tethys.TethysState; +import tethys.calibration.CalibrationHandler; import tethys.deployment.DeploymentHandler; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; -public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableObserver { +public class DeploymentsPanel extends TethysExportPanel implements DeploymentTableObserver { - private JPanel mainPanel; +// private JPanel mainPanel; private PAMGuardDeploymentsTable pamDeploymentsTable; private DeploymentExportPanel exportPanel; - private JButton exportButton, optionsButton; -// private TethysDeploymentsTable tethysDeploymentsTable; - private JLabel exportWarning; + private JLabel effortName; public DeploymentsPanel(TethysControl tethysControl) { - super(tethysControl); + super(tethysControl, tethysControl.getDeploymentHandler(), true); DeploymentHandler deploymentHandler = tethysControl.getDeploymentHandler(); pamDeploymentsTable = new PAMGuardDeploymentsTable(tethysControl); exportPanel = new DeploymentExportPanel(tethysControl, pamDeploymentsTable); pamDeploymentsTable.addObserver(exportPanel); -// tethysDeploymentsTable = new TethysDeploymentsTable(tethysControl); - mainPanel = new PamPanel(new BorderLayout()); + + JPanel mainPanel = getMainPanel(); mainPanel.setBorder(new TitledBorder("Recording periods and deployment information")); pamDeploymentsTable.addObserver(this); pamDeploymentsTable.addObserver(deploymentHandler); -// JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); -// splitPane.add(pamDeploymentsTable.getComponent()); -// splitPane.add(tethysDeploymentsTable.getComponent()); -// mainPanel.add(splitPane,BorderLayout.CENTER); -// SwingUtilities.invokeLater(new Runnable() { -// -// @Override -// public void run() { -// splitPane.setDividerLocation(0.6); -// } -// }); - JPanel ctrlPanel = new PamPanel(new BorderLayout()); - JPanel ctrlButtons = new JPanel(); - ctrlButtons.setLayout(new BoxLayout(ctrlButtons, BoxLayout.X_AXIS)); - optionsButton = new JButton("Options ..."); - exportButton = new JButton("Export ..."); - ctrlButtons.add(optionsButton); - ctrlButtons.add(exportButton); - ctrlPanel.add(BorderLayout.WEST, ctrlButtons); - optionsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - getTethysControl().getDeploymentHandler().showOptions(null); - } - }); - - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportDeployments(); - } - }); - exportWarning = new JLabel(" "); - ctrlPanel.add(BorderLayout.CENTER, exportWarning); - - mainPanel.add(BorderLayout.CENTER, pamDeploymentsTable.getComponent()); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); -// mainPanel.add(BorderLayout.EAST, exportPanel.getComponent()); - exportButton.setEnabled(false); - } - - protected void exportDeployments() { - getTethysControl().getDeploymentHandler().exportDeployments(); - } - - @Override - public JComponent getComponent() { - return mainPanel; + effortName = new JLabel(" "); + JPanel centralPanel = new JPanel(new BorderLayout()); + centralPanel.add(BorderLayout.NORTH, effortName); + centralPanel.add(BorderLayout.CENTER,pamDeploymentsTable.getComponent()); + mainPanel.add(BorderLayout.CENTER, centralPanel); } @Override @@ -98,12 +55,21 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO enableExportButton(); } - - private void enableExportButton() { + if (!getTethysControl().isServerOk()) { + disableExport("Tethys server not running"); + return; + } + + CalibrationHandler calHandler = getTethysControl().getCalibrationHandler(); + if (calHandler.haveAllChannelCalibrations() == false) { + disableExport("Calibration data for each channel must be exported before creating Deployment documents"); + return; + } + ArrayList selected = pamDeploymentsTable.getSelectedPeriods(); - if (selected == null) { - exportButton.setEnabled(false); + if (selected == null || selected.size() == 0) { + disableExport("You must select one or more deployment periods to export"); return; } boolean existing = false; @@ -118,17 +84,35 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO } String warning = null; if (existing) { - warning = " One or more deployment documents already exist. These must be deleted prior to exporting new documents"; - exportWarning.setText(warning); + warning = "One or more deployment documents already exist. These must be deleted prior to exporting new documents"; + disableExport(warning); + return; } - - exportButton.setEnabled(selected.size()>0 & existing == false && getTethysControl().isServerOk()); + + enableExport(true); } @Override public void updateState(TethysState tethysState) { super.updateState(tethysState); enableExportButton(); + RecordingList recordingList = pamDeploymentsTable.getMasterList(); + if (recordingList == null) { + effortName.setText(" No available effort data"); + } + else { + effortName.setText(" Effort from " + recordingList.getSourceName()); + } + } + + @Override + protected void exportButtonPressed(ActionEvent e) { + getTethysControl().getDeploymentHandler().exportDeployments(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + getTethysControl().getDeploymentHandler().showOptions(null); } diff --git a/src/tethys/swing/FancyClientButton.java b/src/tethys/swing/FancyClientButton.java index 266e9811..61ca0cca 100644 --- a/src/tethys/swing/FancyClientButton.java +++ b/src/tethys/swing/FancyClientButton.java @@ -1,12 +1,15 @@ package tethys.swing; import java.awt.BorderLayout; +import java.awt.Desktop; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; import javax.swing.AbstractButton; import javax.swing.BoxLayout; @@ -30,14 +33,14 @@ import tethys.dbxml.DBXMLConnect; */ public class FancyClientButton extends JPanel { + private TethysControl tethysControl; + private JButton clientButton; private JButton dropButton; private JPopupMenu collectionsMenu; - private TethysControl tethysControl; private JCheckBoxMenuItem showBrowser; private AbstractButton showPAMGuard; - public FancyClientButton(TethysControl tethysControl) { this.tethysControl = tethysControl; setLayout(new GridBagLayout()); @@ -105,6 +108,16 @@ public class FancyClientButton extends JPanel { menuItem.addActionListener(new OpenCollection(collections[i])); collectionsMenu.add(menuItem); } + collectionsMenu.addSeparator(); + JMenuItem tmpItem = new JMenuItem("Open temp folder"); + collectionsMenu.add(tmpItem); + tmpItem.setToolTipText("Open folder used for temporary document files during export in Windows Explorer"); + tmpItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + openTempFolder(); + } + }); dropButton.addActionListener(new ActionListener() { @Override @@ -115,6 +128,19 @@ public class FancyClientButton extends JPanel { enableItems(); } + protected void openTempFolder() { + File tempFolder = tethysControl.getDbxmlConnect().checkTempFolder(); + if (tempFolder == null) { + return; + } + try { + Desktop.getDesktop().open(tempFolder); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + protected void enableItems() { boolean isP = tethysControl.getTethysExportParams().listDocsInPamguard; showBrowser.setSelected(!isP); diff --git a/src/tethys/swing/PAMGuardDeploymentsTable.java b/src/tethys/swing/PAMGuardDeploymentsTable.java index 1b870921..6d3819d8 100644 --- a/src/tethys/swing/PAMGuardDeploymentsTable.java +++ b/src/tethys/swing/PAMGuardDeploymentsTable.java @@ -32,6 +32,7 @@ import tethys.TethysState.StateType; import tethys.dbxml.TethysException; import tethys.deployment.DeploymentHandler; import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; import tethys.niluswraps.PDeployment; @@ -55,6 +56,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { private ArrayList observers = new ArrayList<>(); +private RecordingList masterList; + public PAMGuardDeploymentsTable(TethysControl tethysControl) { super(tethysControl); // deploymentHandler = new DeploymentHandler(getTethysControl()); @@ -74,6 +77,10 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { public JComponent getComponent() { return mainPanel; } + + public RecordingList getMasterList() { + return masterList; + } private class TableMouse extends MouseAdapter { @@ -95,7 +102,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { public void mouseClicked(MouseEvent e) { int aRow = table.getSelectedRow(); int col = table.getSelectedColumn(); - ArrayList periods = deploymentOverview.getRecordingPeriods(); + ArrayList periods = getMasterList().getEffortPeriods(); if (aRow >= 0 && aRow < periods.size() && col == TableModel.SELECTCOLUMN) { periods.get(aRow).toggleSelected(); notifyObservers(); @@ -118,7 +125,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } // make a list of RecordingPeriods which don't currently have a Deployment document ArrayList newPeriods = new ArrayList<>(); - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList allPeriods = getMasterList().getEffortPeriods(); +// ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); ArrayList matchedDeployments = new ArrayList<>(); for (int i = 0; i < selRows.length; i++) { PDeployment tethysDeployment = allPeriods.get(selRows[i]).getMatchedTethysDeployment(); @@ -155,14 +163,6 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } if (matchedDeployments.size() == 1) { - menuItem = new JMenuItem("Delete deployment document " + matchedDeployments.get(0)); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - deleteDeployment(matchedDeployments.get(0)); - } - }); - popMenu.add(menuItem); menuItem = new JMenuItem("Display deployment document " + matchedDeployments.get(0)); menuItem.addActionListener(new ActionListener() { @Override @@ -179,7 +179,17 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } }); popMenu.add(menuItem); + + popMenu.addSeparator(); + menuItem = new JMenuItem("Delete deployment document " + matchedDeployments.get(0)); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + deleteDeployment(matchedDeployments.get(0)); + } + }); + popMenu.add(menuItem); } else if (matchedDeployments.size() > 1){ @@ -200,7 +210,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } protected void selectAll(boolean select) { - ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList recordingPeriods = getMasterList().getEffortPeriods(); +// ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); for (int i = 0; i < recordingPeriods.size(); i++) { recordingPeriods.get(i).setSelected(select); } @@ -329,7 +340,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { if (deploymentOverview == null) { return null; } - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList allPeriods = getMasterList().getEffortPeriods(); ArrayList selPeriods = new ArrayList(); int n = allPeriods.size(); for (int i = 0; i < n; i++) { @@ -348,6 +359,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { private void updateDeployments() { DeploymentHandler deploymentHandler = getTethysControl().getDeploymentHandler(); deploymentOverview = deploymentHandler.getDeploymentOverview(); + masterList = deploymentOverview.getMasterList(getTethysControl()); if (deploymentOverview == null) { return; } @@ -373,7 +385,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { return 0; } else { - return deploymentOverview.getRecordingPeriods().size(); + return getMasterList().size(); } } @@ -398,13 +410,14 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { @Override public Object getValueAt(int rowIndex, int columnIndex) { - RecordingPeriod period = deploymentOverview.getRecordingPeriods().get(rowIndex); + RecordingList masterList = getMasterList(); + RecordingPeriod period = masterList.getEffortPeriods().get(rowIndex); // DeploymentRecoveryPair deplInfo = deploymentInfo.get(rowIndex); if (columnIndex == 6) { - return deploymentOverview.getDutyCycleInfo(); + return masterList.assessDutyCycle(); } if (columnIndex == 4 && rowIndex > 0) { - RecordingPeriod prevPeriod = deploymentOverview.getRecordingPeriods().get(rowIndex-1); + RecordingPeriod prevPeriod = masterList.getEffortPeriods().get(rowIndex-1); long gap = period.getRecordStart() - prevPeriod.getRecordStop(); return PamCalendar.formatDuration(gap); } diff --git a/src/tethys/swing/TethysConnectionPanel.java b/src/tethys/swing/TethysConnectionPanel.java index d96b7a54..5a7cc90b 100644 --- a/src/tethys/swing/TethysConnectionPanel.java +++ b/src/tethys/swing/TethysConnectionPanel.java @@ -277,12 +277,17 @@ public class TethysConnectionPanel extends TethysGUIPanel { @Override public void updateState(TethysState tethysState) { super.updateState(tethysState); - if (tethysState.stateType == StateType.UPDATESERVER) { + switch (tethysState.stateType) { + case UPDATESERVER: fillServerControl(); updateProjectList(); - } - if (tethysState.stateType == StateType.NEWPROJECTSELECTION) { + break; + case NEWPROJECTSELECTION: updateProjectList(); + break; + case UPDATEMETADATA: + updateInstrumentsList(); + break; } } diff --git a/src/tethys/swing/TethysDeploymentsTable.java b/src/tethys/swing/TethysDeploymentsTable.java index 08781605..21c45bbe 100644 --- a/src/tethys/swing/TethysDeploymentsTable.java +++ b/src/tethys/swing/TethysDeploymentsTable.java @@ -21,6 +21,7 @@ import tethys.TethysControl; import tethys.TethysMenuActions; import tethys.TethysState; import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; import tethys.niluswraps.PDeployment; public class TethysDeploymentsTable extends TethysGUIPanel { @@ -121,8 +122,7 @@ public class TethysDeploymentsTable extends TethysGUIPanel { @Override public String getColumnName(int column) { return columnNames[column]; - } - + } public String getMatchText(PDeployment deployment) { // TODO Auto-generated method stub @@ -132,8 +132,9 @@ public class TethysDeploymentsTable extends TethysGUIPanel { if (deploymentOverview == null) { return "No PAMGuard data"; } - Long depStart = deploymentOverview.getFirstStart(); - Long depEnd = deploymentOverview.getLastEnd(); + RecordingList masterList = deploymentOverview.getMasterList(getTethysControl()); + Long depStart = masterList.getStart(); + Long depEnd = masterList.getEnd(); if (depStart == null) { return "No PAMGuard recordings"; } diff --git a/src/tethys/swing/TethysExportPanel.java b/src/tethys/swing/TethysExportPanel.java new file mode 100644 index 00000000..6fdfaa6e --- /dev/null +++ b/src/tethys/swing/TethysExportPanel.java @@ -0,0 +1,227 @@ +package tethys.swing; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import PamView.dialog.NorthPanel; +import PamView.dialog.SettingsButton; +import PamView.help.PamHelp; +import tethys.Collection; +import tethys.CollectionHandler; +import tethys.TethysControl; + +/** + * Common panel used by Calibrations, Deployments and Detections to show an export button and other + * common components, such as help, a table, etc.. + * @author dg50 + * + */ +abstract public class TethysExportPanel extends TethysGUIPanel { + + private TippedButton exportButton; + + private JButton optionsButton, helpButton; + + private JPanel mainPanel, northPanel; + + private JLabel message; + + private CollectionHandler collectionHandler; + + private String helpPoint; + + private boolean showOptions; + + public TethysExportPanel(TethysControl tethysControl, CollectionHandler collectionHandler, boolean showOptions) { + super(tethysControl); + this.collectionHandler = collectionHandler; + this.showOptions = showOptions; + this.helpPoint = collectionHandler.getHelpPoint(); + + mainPanel = new JPanel(new BorderLayout()); + northPanel = new JPanel(new BorderLayout()); + JPanel nwPanel = new JPanel(); + nwPanel.setLayout(new BoxLayout(nwPanel, BoxLayout.X_AXIS)); + JPanel nePanel = new JPanel(); + nePanel.setLayout(new BoxLayout(nePanel, BoxLayout.X_AXIS)); + northPanel.add(BorderLayout.CENTER, nwPanel); + northPanel.add(BorderLayout.EAST, nePanel); + mainPanel.add(BorderLayout.NORTH, northPanel); + + optionsButton = new SettingsButton(); + exportButton = new TippedButton("Export ...", "Export " + collectionHandler.collectionName() + " to Tethys"); + helpButton = new JButton("?"); + helpButton.setToolTipText("Show context sensitive help"); + JLabel space = new JLabel(" "); + message = new JLabel (" "); + + nwPanel.add(optionsButton); + nwPanel.add(exportButton); + nwPanel.add(space); + nwPanel.add(message); + nePanel.add(helpButton); + + showAndHide(); + + optionsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + optionsButtonPressed(e); + } + }); + exportButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + exportButtonPressed(e); + } + }); + helpButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + helpButtonPressed(e); + } + }); + } + + /** + * Show the help. + * @param e + */ + protected void helpButtonPressed(ActionEvent e) { + if (helpPoint == null) { + return; + } + PamHelp.getInstance().displayContextSensitiveHelp(helpPoint); + } + + /** + * Export button has been pressed. + * @param e + */ + protected abstract void exportButtonPressed(ActionEvent e); + + /** + * Options button has been pressed. + * @param e + */ + protected abstract void optionsButtonPressed(ActionEvent e); + + private void showAndHide() { + optionsButton.setVisible(showOptions); + helpButton.setVisible(helpPoint != null); + } + + @Override + public JComponent getComponent() { + return mainPanel; + } + + /** + * @return the helpPoint + */ + public String getHelpPoint() { + return helpPoint; + } + + /** + * @param helpPoint the helpPoint to set + */ + public void setHelpPoint(String helpPoint) { + this.helpPoint = helpPoint; + showAndHide(); + } + + /** + * @return the showOptions + */ + public boolean isShowOptions() { + return showOptions; + } + + /** + * @param showOptions the showOptions to set + */ + public void setShowOptions(boolean showOptions) { + this.showOptions = showOptions; + showAndHide(); + } + + /** + * @return the exportButton + */ + public TippedButton getExportButton() { + return exportButton; + } + + /** + * @return the optionsButton + */ + public JButton getOptionsButton() { + return optionsButton; + } + + /** + * @return the helpButton + */ + public JButton getHelpButton() { + return helpButton; + } + + /** + * @return the mainPanel + */ + public JPanel getMainPanel() { + return mainPanel; + } + + /** + * @return the northPanel + */ + public JPanel getNorthPanel() { + return northPanel; + } + + /** + * @return the message + */ + public JLabel getMessage() { + return message; + } + + /** + * @return the collectionHandler + */ + public CollectionHandler getCollectionHandler() { + return collectionHandler; + } + + /** + * Enable or disable export button, leaving tool tips alone + * @param enable + */ + public void enableExport(boolean enable) { + exportButton.setEnabled(enable); + if (enable) { + message.setText(null); + } + } + + /** + * Disable the export button and set the tooltip. + * @param disabledTip + */ + public void disableExport(String disabledTip) { + exportButton.disable(disabledTip); + message.setText(disabledTip); + } + +} diff --git a/src/tethys/swing/TippedButton.java b/src/tethys/swing/TippedButton.java new file mode 100644 index 00000000..46c369e2 --- /dev/null +++ b/src/tethys/swing/TippedButton.java @@ -0,0 +1,77 @@ +package tethys.swing; + +import javax.swing.JButton; + +public class TippedButton extends JButton { + + private static final long serialVersionUID = 1L; + private String enabledTip; + private String disabledTip; + + /** + * Create a button with standard tips which will be used for enabled state + * @param text + * @param enabledTip + */ + public TippedButton(String text, String enabledTip) { + this(text, enabledTip, null); + } + + /** + * Create a button with standard tips which will be used for enabled and disabled state + * @param text + * @param enabledTip + * @param disabledTip + */ + public TippedButton(String text, String enabledTip, String disabledTip) { + super(text); + this.enabledTip = enabledTip; + this.disabledTip = disabledTip; + setToolTipText(enabledTip); + } + + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + setToolTipText(enable ? enabledTip : disabledTip); + } + + /* + * Call to disable the button and at the same time + * set a tooltip giving the reason. + */ + public void disable(String newTip) { + disabledTip = newTip; + setEnabled(false); + } + + /** + * @return the enabledTip + */ + public String getEnabledTip() { + return enabledTip; + } + + /** + * @param enabledTip the enabledTip to set + */ + public void setEnabledTip(String enabledTip) { + this.enabledTip = enabledTip; + } + + /** + * @return the disabledTip + */ + public String getDisabledTip() { + return disabledTip; + } + + /** + * @param disabledTip the disabledTip to set + */ + public void setDisabledTip(String disabledTip) { + this.disabledTip = disabledTip; + } + + +} diff --git a/src/tethys/swing/XMLStringView.java b/src/tethys/swing/XMLStringView.java index 26098fe3..6886c493 100644 --- a/src/tethys/swing/XMLStringView.java +++ b/src/tethys/swing/XMLStringView.java @@ -28,6 +28,9 @@ public class XMLStringView extends PamDialog { getCancelButton().setVisible(false); setModal(false); + + getOkButton().setText("Close"); + getOkButton().setToolTipText("Close window"); } public static void showDialog(Window parent, String collection, String documentId, String xmlString) { @@ -38,7 +41,7 @@ public class XMLStringView extends PamDialog { @Override public boolean getParams() { - return false; + return true; } @Override diff --git a/src/tethys/swing/export/DeploymentPeriodPanel.java b/src/tethys/swing/export/DeploymentPeriodPanel.java index 26b3e9b3..c3733fd9 100644 --- a/src/tethys/swing/export/DeploymentPeriodPanel.java +++ b/src/tethys/swing/export/DeploymentPeriodPanel.java @@ -19,6 +19,7 @@ import metadata.PamguardMetaData; import nilus.Deployment; import nilus.DeploymentRecoveryDetails; import tethys.TethysTimeFuncs; +import tethys.tooltips.TethysTips; public class DeploymentPeriodPanel { @@ -68,6 +69,9 @@ public class DeploymentPeriodPanel { enableControls(); } }); + + deploymentStart.setToolTipText(TethysTips.findTip(DeploymentRecoveryDetails.class, "TimeStamp")); + deploymentEnd.setToolTipText(TethysTips.findTip(DeploymentRecoveryDetails.class, "TimeStamp")); } protected void enableControls() { diff --git a/src/tethys/swing/export/DescriptionTypePanel.java b/src/tethys/swing/export/DescriptionTypePanel.java index 64178883..0220468c 100644 --- a/src/tethys/swing/export/DescriptionTypePanel.java +++ b/src/tethys/swing/export/DescriptionTypePanel.java @@ -19,6 +19,7 @@ import PamView.PamGui; import PamView.dialog.PamDialog; import nilus.DescriptionType; import tethys.niluswraps.WrappedDescriptionType; +import tethys.tooltips.TethysTips; /** * Panel containing the three test entry fields for nilus.DescriptionType @@ -73,6 +74,10 @@ public class DescriptionTypePanel { addScrollablePanel(tObjectives, "Objectives"); addScrollablePanel(tAbstract, "Abstract"); addScrollablePanel(tMethod, "Method"); + + tObjectives.setToolTipText(TethysTips.Detections_Description_Objectives); + tAbstract.setToolTipText(TethysTips.Detections_Description_Abstract); + tMethod.setToolTipText(TethysTips.Detections_Description_Method); } private void addScrollablePanel(JTextArea textArea, String title) { diff --git a/src/tethys/swing/export/ExportWorkerCard.java b/src/tethys/swing/export/ExportWorkerCard.java index 6c512c24..27475bad 100644 --- a/src/tethys/swing/export/ExportWorkerCard.java +++ b/src/tethys/swing/export/ExportWorkerCard.java @@ -147,7 +147,7 @@ public class ExportWorkerCard extends ExportWizardCard implements DetectionExpor totExpected *= progress.exportCount/(progress.exportCount+progress.skipCount); } projectedCount.setText(String.format("%d", totExpected)); - long perc = (progress.exportCount+progress.skipCount) * 100 / progress.totalUnits; + long perc = (progress.doneMapPoints) * 100 / progress.nMapPoints; progressBar.setValue((int) perc); } switch (progress.state) { @@ -176,6 +176,18 @@ public class ExportWorkerCard extends ExportWizardCard implements DetectionExpor boolean stopped = state == DetectionExportProgress.STATE_CANCELED || state == DetectionExportProgress.STATE_COMPLETE; export.setEnabled(stopped); cancel.setEnabled(!stopped); + detectionsExportWizard.getCancelButton().setEnabled(stopped); + detectionsExportWizard.getPreviousButton().setEnabled(stopped); + } + + @Override + public void setVisible(boolean visible) { + /** + * setVisible is called by the wizard for each component, so + * we can see here if we're on this or not. + */ + super.setVisible(visible); + detectionsExportWizard.getOkButton().setEnabled(!visible); } } diff --git a/src/tethys/swing/export/ResponsiblePartyPanel.java b/src/tethys/swing/export/ResponsiblePartyPanel.java index 93b26816..4a42f959 100644 --- a/src/tethys/swing/export/ResponsiblePartyPanel.java +++ b/src/tethys/swing/export/ResponsiblePartyPanel.java @@ -10,6 +10,7 @@ import javax.swing.border.TitledBorder; import PamView.dialog.PamGridBagContraints; import nilus.ContactInfo.Address; +import tethys.tooltips.TethysTips; import nilus.ResponsibleParty; /** @@ -56,6 +57,11 @@ public class ResponsiblePartyPanel { c.gridx = 0; c.gridy++; + name.setToolTipText("Person responsible for data"); + organisation.setToolTipText("Responsible organization"); + position.setToolTipText("Persons role in organization"); + email.setToolTipText("email address or other contact details"); + } public JPanel getMainPanel() { diff --git a/src/tethys/tooltips/TethysTips.java b/src/tethys/tooltips/TethysTips.java new file mode 100644 index 00000000..8aff6fb2 --- /dev/null +++ b/src/tethys/tooltips/TethysTips.java @@ -0,0 +1,403 @@ +package tethys.tooltips; + +import java.lang.reflect.Field; + +import nilus.Deployment; + +/** + * Class to make it easy to find tooltips for a given nilus class and field nams. The constatns + * could be used directly, or can be found using findTip(Class, String) using the class type + * and field name for any nilus object. + * Tips were generates from a set of csv files extracted from the xml schema using Matlab code, then formatted + * in Matlab and pasted into this class, so will need to rerun that process should the xml schemas be updated. + * + * @author dg50 + * + */ +public class TethysTips { + +// public static void main(String[] args) { +// Class cls = Deployment.Data.class; +// String field = "Audio"; +// String foundTip = findTip(cls, field); +// System.out.println(foundTip); +// } + + /** + * find the tooltip for a given class and field within that class. + * @param aClass + * @param field + * @return found tip or null + */ + public static String findTip(Class aClass, String fieldName) { + Package pack = aClass.getPackage(); + String packName = pack.toString(); + String clsName = aClass.getCanonicalName(); + if (clsName.startsWith("nilus.") == false) { + return null; + } + clsName = clsName.substring(6); + clsName = clsName.replace('.', '_'); + String varName = clsName + "_" + fieldName; + // now try to find that field in this class and get it's value. + Field field = null; + try { + field = TethysTips.class.getDeclaredField(varName); + } catch (NoSuchFieldException | SecurityException e) { + return null; + } + if (field == null) { + return null; + } + Object tip = null; + try { + tip = field.get(null); + } catch (IllegalArgumentException | IllegalAccessException e) { + return null; + } + if (tip == null) { + return null; + } + + return tip.toString(); + } + + // Annotations taken from schemata_csv + public static final String Calibration_Id = "Identifier of instrument, preamplifier, or hydrophone. Corresponds to elements in Deployment: Deployment/Instrument/Id, Deployment/Sensors/Audio/HydrophoneId, Deployment/Sensors/Audio[i]/PreampId. As instruments may be calibrated multiple times, it is not an error for duplicate Id values to appear. It is recommended that the three different types of identifiers (instrument, hydrophone, preamp) be distinct, but the Type element may be used to distinguish them if they are not."; + public static final String Calibration_TimeStamp = "Date and time of calibration"; + public static final String Calibration_Type = "hydrophone, preamplifier, or end-to-end Indicates type of calibration"; + public static final String Calibration_Process = "Process used to calibrate instrument."; + public static final String Calibration_ResponsibleParty = "Who conducted/managed the calibration?"; + public static final String Calibration_IntensityReference_uPa = "Reference intensity in µ Pascals for dB measurements. Commonly used: underwater acoustics: 1 terrestrial acoustics: 20"; + public static final String Calibration_Sensitivity_dBV = "Optional measurement of transducer sensitivity at 1 kHz."; + public static final String Calibration_Sensitivity_V = "Optional measurement of Sensitivity_dBV on a linear scale, provided by many transducer manufacturers."; + public static final String Calibration_Sensitivity_dBFS = "Optional measurement for digital transducers. Digital transducers do not output voltage measurements. In this case, the 1 kHz sensitivity measurement is measured relative to peak output of the full-scale signal instead of RMS. It should be noted that for sinusoidal signals, the RMS sensitivity will be 3 dB lower (Lewis, 2012). Lewis, J. (2012). \"Understanding Microphone Sensitivity,\" Analog Dialogue 46(2). 14-16."; + public static final String Calibration_FrequencyResponse = "Lists of frequencies (Hz) and responses (dB). Lists must be of equal length."; + public static final String Calibration_MetadataInfo = "Information about who is responsible for this metadata record, when it was created, and how often it is updated."; + public static final String Calibration_Process_Method = "Text based description of algorithm or citation"; + public static final String Calibration_Process_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Calibration_Process_Version = "Software version identifier"; + public static final String Calibration_Process_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Calibration_Process_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Calibration_Process_SupportSoftware_Version = "Software version identifier."; + public static final String Calibration_Process_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Calibration_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Calibration_QualityAssurance_Quality = "Measurement is: unverified, valid, invalid"; + public static final String Calibration_QualityAssurance_AlternateCalibration = "Provide an alternative calibration Id that should be used (if available) when the Quality value is invalid."; + public static final String Calibration_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Calibration_MetadataInfo_Date = "Last update."; + public static final String Calibration_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Calibration_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + // Annotations taken from schemata_csv + public static final String Deployment_Id = "Character sequence that uniquely identifies this deployment."; + public static final String Deployment_Description = "Objectives, abstract and high-level methods."; + public static final String Deployment_Project = "Name of project associated with this deployment. Can be related to a geographic region, funding source, etc."; + public static final String Deployment_DeploymentId = "A number related to either the Nth deployment operation in a series of deployments or the Nth deployment at a specific site. This is different from Id which is a unqiue identifier for the deployment. If a vessel deployed 5 instruments, they might all have the same DeploymentId. While not enforced, it is expected that the combination of Project, DeploymentId, and (Site or Cruise) to be unique."; + public static final String Deployment_DeploymentAlias = "Alternative deployment description."; + public static final String Deployment_Site = "Name of a location where instruments are frequently deployed. Can be something as simple as a letter or a geographic name. Strongly recommended for long-term time series recorded at a specific point."; + public static final String Deployment_SiteAliases = "Alternative names for the deployment location"; + public static final String Deployment_Cruise = "Name of deployment cruise."; + public static final String Deployment_Platform = "On what platform is the instrument deployed? (e.g. mooring, tag)"; + public static final String Deployment_Region = "Name of geographic region."; + public static final String Deployment_Instrument = "Instrument type and identifier."; + public static final String Deployment_SamplingDetails = "Information about recordings on each channel. Sample rate, quantization bits, etc."; + public static final String Deployment_Data = "Data from instrument, a URI is provided when not present (typical for audio)."; + public static final String Deployment_DeploymentDetails = "Instrument deployment location, time, etc."; + public static final String Deployment_RecoveryDetails = "Instrument recovery, location, time, etc."; + public static final String Deployment_Sensors = "Sensors on instrument."; + public static final String Deployment_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Deployment_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Deployment_Description_Abstract = "Overview of effort."; + public static final String Deployment_Description_Method = "High-level description of the method used."; + public static final String Deployment_Instrument_Type = "Instrument type, e.g. HARP, EAR, Popup, DMON, Rock Hopper, etc."; + public static final String Deployment_Instrument_InstrumentId = "Instrument identifier, e.g. serial number"; + public static final String Deployment_Instrument_GeometryType = "Sensor attachment \"rigid\" - relative geometry is fixed, \"cabled\" - relative geometry may be expected to deform depending on movement, currents, etc."; + public static final String Deployment_SamplingDetails_Channel_ChannelNumber = "Channels and sensors are bound together from Start to End. While not enforced, we assume channels are numbered from 1 to N."; + public static final String Deployment_SamplingDetails_Channel_SensorNumber = "Audio sensor index within the Sensors element. This allows us to associate a channel with a physical hydrophone."; + public static final String Deployment_SamplingDetails_Channel_Sampling = "Sampling rate and quantization may change over time."; + public static final String Deployment_SamplingDetails_Channel_Gain = "Initial gain setting (assumed 0 if not populated) and any subsequent changes."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle = "Duty cycle is represented by the recording duration and the interval from the start of one recording session to the next. A duration of 3 m and an interval of 5 m would represent a 60% duty cycle, 3 m on, 2 m off."; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen = "Sampling regimen may change over time. Each entry shows the start of a sampling configuration."; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen_SampleRate_kHz = "Number of samples per second in kHz, e.g. 192 is 192,000 samples/s"; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen_SampleBits = "Number of bits per sample."; + public static final String Deployment_SamplingDetails_Channel_Gain_Regimen_Gain_rel = "Only used if gain is not calibrated. Relative gain may be a number on a dial."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen = "Duty cycling regimen may change over time. Each entry shows the start of a duty cycle configuration. The abscence of entries indicates continuous sampling as would having equal values in RecordingDuration_m and RecordingInterval_m."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_TimeStamp = "Indicates when the duty cycle becomes active. It remains active until the next Regimen entry."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_RecordingDuration_s = "The amount of time in minutes during each recording interval when the data logger is recoring. Use the attribute Offset_s when the recording does not begin at the beginning of each recording interval."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_RecordingInterval_s = "Time between consecutive recordings. If RecordingDuration_s is 1800 s and RecordingInterval_s is 3600 s, then we record for the 30 min of each hour."; + public static final String Deployment_QualityAssurance_Description = "Text based description of process."; + public static final String Deployment_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_QualityAssurance_Quality = "If no quality assurance, create an entry of Category unverified spanning the acoustic record."; + public static final String Deployment_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Deployment_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Deployment_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Deployment_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_QualityAssurance_Quality_Category = "categories: unverified, good, compromised, unusable"; + public static final String Deployment_QualityAssurance_Quality_FrequencyRange = "QA metric applies to what frequency range?"; + public static final String Deployment_QualityAssurance_Quality_Comment = "Additional qualitative information"; + public static final String Deployment_Data_Audio = "Information about audio data."; + public static final String Deployment_Data_Tracks = "A set of measurements about a ship/instrument's track line."; + public static final String Deployment_Data_Audio_URI = "Uniform Resource Indicator that points to audio content. Examples: digital object identifier, web address, or even a simple string describing the storage location."; + public static final String Deployment_Data_Audio_Processed = "Pointer to location of data that has been processed (e.g. checked for quality, decimated, etc.)"; + public static final String Deployment_Data_Audio_Raw = "Pointer to raw data from the instrument."; + public static final String Deployment_Data_Tracks_Track = "A set of sorted (by time) points associated with one or more tracklines."; + public static final String Deployment_Data_Tracks_TrackEffort = "Not all measurements are associated with an instrument/ship's planned trackline (e.g. when in chase mode or transiting between tracklines). Specify times for track effort here if needed."; + public static final String Deployment_Data_Tracks_URI = "Pointer to trackline information."; + public static final String Deployment_Data_Tracks_Track_TrackId = "Optional trackline number. If unimportant, everything can be put in one Points element."; + public static final String Deployment_Data_Tracks_Track_Point = "Timestamped measurements: long/lat, bearing, etc. Points should be sorted by timestamp."; + public static final String Deployment_Data_Tracks_Track_Point_Bearing_DegN = "Bearing in degrees [0, 360) relative to true or magnetic north (as specified by north attribute, default magnetic)"; + public static final String Deployment_Data_Tracks_Track_Point_Speed_kn = "Speed in knots"; + public static final String Deployment_Data_Tracks_Track_Point_Pitch_deg = "Instrument pitch [0, 360) degrees"; + public static final String Deployment_Data_Tracks_Track_Point_Roll_deg = "Instrument roll [0, 360) degrees"; + public static final String Deployment_Data_Tracks_Track_Point_Elevation_m = "Instrument elevation (meters) relative to average sea level."; + public static final String Deployment_Data_Tracks_Track_Point_GroundElevation_m = "Ground or seabed elevation (meters) relative to average sea level."; + public static final String Deployment_Data_Tracks_Track_Point_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_Data_Tracks_Track_Point_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_Data_Tracks_TrackEffort_OnPath_FocalArea = "This element is used to provide names that specify a focal area in which the study was conducted, such as a National Marine Sanctuary."; + public static final String Deployment_Data_Tracks_TrackEffort_OffPath_FocalArea = "This element is used to provide names that specify a focal area in which the study was conducted, such as a National Marine Sanctuary."; + public static final String Deployment_DeploymentDetails_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_DeploymentDetails_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_DeploymentDetails_ElevationInstrument_m = "The elevation at which this instrument is positioned."; + public static final String Deployment_DeploymentDetails_DepthInstrument_m = "Not usually required. This field is designed to record depth with respect to the ground or seabed. Uses for this field include mines and alpine lakes."; + public static final String Deployment_DeploymentDetails_Elevation_m = "Elevation of ground/sea bed"; + public static final String Deployment_DeploymentDetails_TimeStamp = "Time at which instrument was deployed/recovered. Lost instruments: set recovery time to deployment time."; + public static final String Deployment_DeploymentDetails_AudioTimeStamp = "Recording start or end - May differ from deployment time."; + public static final String Deployment_DeploymentDetails_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_DeploymentDetails_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_RecoveryDetails_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_RecoveryDetails_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_RecoveryDetails_ElevationInstrument_m = "The elevation at which this instrument is positioned."; + public static final String Deployment_RecoveryDetails_DepthInstrument_m = "Not usually required. This field is designed to record depth with respect to the ground or seabed. Uses for this field include mines and alpine lakes."; + public static final String Deployment_RecoveryDetails_Elevation_m = "Elevation of ground/sea bed"; + public static final String Deployment_RecoveryDetails_TimeStamp = "Time at which instrument was deployed/recovered. Lost instruments: set recovery time to deployment time."; + public static final String Deployment_RecoveryDetails_AudioTimeStamp = "Recording start or end - May differ from deployment time."; + public static final String Deployment_RecoveryDetails_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_RecoveryDetails_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_Sensors_Audio_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Audio_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Audio_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Audio_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Audio_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Audio_HydrophoneId = "Optional hydrophone identifier."; + public static final String Deployment_Sensors_Audio_PreampId = "Optional preamplifier identifier."; + public static final String Deployment_Sensors_Depth_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Depth_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Depth_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Depth_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Depth_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Sensor_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Sensor_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Sensor_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Sensor_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Sensor_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Sensor_Type = "Description of data gathered by this sensor, e.g., temperature"; + public static final String Deployment_Sensors_Sensor_Properties = "List of property elements describing the sensor. These may be arbitrary. Example: Properties can have child Units with value °C. Children may be nested."; + public static final String Deployment_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Deployment_MetadataInfo_Date = "Last update."; + public static final String Deployment_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Deployment_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + + // Annotations taken from schemata_csv + public static final String Detections_Id = "Identification string that is unique to all documents of this type (currently optional, will be required in the future)"; + public static final String Detections_Description = "Objectives, abstract and high-level methods."; + public static final String Detections_DataSource = "Acoustic data identifier."; + public static final String Detections_Algorithm = "Detailed methods."; + public static final String Detections_QualityAssurance = "Description of quality assurance checks (if any)."; + public static final String Detections_UserId = "User that submitted the document."; + public static final String Detections_Effort = "Span and scope of detection effort."; + public static final String Detections_OnEffort = "Collection of individual detections."; + public static final String Detections_OffEffort = "Collection of off-effort (ad-hoc) detections. Each detection has the same format as the OnEffort ones."; + public static final String Detections_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Detections_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Detections_Description_Abstract = "Overview of effort."; + public static final String Detections_Description_Method = "High-level description of the method used."; + public static final String Detections_DataSource_EnsembleId = "Serves as a foreign key into the ensembles collection and must match an Id element in an ensemble document. Ensembles are used to group instruments together for a common purpose (e.g. large aperture array)."; + public static final String Detections_DataSource_DeploymentId = "Serves as a foreign key into the Deployments collection and must match an Id element in a deployment document."; + public static final String Detections_Algorithm_Method = "Text based description of algorithm or citation"; + public static final String Detections_Algorithm_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Detections_Algorithm_Version = "Software version identifier"; + public static final String Detections_Algorithm_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Detections_Algorithm_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Detections_Algorithm_SupportSoftware_Version = "Software version identifier."; + public static final String Detections_Algorithm_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Detections_QualityAssurance_Description = "Text based description of process."; + public static final String Detections_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Detections_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Detections_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Detections_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Detections_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Detections_Effort_Start = "Timestamp indicating the start of systematic effort to find the species and phenomena listed in the Effort/Kind entries."; + public static final String Detections_Effort_End = "Timestamp indicating end of systematic effort."; + public static final String Detections_Effort_dBReferenceIntensity_uPa = "All dB measurements are made relative to this value in uPa. Typical values are 1 for underwater acoustics and 20 for terrestrial acoustics."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic = "Used to describe meaningful gaps in the analysis effort. Problems with the data should not be addressed here, but rather in Deployment/QualityAssurance/Quality. Note that tools may not take Gaps into account when reporting effort statistics."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen = "Peridoic analysis regimen may change over time. Each entry shows the start of an analysis regimen. The abscence of entries indicates continuous analysis as would having equal values in AnalysisDuration_s and AnalysisInterval_s. The time offsets in these fields are with respect to actual time. Duty cycled data are not taken into account in their specification. As an example, if we analyzed the first 30 min of each hour and the deployment's recording duty cycle were 15 min of recording every 30 min, this analysis duration would only result in 15 min of analysis every hour."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_TimeStamp = "Indicates when the regimen becomes active. It remains active until the next Regimen entry."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_AnalysisDuration_s = "When analysis starts, the data are analyzed for this many seconds. Optional attribute Offset_s may be used to denote the number of seconds after the timestamp that analysis started. If Offset_s is not present, analysis starts at the Timestamp."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_AnalysisInterval_s = "Time between consecutive effort. If AnalysisDuration_s is 1800 s and AnalysisInterval_s is 3600 s, then we perform analysis on the first 30 min of each hour starting at TimeStamp."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_Start = "Timestamp indicating the start of a gap in the systematic effort to find the species and phenomena listed in the Effort/Kind entries."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_End = "Timestamp indicating end of systematic effort gap."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_Reason = "Reason for gap in analysis."; + public static final String Detections_Effort_Kind_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_Effort_Kind_Call = "Name that describes call."; + public static final String Detections_Effort_Kind_Granularity = "Type of detections: call - individual call, encounter - set of calls, binned - presence detected within period specified by bin size attribute in Effort. grouped – A set of individual detections of any granularity that have been grouped together. Examples include situations such as song or other groupings (e.g. detections of the same animals picked up on multiple instruments). Grouped detections may specify the individual detections regardless of their granularity that are part of the group. This is different from granularities encounter and binned where one might expect multiple calls to occur, but the individual detections are not recorded."; + public static final String Detections_Effort_Kind_Parameters_Subtype = "subcategory of call"; + public static final String Detections_Effort_Kind_Parameters_FrequencyMeasurements_Hz = "Specifies a list of frequencies at which measurements are made. Each detection for this Kind should have a list of FrequencyMeasurements where each item corresponds to a frequency in this list. Useful for studying ambient sound or soundscapes. Be sure to declare Effort/ReferenceIntensity_uPa."; + public static final String Detections_OnEffort_Detection_Input_file = "Optional name of audio file (or indirect representation) from which this detection was generated."; + public static final String Detections_OnEffort_Detection_Start = "Time at which event started. For many detectors, this may not the actual starting time of the event."; + public static final String Detections_OnEffort_Detection_End = "Optional end time of event."; + public static final String Detections_OnEffort_Detection_Count = "An optional count of the number of times a call occurred within a bin or across an encounter."; + public static final String Detections_OnEffort_Detection_Event = "Optional tag for identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Detections_OnEffort_Detection_UnitId = "Specifies ensemble unit (when using an ensemble source)."; + public static final String Detections_OnEffort_Detection_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_OnEffort_Detection_Call = "In most cases, the call field should be present. May be omitted if the goal is species detection only, or repeated for multiple types of calls when the granularity effort is not \"call\"."; + public static final String Detections_OnEffort_Detection_Image = "Name of image file (spectrogram, etc.)"; + public static final String Detections_OnEffort_Detection_Audio = "Name of audio file (short snippet)"; + public static final String Detections_OnEffort_Detection_Parameters_Subtype = "subcategory of call"; + public static final String Detections_OnEffort_Detection_Parameters_Score = "Measure from detector, e.g. likelihood ratio, projection, etc."; + public static final String Detections_OnEffort_Detection_Parameters_Confidence = "Measure of confidence in detection. Range: [0, 1]"; + public static final String Detections_OnEffort_Detection_Parameters_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Detections_OnEffort_Detection_Parameters_ReceivedLevel_dB = "dB relative to reference intensity defined in Effort/ReferenceIntenstiy_uPa"; + public static final String Detections_OnEffort_Detection_Parameters_FrequencyMeasurements_dB = "List of received levels at various frequencies relative to the reference value defiend in Effort/ReferenceIntensity_uPa. The frequency measurements should be consistent for each species and call type and must correspond to a a list of frequencies defined in Effort/Kind/SubType."; + public static final String Detections_OnEffort_Detection_Parameters_Peaks_Hz = "Typically used for spectra of short echolocation bursts, notes the spectral peaks in a list sorted from low to high frequency."; + public static final String Detections_OnEffort_Detection_Parameters_Duration_s = "When the call granularity is binned or encounter, this may be used to describe the mean duration of calls during the bout. As an example, at SIO we use this to track the mean duration of binned anthropogenic sources such as explosions."; + public static final String Detections_OnEffort_Detection_Parameters_Sideband_Hz = "Signal sideband frequencies in a list sorted from low to high frequency."; + public static final String Detections_OnEffort_Detection_Parameters_EventRef = "Reference to other detections for hierarchical organization."; + public static final String Detections_OnEffort_Detection_Parameters_UserDefined = "Study specific parameters"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_Offset_s = "List of offsets from start in seconds"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_Hz = "Frequency measurement for each Offset_s (Hz). List must be of same length as Offset_s"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_dB = "Optional intensity measurment (dB) for each Offset_s (dB). List must be of the same length as Offset_s"; + public static final String Detections_OffEffort_Detection_Input_file = "Optional name of audio file (or indirect representation) from which this detection was generated."; + public static final String Detections_OffEffort_Detection_Start = "Time at which event started. For many detectors, this may not the actual starting time of the event."; + public static final String Detections_OffEffort_Detection_End = "Optional end time of event."; + public static final String Detections_OffEffort_Detection_Count = "An optional count of the number of times a call occurred within a bin or across an encounter."; + public static final String Detections_OffEffort_Detection_Event = "Optional tag for identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Detections_OffEffort_Detection_UnitId = "Specifies ensemble unit (when using an ensemble source)."; + public static final String Detections_OffEffort_Detection_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_OffEffort_Detection_Call = "In most cases, the call field should be present. May be omitted if the goal is species detection only, or repeated for multiple types of calls when the granularity effort is not \"call\"."; + public static final String Detections_OffEffort_Detection_Image = "Name of image file (spectrogram, etc.)"; + public static final String Detections_OffEffort_Detection_Audio = "Name of audio file (short snippet)"; + public static final String Detections_OffEffort_Detection_Parameters_Subtype = "subcategory of call"; + public static final String Detections_OffEffort_Detection_Parameters_Score = "Measure from detector, e.g. likelihood ratio, projection, etc."; + public static final String Detections_OffEffort_Detection_Parameters_Confidence = "Measure of confidence in detection. Range: [0, 1]"; + public static final String Detections_OffEffort_Detection_Parameters_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Detections_OffEffort_Detection_Parameters_ReceivedLevel_dB = "dB relative to reference intensity defined in Effort/ReferenceIntenstiy_uPa"; + public static final String Detections_OffEffort_Detection_Parameters_FrequencyMeasurements_dB = "List of received levels at various frequencies relative to the reference value defiend in Effort/ReferenceIntensity_uPa. The frequency measurements should be consistent for each species and call type and must correspond to a a list of frequencies defined in Effort/Kind/SubType."; + public static final String Detections_OffEffort_Detection_Parameters_Peaks_Hz = "Typically used for spectra of short echolocation bursts, notes the spectral peaks in a list sorted from low to high frequency."; + public static final String Detections_OffEffort_Detection_Parameters_Duration_s = "When the call granularity is binned or encounter, this may be used to describe the mean duration of calls during the bout. As an example, at SIO we use this to track the mean duration of binned anthropogenic sources such as explosions."; + public static final String Detections_OffEffort_Detection_Parameters_Sideband_Hz = "Signal sideband frequencies in a list sorted from low to high frequency."; + public static final String Detections_OffEffort_Detection_Parameters_EventRef = "Reference to other detections for hierarchical organization."; + public static final String Detections_OffEffort_Detection_Parameters_UserDefined = "Study specific parameters"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_Offset_s = "List of offsets from start in seconds"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_Hz = "Frequency measurement for each Offset_s (Hz). List must be of same length as Offset_s"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_dB = "Optional intensity measurment (dB) for each Offset_s (dB). List must be of the same length as Offset_s"; + public static final String Detections_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Detections_MetadataInfo_Date = "Last update."; + public static final String Detections_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Detections_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + + // Annotations taken from schemata_csv + public static final String Ensemble_Id = "Ensemble name (unique identifier)."; + public static final String Ensemble_Unit = "Associates a virtual unit of an ensemble with an actual deployment."; + public static final String Ensemble_ZeroPosition = "Provides a zero point to which relative localizations can be referenced."; + public static final String Ensemble_Unit_UnitId = "A unique unit number that identifies this instrument within the ensemble."; + public static final String Ensemble_Unit_DeploymentId = "Reference to a deployment document Id field. Uniquely identifies the deployment."; + public static final String Ensemble_ZeroPosition_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Ensemble_ZeroPosition_Latitude = "Expressed in degrees North [-90, 90]"; + + + // Annotations taken from schemata_csv + public static final String Localize_Id = "Identification string that is unique to all documents of this type"; + public static final String Localize_Description = "Text based description of process."; + public static final String Localize_DataSource = "Indicates the deployment or ensemble from which the process (e.g. detector) derived information."; + public static final String Localize_Algorithm = "Description of an algorithm or process."; + public static final String Localize_QualityAssurance = "Description of quality assurance checks (if any)."; + public static final String Localize_ResponsibleParty = "Person/organization responsible for generating metadata"; + public static final String Localize_UserId = "User that submitted the document."; + public static final String Localize_IntermediateData = "Derived data that is used for the localizations that the user wishes to retain."; + public static final String Localize_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Localize_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Localize_Description_Abstract = "Overview of effort."; + public static final String Localize_Description_Method = "High-level description of the method used."; + public static final String Localize_DataSource_EnsembleId = "Serves as a foreign key into the ensembles collection and must match an Id element in an ensemble document. Ensembles are used to group instruments together for a common purpose (e.g. large aperture array)."; + public static final String Localize_DataSource_DeploymentId = "Serves as a foreign key into the Deployments collection and must match an Id element in a deployment document."; + public static final String Localize_Algorithm_Method = "Text based description of algorithm or citation"; + public static final String Localize_Algorithm_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Localize_Algorithm_Version = "Software version identifier"; + public static final String Localize_Algorithm_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Localize_Algorithm_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Localize_Algorithm_SupportSoftware_Version = "Software version identifier."; + public static final String Localize_Algorithm_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Localize_QualityAssurance_Description = "Text based description of process."; + public static final String Localize_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Localize_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Localize_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Localize_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Localize_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Localize_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Localize_Effort_Start = "Time at which we started looking for location information."; + public static final String Localize_Effort_End = "Time at which we stopped looking for location information."; + public static final String Localize_Effort_CoordinateSystem = "What type of localization information is produced?"; + public static final String Localize_Effort_LocalizationType = "Type of localization effort: Bearing, Ranging, Point, Track"; + public static final String Localize_Effort_CoordinateSystem_Type = "How are positions represented? WGS84: global positioning system lat/long, cartesian: m relative to a point, UTM: universal trans Mercatur map-projection, Bearing: Polar measurements of angle and possibly distance."; + public static final String Localize_Effort_CoordinateSystem_Relative = "For bearings, this gives the direction vector for the zero bearing. Angles are measured counter-clockwise to this vector. For cartesian coordinates, this provides the zero point relative to the deployment position or the ReferencePoint of an ensemble."; + public static final String Localize_Effort_CoordinateSystem_UTM = "Parameters for Universal Trans Mercatur projections. NEED TO INVESTIGATE FURTHER AS TO WHETHER THIS OR ANY OTHER PROJECTION IS WORTH ADDING"; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing = "Designed to be a subset of OpenGML DirectionVectorType: http://schemas.opengis.net/gml/3.1.1/base/direction.xsd Unlike the OpenGML, direction may not be specified as a vector, and the verticalAngle is optional."; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Effort_CoordinateSystem_UTM_Zone = "NS zone [1-60]. Each zone covers 80°S to 84°N in 6° width zones. Zone 1 180 is 180-186° E with increasing zone #s corresponding to 6° eastward increments."; + public static final String Localize_Effort_ReferencedDocuments_Document_Type = "What type of document is being referenced? Detections or Localizations"; + public static final String Localize_Effort_ReferencedDocuments_Document_Id = "Unique identifier string for the document being referenced."; + public static final String Localize_Effort_ReferencedDocuments_Document_Index = "All localizations that wish to reference other detections or localizations from the referenced document should use this index value."; + public static final String Localize_IntermediateData_Correlations_Correlation_Primary = "Primary hydropphone"; + public static final String Localize_IntermediateData_Correlations_Correlation_Secondary = "Secondary hydropphone"; + public static final String Localize_IntermediateData_Correlations_Correlation_Correlations = "Correlation between detections on primary hydrophone and signals on secondary hydrophones. Each column j is the set of lags corresponding to the j'th detection on the primary hydrophone."; + public static final String Localize_Localizations_Localization_Event = "Optional tag typically in ISO datetime format YYYY-MM-DDTHH:MM:SSZ identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made. When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Localize_Localizations_Localization_TimeStamp = "Time of localization in reference time frame (e.g. time of arrival at primary hydrophone)"; + public static final String Localize_Localizations_Localization_SpeciesId = "Species can be identified by the detections from the detections that are referenced. As these references are not mandatory, the optional SpeciesID can be used to identify the species that produced the localized source."; + public static final String Localize_Localizations_Localization_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Localize_Localizations_Localization_Bearing = "Direction towards acoustic source."; + public static final String Localize_Localizations_Localization_Ranging = "Range and direction towards acoustic source. Combine with bearing, rename angular Rename StdError to Error specify in methods/algorrithm"; + public static final String Localize_Localizations_Localization_WGS84 = "Longitude, latitude and possibly elevation of surce."; + public static final String Localize_Localizations_Localization_Cartesian = "Relative distance to source from receiver zero point."; + public static final String Localize_Localizations_Localization_Track = "Series of associated positions and timestamps"; + public static final String Localize_Localizations_Localization_References_TimeReferenceEnsembleUnit = "STILL NEEDED? Time references which unit of the ensemble (see TimeReferenceChannel) when ensembles are used."; + public static final String Localize_Localizations_Localization_References_TimeReferenceChannel = "STILL NEEDED? Events are detected at different times on different channels, making it necessary to provide the instrument and channel on which the timestamp references."; + public static final String Localize_Localizations_Localization_References_Reference = "Detections/localization used in constructing this localization."; + public static final String Localize_Localizations_Localization_References_Reference_Index = "Must match instance of Index in ReferencedDocuments. This permits identification of a specific document."; + public static final String Localize_Localizations_Localization_References_Reference_EventRef = "Event identifier that uniquely identifies a detection or localization within a referenced document."; + public static final String Localize_Localizations_Localization_Bearing_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Bearing_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Bearing_Ambiguous = "Left right horizontal ambiguity about the bearing reference vector exists?"; + public static final String Localize_Localizations_Localization_Bearing_StdError = "Standard error in degrees for the measurement."; + public static final String Localize_Localizations_Localization_Bearing_StdError_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Bearing_StdError_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Ranging_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_Range_m = "Distance to localized animal/object/phenomenon in meters."; + public static final String Localize_Localizations_Localization_Ranging_Ambiguous = "Left right horizontal ambiguity about the bearing reference vector exists?"; + public static final String Localize_Localizations_Localization_Ranging_StdError_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Ranging_StdError_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_StdError_Range_m = "Distance to localized animal/object/phenomenon in meters."; + public static final String Localize_Localizations_Localization_WGS84_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_WGS84_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_WGS84_AlternatePosition = "Add LongLat3 and StdError Cartesian"; + public static final String Localize_Localizations_Localization_WGS84_StdError_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_WGS84_StdError_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_Cartesian_BearingIDs = "If multiple bearings were used to create this localization, their ids can be provided."; + public static final String Localize_Localizations_Localization_Cartesian_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_Cartesian_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_Cartesian_BearingIDs_EventRef = "Reference to individual bearing within this XML document."; + public static final String Localize_Localizations_Localization_Track_WGS84 = "Series of points or list of values for each type long/lat/depth/elevation/timestamp parameters for each call: receivedLevel_dB sourceLevel_dB ambient_dB"; + public static final String Localize_Localizations_Localization_Track_Cartesian = "Todo: define Cartesian list"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds = "Bounding box for tempo-spatial data from northwest to southeast quadrant. If elevation/depth information is available, separate depth boundaries are given as well. Note that longitudes are always degrees east. When a track crosses 0° east, the northwest longitude will be > than the souteast longitude (e.g. a 2° path from 359°E to 1°E)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_NorthWest_Longitude = "Longitude is expressed in degrees East [0,360)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_NorthWest_Latitude = "Expressed in degrees North [-90,90]"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_SouthEast_Longitude = "Longitude is expressed in degrees East [0,360)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_SouthEast_Latitude = "Expressed in degrees North [-90,90]"; + public static final String Localize_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Localize_MetadataInfo_Date = "Last update."; + public static final String Localize_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Localize_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115";} diff --git a/src/userDisplay/UserDisplayControl.java b/src/userDisplay/UserDisplayControl.java index 15575bf1..2fe0b39d 100644 --- a/src/userDisplay/UserDisplayControl.java +++ b/src/userDisplay/UserDisplayControl.java @@ -21,7 +21,6 @@ package userDisplay; import java.awt.Frame; -import java.awt.MenuItem; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.Serializable; @@ -31,18 +30,14 @@ import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; -import clickDetector.ClickClassifiers.UserTypesPanel; import dataPlots.TDDisplayProvider; -import dataPlotsFX.JamieDev; import dataPlotsFX.TDDisplayProviderFX; -import radardisplay.RadarDisplay; import radardisplay.RadarDisplayProvider; import radardisplay.RadarParameters; import radardisplay.RadarParametersDialog; import radardisplay.RadarProjector; import soundPlayback.PlaybackControl; import fftManager.FFTDataUnit; -import localiserDisplay.LocaliserDisplayProvider; import pamScrollSystem.AbstractScrollManager; import pamScrollSystem.coupling.ScrollerCoupling; //import localiserDisplay.LocaliserDisplayProvider; @@ -55,8 +50,6 @@ import PamController.PamSettings; import PamModel.PamDependency; import PamModel.PamDependent; import PamModel.PamModel; -import PamModel.SMRUEnable; -import PamView.MenuItemEnabler; import PamView.PamGui; import Spectrogram.SpectrogramDiplayProvider; import Spectrogram.SpectrogramParameters; @@ -95,10 +88,10 @@ public class UserDisplayControl extends PamControlledUnit implements TDDisplayProvider.register(); TDDisplayProviderFX.register(); - //register the localiser display. - if (JamieDev.isEnabled()) { - LocaliserDisplayProvider.register(); - } +// //register the localiser display. +// if (JamieDev.isEnabled()) { +// LocaliserDisplayProvider.register(); +// } } diff --git a/src/userDisplayFX/SwingDisplayNodeFX.java b/src/userDisplayFX/SwingDisplayNodeFX.java index 514bf3de..94eb479b 100644 --- a/src/userDisplayFX/SwingDisplayNodeFX.java +++ b/src/userDisplayFX/SwingDisplayNodeFX.java @@ -143,4 +143,10 @@ public class SwingDisplayNodeFX implements UserDisplayNodeFX{ } + @Override + public UserDisplayControlFX getUserDisplayControl() { + // TODO Auto-generated method stub + return null; + } + } \ No newline at end of file diff --git a/src/userDisplayFX/UserDisplayNodeFX.java b/src/userDisplayFX/UserDisplayNodeFX.java index eb3c45aa..68f9bf67 100644 --- a/src/userDisplayFX/UserDisplayNodeFX.java +++ b/src/userDisplayFX/UserDisplayNodeFX.java @@ -10,6 +10,13 @@ import pamViewFX.fxNodes.internalNode.PamInternalPane; */ public interface UserDisplayNodeFX { + /** + * Get a display controller associated with the display. Note that a display does not need a controller + * so this can return null. + * @return the associated control for the display. + */ + public UserDisplayControlFX getUserDisplayControl(); + /** * The name of display. Used for default tabs etc. * @return the name of the display diff --git a/src/videoRangePanel/VRControl.java b/src/videoRangePanel/VRControl.java index a8225eeb..1420e5ea 100644 --- a/src/videoRangePanel/VRControl.java +++ b/src/videoRangePanel/VRControl.java @@ -511,7 +511,7 @@ public class VRControl extends PamControlledUnit implements PamSettings { long time1=System.currentTimeMillis(); if (vrTabPanelControl.openImageFile(file)) { //now set the scroller to move to to the correct imu or angle data; - if (currentImage.getTimeMilliseconds()!=0 && !checkViewLoadTime(currentImage.getTimeMilliseconds())){ + if (currentImage!=null && currentImage.getTimeMilliseconds()!=0 && !checkViewLoadTime(currentImage.getTimeMilliseconds())){ vrScroller.setRangeMillis(currentImage.getTimeMilliseconds()-loadTime, currentImage.getTimeMilliseconds()+loadTime, true); } } diff --git a/src/videoRangePanel/importTideData/CSVTideParser.java b/src/videoRangePanel/importTideData/CSVTideParser.java new file mode 100644 index 00000000..31d68fd7 --- /dev/null +++ b/src/videoRangePanel/importTideData/CSVTideParser.java @@ -0,0 +1,42 @@ +package videoRangePanel.importTideData; + +import java.util.ArrayList; +import java.util.List; + +import PamUtils.LatLong; +import PamUtils.PamCalendar; +import PamUtils.TxtFileUtils; + +public class CSVTideParser implements TideParser { + + @Override + public TideDataUnit parseTideLine(String line, LatLong location) { + + ArrayList txtData= TxtFileUtils.parseTxtLine(line, ","); + + String date = txtData.get(0); + + long timeMillis = PamCalendar.unpackStandardDateTime(date); + + double level = Double.valueOf( txtData.get(1)); + + TideDataUnit tideDataUnit = new TideDataUnit(timeMillis, level, 0, 0, null ); + + System.out.println("Tide data: " + PamCalendar.formatDateTime(timeMillis) + " level: " + level); + + + return tideDataUnit; + } + + @Override + public LatLong getLocation(List txtData) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getName() { + return "CSV Parser"; + } + +} diff --git a/src/videoRangePanel/importTideData/POLPREDParser2.java b/src/videoRangePanel/importTideData/POLPREDParser2.java index f016d9ff..29116753 100644 --- a/src/videoRangePanel/importTideData/POLPREDParser2.java +++ b/src/videoRangePanel/importTideData/POLPREDParser2.java @@ -12,6 +12,7 @@ import PamUtils.TxtFileUtils; /** * Polpred tide file + * * @author Jamie Macaulay * */ diff --git a/src/videoRangePanel/importTideData/TideManager.java b/src/videoRangePanel/importTideData/TideManager.java index 593a4c45..0b989703 100644 --- a/src/videoRangePanel/importTideData/TideManager.java +++ b/src/videoRangePanel/importTideData/TideManager.java @@ -74,6 +74,7 @@ public class TideManager extends DataImport { tideParsers.add(new POLPREDParser1()); tideParsers.add(new POLPREDParser2()); tideParsers.add(new POLPREDParser3()); + tideParsers.add(new CSVTideParser()); } diff --git a/src/videoRangePanel/layoutFX/VRDisplayFX.java b/src/videoRangePanel/layoutFX/VRDisplayFX.java index d43850b6..5c3c7d3e 100644 --- a/src/videoRangePanel/layoutFX/VRDisplayFX.java +++ b/src/videoRangePanel/layoutFX/VRDisplayFX.java @@ -156,9 +156,9 @@ public class VRDisplayFX extends PamBorderPane implements VRPane { this.setCenter(holder); - //need to open image file here because the FX stuff takes a bit of time to load - //on the FX thread. If this is attempted in control constructor the FX GUI has not - //loaded and so a null pointer exception is thrown. +// //need to open image file here because the FX stuff takes a bit of time to load +// //on the FX thread. If this is attempted in control constructor the FX GUI has not +// //loaded and so a null pointer exception is thrown. if (this.vrControl.getVRParams().currentImageFile!=null && vrControl.getVRParams().currentImageFile.exists()) { vrImagePane.openNewFile(this.vrControl.getVRParams().currentImageFile); @@ -414,5 +414,10 @@ public class VRDisplayFX extends PamBorderPane implements VRPane { } + public VRMetaDataPaneFX getInfoPane() { + return getImagePane().getInfoPane(); + } + + } diff --git a/src/videoRangePanel/layoutFX/VRDisplayFX2AWT.java b/src/videoRangePanel/layoutFX/VRDisplayFX2AWT.java index b430cdc4..32f63090 100644 --- a/src/videoRangePanel/layoutFX/VRDisplayFX2AWT.java +++ b/src/videoRangePanel/layoutFX/VRDisplayFX2AWT.java @@ -3,6 +3,7 @@ package videoRangePanel.layoutFX; import java.awt.Point; import java.io.File; +import javafx.application.Platform; import javafx.scene.layout.Pane; import videoRangePanel.VRControl; import videoRangePanel.VRPane; @@ -71,9 +72,11 @@ public class VRDisplayFX2AWT implements VRPane { } public void update(int updateType) { + Platform.runLater(()->{ if (vRDisplayFX!=null) { vRDisplayFX.update(updateType); } + }); } /** diff --git a/src/videoRangePanel/layoutFX/VRImagePane.java b/src/videoRangePanel/layoutFX/VRImagePane.java index 97b3cd61..5b24d8da 100644 --- a/src/videoRangePanel/layoutFX/VRImagePane.java +++ b/src/videoRangePanel/layoutFX/VRImagePane.java @@ -3,6 +3,7 @@ package videoRangePanel.layoutFX; import java.awt.Point; import java.io.File; import java.net.URLConnection; +import java.util.ArrayList; import javafx.animation.KeyFrame; import javafx.animation.Timeline; @@ -205,7 +206,7 @@ public class VRImagePane extends PamBorderPane { scrollPane = new VRZoomScrollPane(stackHolder); scrollPane.setPannable(true); - scrollPane.getStylesheets().add(getDarkStyle() ); + scrollPane.getStylesheets().addAll(getDarkStyle() ); scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS); scrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS); @@ -218,7 +219,7 @@ public class VRImagePane extends PamBorderPane { hidingBottomHolder.setStyle("-fx-background-color: -fx-darkbackground-trans"); //from dark stylesheet //Bit of a hack here. Have set the offset to 15 pixels so that the hiding pane does not cover the scroll bar. hiddenControlPane = new HidingPane(Side.BOTTOM , hidingBottomHolder , mainHolder, true, 15); - hiddenControlPane.getStylesheets().add(getDarkStyle() ); + hiddenControlPane.getStylesheets().addAll(getDarkStyle() ); hiddenControlPane.showHidePane(true); hiddenControlPane.getChildren().remove( hiddenControlPane.getHideButton()); @@ -227,7 +228,7 @@ public class VRImagePane extends PamBorderPane { hidingRightHolder.setCenter(null); hidingRightHolder.setStyle("-fx-background-color: -fx-darkbackground-trans"); //from dark stylesheet hiddenRightPane = new HidingPane(Side.RIGHT , hidingRightHolder , mainHolder, true); - hiddenRightPane.getStylesheets().add(getDarkStyle() ); + hiddenRightPane.getStylesheets().addAll(getDarkStyle() ); //****add main image pane to stack pane****// mainHolder.getChildren().add(scrollPane); @@ -249,7 +250,7 @@ public class VRImagePane extends PamBorderPane { imageControls.setPadding(new Insets(10,10,10,10)); imageControls.setMaxWidth(300); imageControls.setMaxHeight(35); - imageControls.getStylesheets().add(getDarkStyle() ); + imageControls.getStylesheets().addAll(getDarkStyle() ); imageControls.setStyle("-fx-background-color: -fx-darkbackground-trans"); //from dark stylesheet @@ -352,7 +353,7 @@ public class VRImagePane extends PamBorderPane { StackPane.setAlignment(ribbonHolder, Pos.TOP_LEFT); ribbonHolder.setMaxHeight(35); ribbonHolder.maxWidthProperty().bind(stackPane.widthProperty().subtract(imageControls.widthProperty())); - ribbonHolder.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); + ribbonHolder.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getDialogCSS()); //ribbonHolder.setStyle("-fx-background-color: -fx-darkbackground-trans"); //from dark stylesheet mainHolder.getChildren().add(ribbonHolder); @@ -510,8 +511,9 @@ public class VRImagePane extends PamBorderPane { break; } stackHolder.getChildren().add(vrImage.getNode()); + vrImage.setMedia(currentFile); - + //set the meta data. infoPane.setMetaText(vrImage.getMetaData()); //set the image edit pane. @@ -522,6 +524,8 @@ public class VRImagePane extends PamBorderPane { //set the control pane. hidingBottomHolder.setCenter(vrImage.getControlPane()); + + } /** @@ -754,10 +758,14 @@ public class VRImagePane extends PamBorderPane { * Get dark style CSS. * @return dark style CSS. */ - private String getDarkStyle() { + private ArrayList getDarkStyle() { return PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS(); } + public VRMetaDataPaneFX getInfoPane() { + return infoPane; + } + diff --git a/src/videoRangePanel/layoutFX/VRMediaView.java b/src/videoRangePanel/layoutFX/VRMediaView.java index ea386fa2..6ef0f952 100644 --- a/src/videoRangePanel/layoutFX/VRMediaView.java +++ b/src/videoRangePanel/layoutFX/VRMediaView.java @@ -5,6 +5,7 @@ import java.net.MalformedURLException; import java.util.ArrayList; import PamUtils.PamCalendar; +import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableMap; @@ -196,27 +197,55 @@ public class VRMediaView extends PamBorderPane implements VRImage { public boolean setMedia(File currentFile) { this.currentFile=currentFile; this.fileTime=currentFile.lastModified(); - + try { media = new Media(currentFile.toURI().toURL().toExternalForm()); + + + //player.getMedia().getMetadata(); + // Media media = new Media("file:///home/paul/MoviePlayer/trailers/sintel.mp4"); + player = new MediaPlayer(media); + + //the media player is loaded asynchronously so must wait. + player.setOnReady(() -> { + //the meta data + this.metaData = getMetaData(media); + setUpPlayer() ; + setImageFreeze(); + + Platform.runLater(()->{ + vrDisplayFX.getInfoPane().setMetaText( getMetaData()); + }); + + }); + // player.setAutoPlay(true); + mediaView.setMediaPlayer(player); + + //wait for the player to be ready.... + //player.getOnReady().wait(); + + //18/09/2023 - really no great but only way I could + Thread.sleep(100); + + + // while (player.getStatus()==MediaPlayer.Status.UNKNOWN) { + + //not great just to put a sleep here but while loop to wait on the + // Thread.sleep(100); + // System.out.println("Status: " + player.getStatus()); + // player.play(); + // } + + return true; + } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); - return false; + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - - //the meta data - this.metaData = getMetaData(media); - player.getMedia().getMetadata(); - // Media media = new Media("file:///home/paul/MoviePlayer/trailers/sintel.mp4"); - player = new MediaPlayer(media); - setUpPlayer() ; - mediaView.setMediaPlayer(player); - - setImageFreeze(); - //player.play(); //TEMP - - return true; + return false; } @@ -229,22 +258,24 @@ public class VRMediaView extends PamBorderPane implements VRImage { MetaData metaData = new MetaData(); try { ObservableMap vidMetaData = currentFile.getMetadata(); - + + // System.out.println("File information: " + currentFile.getHeight() + " " + currentFile.getWidth() + " " + currentFile.getMetadata()); + //TODO - need to implement JavaFX metadata here. -// printDetails("Bit-rate", metaData.bitRate=currentFile.getBitRate()); -// printDetails("Frame Rate", metaData.frameRate=vInfo.getFrameRate()); + // printDetails("Bit-rate", metaData.bitRate=currentFile.getBitRate()); + // printDetails("Frame Rate", metaData.frameRate=vInfo.getFrameRate()); printDetails("Height", metaData.height=currentFile.getHeight()); printDetails("Width", metaData.width=currentFile.getHeight()); printDetails("Duration", metaData.duration=(long) currentFile.getDuration().toMillis()); -// printDetails("Format", metaData.format=info.getFormat()); -// -// printDetails("Bit-rate", metaData.bitRate=info.getAudio().getBitRate()); -// printDetails("Channels", metaData.audioChannels=info.getAudio().getChannels()); -// printDetails("Sampling Rate", metaData.audioSamplingRate=info.getAudio().getSamplingRate()); + // printDetails("Format", metaData.format=info.getFormat()); + // + // printDetails("Bit-rate", metaData.bitRate=info.getAudio().getBitRate()); + // printDetails("Channels", metaData.audioChannels=info.getAudio().getChannels()); + // printDetails("Sampling Rate", metaData.audioSamplingRate=info.getAudio().getSamplingRate()); } catch (Exception e) { - System.out.println("Could nto find meta data!"); + System.out.println("Could not find meta data!"); e.printStackTrace(); } return metaData; @@ -252,27 +283,31 @@ public class VRMediaView extends PamBorderPane implements VRImage { boolean wasPausedOnDrag =false; + + /** * Set up the player controls to play, pause and set time of media. This is called whenever a new Media File is * played. */ private void setUpPlayer() { - player.setOnReady(new Runnable() { - @Override - public void run() { + // player.setOnReady(new Runnable() { + // @Override + // public void run() { - int w = player.getMedia().getWidth(); - int h = player.getMedia().getHeight(); + int w = player.getMedia().getWidth(); + int h = player.getMedia().getHeight(); - seekslider.setMin(0.0); - seekslider.setValue(0.0); - seekslider.setMax(player.getTotalDuration().toSeconds()); + seekslider.setMin(0.0); + seekslider.setValue(0.0); + seekslider.setMax(player.getTotalDuration().toSeconds()); - player.setVolume(0.5); + // System.out.println("Get total duration:" + player.getTotalDuration()); - } - }); + player.setVolume(0.5); + + // } + // }); /******Time Slider****/ @@ -289,7 +324,7 @@ public class VRMediaView extends PamBorderPane implements VRImage { } }); - + //need to pause the video if the slider is dragged and than start again when slider is Ok. seekslider.valueChangingProperty().addListener((obsVal, oldVal, newVal)->{ if (newVal && newVal!=oldVal) { @@ -361,12 +396,12 @@ public class VRMediaView extends PamBorderPane implements VRImage { player.setOnPlaying(()->{ -// playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PAUSE, controlIconSize)); + // playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PAUSE, controlIconSize)); playButton.setGraphic(PamGlyphDude.createPamIcon("mdi2p-pause", controlIconSize)); }); player.setOnPaused(()->{ -// playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PLAY_ARROW, controlIconSize)); + // playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PLAY_ARROW, controlIconSize)); playButton.setGraphic(PamGlyphDude.createPamIcon("mdi2p-play", controlIconSize)); }); @@ -423,21 +458,21 @@ public class VRMediaView extends PamBorderPane implements VRImage { */ private void setVolumeGraphic(double volume) { if (volume ==0) { -// volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_OFF, controlIconSize)); + // volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_OFF, controlIconSize)); volumeButton.setGraphic(PamGlyphDude.createPamIcon("mdi2v-volume-off", controlIconSize)); } else if (volume>0 && volume<=0.33) { -// volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_MUTE, controlIconSize)); + // volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_MUTE, controlIconSize)); volumeButton.setGraphic(PamGlyphDude.createPamIcon("mdi2v-volume-mute", controlIconSize)); } else if (volume>0.33 && volume<=0.66) { -// volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_DOWN, controlIconSize)); + // volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_DOWN, controlIconSize)); volumeButton.setGraphic(PamGlyphDude.createPamIcon("mdi2v-volume-medium", controlIconSize)); } else if (volume>0.66 && volume<1) { -// volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_UP, controlIconSize)); + // volumeButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.VOLUME_UP, controlIconSize)); volumeButton.setGraphic(PamGlyphDude.createPamIcon("mdi2v-volume-high", controlIconSize)); } @@ -452,10 +487,11 @@ public class VRMediaView extends PamBorderPane implements VRImage { } /** - * Gte the duration of a single frame. - * @return the duration of a single frame. + * Get the duration of a single frame. + * @return the duration of a single frame in milliseconds. */ private long getSingleFrameDuration() { + if ( metaData.frameRate==0.0) return 100; long frameDuration = (long) (Math.ceil(1000.*(1/(double) metaData.frameRate))); return frameDuration; } @@ -471,7 +507,7 @@ public class VRMediaView extends PamBorderPane implements VRImage { //long frameDuration = (long) (Math.ceil(1000.*(1/(double) metaData.frameRate))); -// System.out.println("Frame duration millis: " + millis); + // System.out.println("Frame duration millis: " + millis); if (!forward) millis =-millis; @@ -531,44 +567,44 @@ public class VRMediaView extends PamBorderPane implements VRImage { //the play and pause button playButton = new PamButton(); -// playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PLAY_ARROW, controlIconSize)); + // playButton.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.PLAY_ARROW, controlIconSize)); playButton.setGraphic(PamGlyphDude.createPamIcon("mdi2p-play", controlIconSize)); playButton.getStyleClass().add("square-button-trans"); playButton.setTooltip(new Tooltip("Play or pause the media")); leftFrame = new PamButton(); -// leftFrame.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.CHEVRON_LEFT, controlIconSize)); + // leftFrame.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.CHEVRON_LEFT, controlIconSize)); leftFrame.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-left", controlIconSize)); leftFrame.getStyleClass().add("square-button-trans"); leftFrame.setTooltip(new Tooltip("Move to the previous frame")); rightFrame= new PamButton(); -// rightFrame.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.CHEVRON_RIGHT,controlIconSize)); + // rightFrame.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.CHEVRON_RIGHT,controlIconSize)); rightFrame.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-right",controlIconSize)); rightFrame.getStyleClass().add("square-button-trans"); rightFrame.setTooltip(new Tooltip("Move to the next frame")); nextMedia= new PamButton(); -// nextMedia.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SKIP_PREVIOUS,controlIconSize)); + // nextMedia.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SKIP_PREVIOUS,controlIconSize)); nextMedia.setGraphic(PamGlyphDude.createPamIcon("mdi2s-skip-previous",controlIconSize)); nextMedia.getStyleClass().add("square-button-trans"); nextMedia.setTooltip(new Tooltip("Move to the next image or video")); prevMedia= new PamButton(); -// prevMedia.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SKIP_NEXT, controlIconSize)); + // prevMedia.setGraphic(PamGlyphDude.createPamGlyph(MaterialIcon.SKIP_NEXT, controlIconSize)); prevMedia.setGraphic(PamGlyphDude.createPamIcon("mdi2s-skip-next", controlIconSize)); prevMedia.getStyleClass().add("square-button-trans"); prevMedia.setTooltip(new Tooltip("Move to the previous image or video")); skipForward= new PamButton(); -// skipForward.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.CHEVRON_DOUBLE_RIGHT, controlIconSize)); + // skipForward.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.CHEVRON_DOUBLE_RIGHT, controlIconSize)); skipForward.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-double-right", controlIconSize)); skipForward.getStyleClass().add("square-button-trans"); skipForward.setTooltip(new Tooltip("Skip 2 seconds forward")); skipBackward= new PamButton(); -// skipBackward.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.CHEVRON_DOUBLE_LEFT, controlIconSize)); + // skipBackward.setGraphic(PamGlyphDude.createPamGlyph(MaterialDesignIcon.CHEVRON_DOUBLE_LEFT, controlIconSize)); skipBackward.setGraphic(PamGlyphDude.createPamIcon("mdi2c-chevron-double-left", controlIconSize)); skipBackward.getStyleClass().add("square-button-trans"); skipBackward.setTooltip(new Tooltip("Skip 2 seconds backward")); @@ -661,7 +697,7 @@ public class VRMediaView extends PamBorderPane implements VRImage { * Size of the video file */ public int height; - + /** * Size of the video file */ @@ -688,6 +724,12 @@ public class VRMediaView extends PamBorderPane implements VRImage { */ public PamImage getImageSnapshot() { + //System.out.println("Fit height: " + mediaView.getFitHeight()); + // + // if (metaData==null || metaData.width==0 || metaData.height == 0 ) { + // return null; + // } + WritableImage image = new WritableImage(metaData.width, metaData.height); mediaView.snapshot(new SnapshotParameters(), image); diff --git a/src/videoRangePanel/layoutFX/VRTabPanelControlFX.java b/src/videoRangePanel/layoutFX/VRTabPanelControlFX.java index 80798d7c..d44c2bb6 100644 --- a/src/videoRangePanel/layoutFX/VRTabPanelControlFX.java +++ b/src/videoRangePanel/layoutFX/VRTabPanelControlFX.java @@ -86,7 +86,7 @@ public class VRTabPanelControlFX extends VRTabPane { Group root= new Group(); Scene scene = new Scene(root, Color.GRAY); scene.getStylesheets().clear(); - scene.getStylesheets().add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); + scene.getStylesheets().addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getGUICSS()); vRDisplay = new VRDisplayFX2AWT(vRControl); vRDisplay.createPane(); diff --git a/src/videoRangePanel/vrmethods/landMarkMethod/LandMarkGroupPane.java b/src/videoRangePanel/vrmethods/landMarkMethod/LandMarkGroupPane.java index fa8d90d4..932e1e2b 100644 --- a/src/videoRangePanel/vrmethods/landMarkMethod/LandMarkGroupPane.java +++ b/src/videoRangePanel/vrmethods/landMarkMethod/LandMarkGroupPane.java @@ -158,7 +158,7 @@ public class LandMarkGroupPane extends PamBorderPane { //make pop over dark ((Parent) popOver.getSkin().getNode()).getStylesheets() - .add(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); + .addAll(PamStylesManagerFX.getPamStylesManagerFX().getCurStyle().getSlidingDialogCSS()); // System.out.println(landMarkTable.getTableView().getSelectionModel().getSelectedItem()); } }); diff --git a/src/whistlesAndMoans/alarm/WMAlarmParameters.java b/src/whistlesAndMoans/alarm/WMAlarmParameters.java index 1978d507..46a480ff 100644 --- a/src/whistlesAndMoans/alarm/WMAlarmParameters.java +++ b/src/whistlesAndMoans/alarm/WMAlarmParameters.java @@ -11,10 +11,11 @@ public class WMAlarmParameters extends DataSelectParams implements Cloneable, Se public static final long serialVersionUID = 1L; - public double minFreq, maxFreq; - public double minAmplitude; - public double minLengthMillis; - public boolean superDetOnly; + public double minFreq = 0.; + public double maxFreq = 30000.; + public double minAmplitude = 90.; + public double minLengthMillis = 0.0; + public boolean superDetOnly = false; @Override public WMAlarmParameters clone() { diff --git a/src/whistlesAndMoans/dataSelector/WMDDataSelector.java b/src/whistlesAndMoans/dataSelector/WMDDataSelector.java index 7838b2d6..34ec0027 100644 --- a/src/whistlesAndMoans/dataSelector/WMDDataSelector.java +++ b/src/whistlesAndMoans/dataSelector/WMDDataSelector.java @@ -1,15 +1,9 @@ package whistlesAndMoans.dataSelector; -import java.io.Serializable; - -import alarm.AlarmParameters; import pamViewFX.fxSettingsPanes.DynamicSettingsPane; import whistlesAndMoans.ConnectedRegionDataUnit; import whistlesAndMoans.WhistleMoanControl; import whistlesAndMoans.alarm.WMAlarmParameters; -import PamController.PamControlledUnitSettings; -import PamController.PamSettingManager; -import PamController.PamSettings; import PamView.dialog.PamDialogPanel; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; @@ -24,6 +18,11 @@ public class WMDDataSelector extends DataSelector { private WMAlarmParameters wmAlarmParameters = new WMAlarmParameters(); + /** + * JavaFX pane. + */ + private WMDSelectPaneFX wmdSelectPaneFX; + public WMDDataSelector(WhistleMoanControl wmControl, PamDataBlock pamDataBlock, String selectorName, boolean allowScores) { super(pamDataBlock, selectorName, allowScores); @@ -94,8 +93,10 @@ public class WMDDataSelector extends DataSelector { @Override public DynamicSettingsPane getDialogPaneFX() { - // TODO Auto-generated method stub - return null; + if (wmdSelectPaneFX == null) { + wmdSelectPaneFX = new WMDSelectPaneFX(this); + } + return wmdSelectPaneFX; } } diff --git a/src/whistlesAndMoans/dataSelector/WMDSelectPaneFX.java b/src/whistlesAndMoans/dataSelector/WMDSelectPaneFX.java new file mode 100644 index 00000000..41818234 --- /dev/null +++ b/src/whistlesAndMoans/dataSelector/WMDSelectPaneFX.java @@ -0,0 +1,151 @@ +package whistlesAndMoans.dataSelector; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import pamViewFX.fxNodes.PamGridPane; +import pamViewFX.fxNodes.PamVBox; +import pamViewFX.fxNodes.utilityPanes.PamToggleSwitch; +import pamViewFX.fxSettingsPanes.DynamicSettingsPane; +import whistlesAndMoans.alarm.WMAlarmParameters; + +/** + * JavaFX settings pane for the whsitle and moan detector data selector. + * + * @author Jamie Macaulay + * + */ +public class WMDSelectPaneFX extends DynamicSettingsPane { + + private Pane mainPane; + + private WMDDataSelector wmdDataSelector; + + private Spinner minFreq; + + private Spinner maxFreq; + + private Spinner minAmplitude; + + private Spinner minLength; + + private PamToggleSwitch superDetOnly; + + public WMDSelectPaneFX(WMDDataSelector wmdDataSelector) { + super(wmdDataSelector); + this.wmdDataSelector = wmdDataSelector; + // TODO Auto-generated constructor stub + mainPane = createPane(); + } + + + private Pane createPane() { + + PamVBox mainPane = new PamVBox(); + mainPane.setSpacing(5.); + + PamGridPane gridPane = new PamGridPane(); + gridPane.setHgap(5.); + gridPane.setVgap(5.); + + int row = 0; + int column = 0; + + minFreq = new Spinner(0.,Double.MAX_VALUE, 100., 100.); + minFreq.setEditable(true); + + gridPane.add(new Label("Min. frequency"), column, row); + column++; + gridPane.add(minFreq, column, row); + column++; + gridPane.add(new Label("Hz"), column, row); + + maxFreq = new Spinner(0.,Double.MAX_VALUE, 30000., 100.); + maxFreq.setEditable(true); + + row++; + column=0; + gridPane.add(new Label("Max. frequency"), column, row); + gridPane.add(maxFreq, ++column, row); + gridPane.add(new Label("Hz"), ++column, row); + + minAmplitude = new Spinner(0.,1000., 90., 1.); + minAmplitude.setEditable(true); + + row++; + column=0; + gridPane.add(new Label("Min. amplitude"), column, row); + gridPane.add(minAmplitude, ++column, row); + gridPane.add(new Label("dB"), ++column, row); + + minLength = new Spinner(0.,Double.MAX_VALUE, 0., 1.); + minLength.setEditable(true); + + row++; + column=0; + gridPane.add(new Label("Min. length"), column, row); + gridPane.add(minLength, ++column, row); + gridPane.add(new Label("milliseconds"), ++column, row); + + row++; + column=0; + superDetOnly = new PamToggleSwitch("Only whistles with super-detections"); + gridPane.add(superDetOnly, column, row); + GridPane.setColumnSpan(superDetOnly, 3); + + mainPane.getChildren().add(gridPane); + + return mainPane; + } + + @Override + public Boolean getParams(Boolean currParams) { + + WMAlarmParameters wmAlarmParameters = wmdDataSelector.getWmAlarmParameters().clone(); + try { + wmAlarmParameters.minFreq = minFreq.getValue(); + wmAlarmParameters.maxFreq = maxFreq.getValue(); + wmAlarmParameters.minAmplitude = minAmplitude.getValue(); + wmAlarmParameters.minLengthMillis = minLength.getValue(); + wmAlarmParameters.superDetOnly = superDetOnly.isSelected(); + } + catch (NumberFormatException e) { + return false; + } + wmdDataSelector.setWmAlarmParameters(wmAlarmParameters); + return true; + } + + @Override + public void setParams(Boolean input) { + + WMAlarmParameters wmAlarmParameters = wmdDataSelector.getWmAlarmParameters(); + + minFreq.getValueFactory().setValue(wmAlarmParameters.minFreq); + maxFreq.getValueFactory().setValue(wmAlarmParameters.maxFreq); + minAmplitude.getValueFactory().setValue(wmAlarmParameters.minAmplitude); + minLength.getValueFactory().setValue(wmAlarmParameters.minLengthMillis); + + superDetOnly.setSelected(wmAlarmParameters.superDetOnly); + + } + + @Override + public String getName() { + return "WMD Data Selector"; + } + + @Override + public Node getContentNode() { + return mainPane; + } + + @Override + public void paneInitialized() { + // TODO Auto-generated method stub + + } + +}