diff --git a/src/PamView/wizard/PamWizardCard.java b/src/PamView/wizard/PamWizardCard.java index bc0d9e14..7d4fa88c 100644 --- a/src/PamView/wizard/PamWizardCard.java +++ b/src/PamView/wizard/PamWizardCard.java @@ -1,7 +1,5 @@ package PamView.wizard; -import java.io.Serializable; - import javax.swing.JPanel; diff --git a/src/clickDetector/ClickClassifiers/ClickBlockSpeciesManager.java b/src/clickDetector/ClickClassifiers/ClickBlockSpeciesManager.java index 89812008..bb7528bf 100644 --- a/src/clickDetector/ClickClassifiers/ClickBlockSpeciesManager.java +++ b/src/clickDetector/ClickClassifiers/ClickBlockSpeciesManager.java @@ -15,7 +15,7 @@ public class ClickBlockSpeciesManager extends DataBlockSpeciesManager + + @@ -138,6 +140,8 @@ + + @@ -198,6 +202,8 @@ + + @@ -276,8 +282,6 @@ - - @@ -290,6 +294,8 @@ + + @@ -388,6 +394,8 @@ + + @@ -456,6 +464,8 @@ + + @@ -592,6 +602,8 @@ + + diff --git a/src/help/PAMGUARDIndex.xml b/src/help/PAMGUARDIndex.xml index c9cf5489..7e8ea629 100644 --- a/src/help/PAMGUARDIndex.xml +++ b/src/help/PAMGUARDIndex.xml @@ -2,6 +2,8 @@ + + diff --git a/src/help/PAMGUARDTOC.xml b/src/help/PAMGUARDTOC.xml index 12209a10..e668ab2c 100644 --- a/src/help/PAMGUARDTOC.xml +++ b/src/help/PAMGUARDTOC.xml @@ -198,10 +198,20 @@ - + + + + + + + + + + + diff --git a/src/help/pamHelpStylesheet.css b/src/help/pamHelpStylesheet.css index d2d474b9..6cfd22db 100644 --- a/src/help/pamHelpStylesheet.css +++ b/src/help/pamHelpStylesheet.css @@ -55,6 +55,14 @@ ol { img.wrap {float: left} img.wrapright {float: right} +img.wrapcenter {float: center} + +.center { + display: block; + margin-left: auto; + margin-right: auto; + width: 85%; +} table, th, td { border: 1px solid black; @@ -65,4 +73,4 @@ th, td { } tr { text-align: center; -} \ No newline at end of file +} diff --git a/src/help/utilities/tethys/docs/calibrations.html b/src/help/utilities/tethys/docs/calibrations.html new file mode 100644 index 00000000..619d33e4 --- /dev/null +++ b/src/help/utilities/tethys/docs/calibrations.html @@ -0,0 +1,147 @@ + + + + + +Instrument Calibration Information + + + + + + +

Instrument calibration information

+ +

+ Most of the calibration data is taken from the array manager and + from the sound acquisition module. However, PAMGuard will ask a + few questions about HOW the instrument was calibrated, when it was + done and who is responsible. +

+

+ There are two dialogs associated with calibration. The first + asks for a calibration method and has the following fields: +

+ +
    +
  • + Method: Must be one of the following options: +
      +
    • Reference hydrophone
    • +
    • Manufacturer’s specification
    • +
    • Piston phone
    • +
    • Other calibrated source
    • +
    • Unknown
    • +
    +
  • + +
  • + Serial number: Hydrophone serial number +
  • + +
  • + Quality: Quality assurance value: +
      +
    • unverified: The calibration has not been verified
    • +
    • valid: The calibration has been validated as per the quality assurance process
    • +
    • invalid: The calibration was found to be invalid during quality assurance
    • +
    +
  • + +
  • + QA Comment: Textual description of the quality assurance + process. +
  • + + +
  • + Calibration method: Textual description of the Method. +
  • +
+ +

+ The second calibrations dialog asks for: +

+ +
    +
  • + Calibration date: Date the calibration was performed. +
  • + +
  • + Update frequency: Must be one of the following: +
      +
    • + as-needed: No updates are planned, but if a change is needed the calibration will be updated (defaul)t +
    • +
    • + unplanned: There are no plans to ever update the record. +
    • +
    • + yearly: A yearly review will be conducted to ensure that the record is valid. +
    • +
    +
  • + +
  • + Technical Person / Data Manager: These two types of data have + the same fields and detail who was responsible for the + calibration and who is responsible for maintaining the record + of the calibration. In many cases, this may be the same + person and copy buttons allow the fields to be duplicated. + +
      +
    • + Name: Responsible party’s naem +
    • +
    • + Organisation: Organisation to which the party reports +
    • +
    • + Position: Responsible party’s title +
    • +
    • + Email: Email contact information +
    • +
    +
  • + +
+ +

+ Fill in as much information as you can! + If the export is successful, a record will show for each + hydrophone (or sensor) in your instrument array in the + calibration information table: +

+ +
+ Panel with information about instrument calibration +
+ + +
+
+ +
+ + + + diff --git a/src/help/utilities/tethys/docs/connection.html b/src/help/utilities/tethys/docs/connection.html new file mode 100644 index 00000000..7dc8b75e --- /dev/null +++ b/src/help/utilities/tethys/docs/connection.html @@ -0,0 +1,121 @@ + + + + +Tethys Connection and Project Details + + + + +

Connection and Project Details

+ +

+ Make sure you have a Tethys Server running. The PAMGuard interface will + only work with Tethys + 3 or later. The section below specifies how to set the + address of Tethys server address as well as determine if + PAMGuard can communicate successfully with Tethys. (The top + panel will be orange if communication is not working. +

+ +

Tethys Server

+ + +

+ The Tethys Server field next to the picture of the goddess Tethys + indicates the address of the Tehtys server. To change this field, + click on the gear icon. You will be prompted to provide a computer + address (URL) and a port. The address should start with http:// + followed by the machine name or internet protocol address unless + the Tethys server has been configured to use an encrypted + connection. In this case, start the address with https://. +

+ +

+ By default, PAMGuard will use http://localhost:9779 which + assumes that your Tethys server is running on the same computer as + PAMGuard and that it expects communication on port 9779, the + default port (administrators may change this). +

+ +

+ If PAMGuard can communicate with the Tethys server, the + Connection and Project panel will be light grey. If + communication is not possible, the panel will be colored + orange. Likely causes for communication failure are: +

+
    +
  • + The server address or port is incorrect. +
  • + +
  • + Tethys has not been started on the server machine. One of the + main reasons that we see this is when the administrator has + not configured Tethys to run automatically as a service. In + that case, when a machine reboots (e.g., for automatic + operaing system updates) the server will not start + automatically. The Tethys manual explains how to configure + Tethys as a service that starts automatically when the machine + boots. +
  • + +
  • + Firewall rules do not permit traffic between the machine + executing PAMGuard and the one hosting Tethys on the selected + port. If you do not have adminstrative privileges, you will + need to contact your support team for help. +
  • +
+ +

Project and Instrument Information

+ +

+ Projects are names used by Tethys to help track work that should + be considered together, such as a series of deployments designed + to answer a specific question or funded under a specific + grant. If you do not already have a project defined in your + PAMGuard database, you can click the "New Project" button in the + "Connection and Project details" section of the Tethys + module. This will start a dialog that asks for a case-sensitive + project name and a geographic region. The geographic region is + for convenience, PAMGuard and Tethys track information by + longitude and latitude, but sometimes it is helpful to query for + information with respect to a geographic name such as Channel + + Islands National Marine Sanctuary. +

+ +

+ The array instrumentation is selected from a drop-down menu next + to the Instruments label. A dropdown menu next to the label + “Instruments” shows the list of hydrophone arrays. These are + likely to have been previously established prior to starting + analysis of your data by using the menu Settings -> Hydrophone + Array. + +

+ +

+ If you are a long-time user of PAMGuard, you will notice + additional fields are required for instrumentation: Instrument + Type and Instrument Id. The type indicates what type of + instrument is being used and may be generic such as a mooring or + array or denote a specific instrument such as a HARP, Rock + Hopper, SoundTrap, etc. The Id is a unique identifier for the + instrument such as a serial number. Note that if you are using + an older PAMGuard database, you may see a blank entry in the + instruments list as these new fields will not have been + populated. Press new/edit to access the instrument settings + from the Tethys module page. +

+ +
+
+ + +
+ + diff --git a/src/help/utilities/tethys/docs/deployments.html b/src/help/utilities/tethys/docs/deployments.html new file mode 100644 index 00000000..afde0149 --- /dev/null +++ b/src/help/utilities/tethys/docs/deployments.html @@ -0,0 +1,143 @@ + + + + + +Deployments + + + + + +

Deployments

+ +

+ Tethys uses deployment records to register information about + when instruments have been deployed as well as their + characteristics. Examples of characteristics that are recorded + include sample rate and the number of quantization bits, + description of duty cycles (if applicable), hydrophone geometry, + and enough details to be able find calibration data for specific + hydrophones. +

+ +

+ PAMGuard will examine the + PAMGuard + database + and + binary Store to determine what records should be generated for + your instrumentation. +

+ +

+ Where data were collected continuously or on a regular duty + cycle, PAMGuard will create a single deployment record. If data + were collected on a more "ad-hoc" basis, where the instrument + has been deployed multiple times or has irregular recording, + PAMGuard will generate a deployment record for each period of + recording. +

+ +

+ The figure below shows an example of ad-hoc recording periods + identified by PAMGuard: +

+
+ Panel showing recording times/deployments for this PAMGuard database +
+ +

+ Occasionally, there may be short recording periods (e.g. while + you were testing kit on deck) that you do not want to export. Use + the select checkbox to pick all of the rows that you wish to + export, or right click on the table and "Select All." +

+ +

+ When one or more deployment records are selected, the + "Export..." button will become available. Selecting the export + button will start a dialog that asks for additional information + about the deployments and then write records to Tethys. +

+

+ The first page of the dialog asks for the project and geographic + region which will be automatically populated if they have been + previously specified. In addition, the following fields are + requested: +

+
    +
  • Cruise name - Optional name of the deployment cruise
  • +
  • Site - Case-sensitive name for the deployment site, + e.g. "Tanner Banks" or a letter designation "T". This can + provide a simple way to identify multiple deployments at the + same general location. +
  • +
  • Responsible Party - A set of fields describing who was + responsible for the deployment and how they may be + contacted. +
  • +
+ +

+ The next page of the dialog asks whether you would like to + export a single deployment document or multiple deployments. + todo: add more detail here +

+ + +

+ Finally, you are prompted to provide optional textual descriptions of: +

    +
  • Objectives - What were your objectives when deploying the + instrument? Example: Determine population estimates for + critically endangered populations of vaquita (Phocoena + sinus). +
  • +
  • Abstract - A textual description of the deployment. + Example: A set of high frequency recorders were deployed across the + northern portion of the Sea of Cortez in the historical range + of the vaquita (Phocoena sinus). These recordings will + support detection and density estimation efforts. +
  • +
  • + Method - A description of the methods used. Example: + Small boat deployment of bottom moored SoundTrap recorders + with acoustic releases. +
  • +
+ + Press Finish to export the records. Once the + document(s) have been successuflly exported, the document name + associated with each recording period will be shown in the + Tethys Deployment column. +

+ + + +

+

+ + + + + diff --git a/src/help/utilities/tethys/docs/detect_localize.html b/src/help/utilities/tethys/docs/detect_localize.html new file mode 100644 index 00000000..a79be4e5 --- /dev/null +++ b/src/help/utilities/tethys/docs/detect_localize.html @@ -0,0 +1,161 @@ + + + + + +Detections/Localizations + + + + + +

Exporting Detections/Localizations (PAMGuard data blocks)

+ +

+ The bottom left panel of the Tethys module shows a list of + different types of PAMGuard data that can be exported. The data + in this list correspond to the various PAMGuard modules that have + been configured. See the + + data model viewer + help for an example of how PAMGuard might be configured. +

+ +

Species information

+ +

+ Some of these data represent detections of specific species or + phenomena that must be translated to Tethys. A context menu + (right-click on most computers) will show the option "Species + info..." that will allow you to specify the + translation of events + to species identifiers and call/sound types. If you try to export + without having done this, the species info dialog will be started + automatically prior to export. +

+ +

Selecting data blocks for import

+ +

+ In the sample data blocks below, four modules have been + configured, but only one of them has been run. Column "N Pam + Data" indicates the number of data records that have been + produced, and "PAMGuard Time" tells us when the data were + processed. "Tethys Documents" indicates how many Tethys records + have been produced, and should be 0 until the data are exported. +

+ +
+ List of results showing detections and other module processing events +
+ +

+ Select the data blocks to be exported by clicking on them. + Multiple lines can be selected by using keyboard modifiers such as + holding the shift while clicking to select all data blocks between + the last clicked block and where you click. Holding the alternate + (ALT) key will allow selection or de-selection of a single item + without affecting the selection state of other blocks. +

+ +

+ + SUGGESTION: It looks like we have to highlight these by clicking + on them. As we use select boxes for recording periods, we might + want to do the same thing here... We might want to rename + N PAM Datas to N PAM Data as data are already plural. + +

+ +

Exporting data blocks

+ +

+ Once the data blocks have been selected, press export. A series + of dialogs will guide you through the export process. The first + set of dialogs simply display a summary of information about what + will be exported. +

+ +
+ List of details describing the mechanism, parameters, and version of modules used in processing +
+ +

+ There is nothing to change in this summary. Press Next once you + have reviewed it. The second panel allows specification of your + objectives, abstract, and method. Many modules will have + pre-populated the method for you. While it is recommended to populate + the objectives and abstract, these fields are optional. +

+ +

+ Press Next to proceed to the next step of the dialog. You will be asked + what details you wish to store within the parameters that were used to + produce these data. Your must select one of the following: +

+ +
    +
  • + None - Do not report any of parameters used to produce these + detections. This option is not recommended as + it severely limits your ability to reproduce your results at a + later date or know whether or not the results of different + studies can be used together. +
  • +
  • Data selector only - not sure what this is
  • +
  • + Module only - Report the parameters that were set with this + module. Only parameters associated with the specific module + will be reported. Examples include score and duration thresholds + as well as any other type of criterion used to determine whether + or not an event is associated with a specific phenomenon or species. +
  • +
  • + Full process chain (default) - This is the most verbose option. It includes the module + parameters as well as anything else that is part of the signal processing chain that leads + to the module. As an example, a module only setting would not report the parameters that were + used to generate a spectrogram that was presented to a module for classification, but the + full process chain would record these details as well. Use this option will dramatically + increase the potential to reproduce your results, but it will generate a large amount of data + about the signal processing chain, much of which might not be useful. +
  • +
+ +

+ The final page of the dialog has an "Export data" button. Press + this to export the data. The system will begin generating the + Tethys document and the "Export data" button will be relabeled "Export complete" + once it is done. At this point, you can press "Finish" to close + the dialog. + + Would it make more sense to export when the user presses + Finish (or change the Finish button Export)? +

+ + + + + + + diff --git a/src/help/utilities/tethys/docs/images/data_blocks.png b/src/help/utilities/tethys/docs/images/data_blocks.png new file mode 100644 index 00000000..ee80ccff Binary files /dev/null and b/src/help/utilities/tethys/docs/images/data_blocks.png differ diff --git a/src/help/utilities/tethys/docs/images/species_codes.png b/src/help/utilities/tethys/docs/images/species_codes.png new file mode 100644 index 00000000..db8ce2ae Binary files /dev/null and b/src/help/utilities/tethys/docs/images/species_codes.png differ diff --git a/src/help/utilities/tethys/docs/images/species_search.png b/src/help/utilities/tethys/docs/images/species_search.png new file mode 100644 index 00000000..73b2c36f Binary files /dev/null and b/src/help/utilities/tethys/docs/images/species_search.png differ diff --git a/src/help/utilities/tethys/docs/images/stream_algo_info.png b/src/help/utilities/tethys/docs/images/stream_algo_info.png new file mode 100644 index 00000000..d9480428 Binary files /dev/null and b/src/help/utilities/tethys/docs/images/stream_algo_info.png differ diff --git a/src/help/utilities/tethys/docs/tethys_module.html b/src/help/utilities/tethys/docs/tethys_module.html new file mode 100644 index 00000000..e21ce748 --- /dev/null +++ b/src/help/utilities/tethys/docs/tethys_module.html @@ -0,0 +1,92 @@ + + + +Tethys Module Overview + + + +

Tethys Module Overview

+ +

+ It is assumed that you are familiar with PAMGuard and have some + knowledge about Tethys. + Documentation + and tutorials + are available at the Tethys + web site. +

+ +

Launch PAMGuard in viewer mode

+

+ Tethys export is only available in + PAMGuard + Viewer mode + and is used to archive project data to a centralised + database. It is NOT a replacement for the + existing PAMGuard + database. Open the PAMGuard database that you wish to use + in viewer mode. +

+ +

PAMGuard Tethys Module

+ +

+ Add a Tethys module to PAMGuard from the File / Add Modules / + Utilities menu. A new tab panel will show the Tethys interface +

+ +
+ +
+ +

+ The tab shows a number of panes for the connection to the server + and the various types of data that will be output to the + database. Some of these panes summarize information on what's in + the current PAMGuard dataset (consisting of your PAMGuard + database and binary store), others may be empty until you start + to export to Tethys. +

+ +

+ The Tethys module consists of several panels: +

+
    +
  • + Connection and Project Details - + Specifies the location of the Tethys server and metadata about + the project. +
  • +
    +
  • Data Export - There are several panes that are responsible + for exporting information about instrument deployments, their + calibrations, and what they detected/localized. + +
  • +
+ +
+
+ + +
+ + diff --git a/src/help/utilities/tethys/docs/tethys_overview.html b/src/help/utilities/tethys/docs/tethys_overview.html index 9419fe10..6e9e6515 100644 --- a/src/help/utilities/tethys/docs/tethys_overview.html +++ b/src/help/utilities/tethys/docs/tethys_overview.html @@ -21,48 +21,68 @@ img { -

Tethys Interface

-

Overview

+

Tethys Interface

+

+

Overview

-

- Tethys mosaic -

-

- - Tethys is a freely - available open source temporal-spatial database for metadata related - to acoustic recordings. The database is intended to house the metadata - from marine mammal detection and localization studies, allowing the - user to perform meta analyses or to aggregate data from many - experimental efforts based on a common attribute. This resulting - database can then be queried based on time, space, or any desired - attribute and the results can be integrated with external datasets - such as NASA's Ocean Color, lunar illumination, etc. in a consistent - manner. While Tethys is designed primarily for acoustic metadata from - marine mammals, the design is general enough to permit use in other - areas as well. - -

-

PAMGuard is compatible with Tethys 3.0 or above, released early in 2024.

-

The Tethys database is not a replacement for the existing - PAMGuard Database. - Where the PAMGuard database only contains data from a single instrument or cruise, the Tethys - database contains data from many cruises and projects and can be used to hold a summary of all data - from a lab or organisation. -

+
+ Tethys mosaic +
+ +

+ PAMGuard is compatible + with Tethys 3.0 or + later. + Tethys is a freely + available open source temporal-spatial database for metadata + related to acoustic recordings. The database is intended to house + the metadata from marine mammal detection and localization + studies, allowing the user to perform meta analyses or to + aggregate data from many experimental efforts based on a common + attribute. This resulting database can then be queried based on + time, space, or any desired attribute and the results can be + integrated with external datasets such as NASA's Ocean Color, + lunar illumination, etc. in a consistent manner. While Tethys is + designed primarily for acoustic metadata from marine mammals, the + design is general enough to permit use in other areas as well. +

+ + +

+ The Tethys database is not a replacement for the existing + PAMGuard + Database. Where the PAMGuard database only contains data from + a single instrument or cruise, the Tethys database contains data + from many cruises and projects and can be used to hold a summary + of all data from a lab or organisation. PAMGuard's Tethys module + provides an interface for exporting detailed or summary + information about acoustic detections to the Tethys database. +

-

Before using the module in PAMGuard, you should install the Tethys Server, which runs under - Windows. - Instructions for installing the Tethys Server can be found here.

- +

+ Before using the module in PAMGuard, you should install the Tethys + Server. While clients that communicate with Tethys can run on a + variety of computer operating systems, there are a small number of + dependencies on Microsoft technologies that require the server to + be installed on a Microsoft Windows machine. + Instructions + for installing the Tethys Server can be found here.

+ +

-

- - - Next: Quick Start -

-
-
-
+

+ Module help: +

+
    +
  1. The Tethys module (start here)
  2. +
  3. The Connecting to Tethys
  4. +
  5. The Instrument calibrations
  6. +
  7. The Instrument deployments
  8. +
  9. The Detections & Localizations
  10. +
  11. Guide to specifying species names
  12. +
+ + diff --git a/src/help/utilities/tethys/docs/tethys_quickstart.html b/src/help/utilities/tethys/docs/tethys_quickstart.html deleted file mode 100644 index d492f5a6..00000000 --- a/src/help/utilities/tethys/docs/tethys_quickstart.html +++ /dev/null @@ -1,80 +0,0 @@ - - - -Tethys - - - -

Tethys Quick Start

- -

This 'Quick Start' guide is aimed at people who are already familiar with both Tethys and PAMGuard

-

The Tethys database is only used in - PAMGUard Viewer mode and is only used to archive project data to a single - centralised database. - It is NOT a replacement for the existing PAMGuard database.

- -

PAMGuard Tethys Module

-

Launch PAMGuard in Viewer Mode with an existing set of data.

-

Add a Tethys module to PAMGuard from the File / Add Modules / Utilities menu.

-

A new tab panel will show the Tethys interface

-
-

The tab shows a number of panels for the connection to the server and the various types - of data that will be output to the database. Some of these should summary information on what's in the - current PAMGuard dataset (consisting of your PAMGuard database and binary store), others may be empty until you start to - export to Tethys.

- - - -

Tethys Server

-

Make sure you have a Tethys Server running. The PAMGuard interface will - only work with Tethys 3.

-

Check the server connection. If PAMGuard has correctly connected to the Tethys server, the top panel of the display will be a normal - grey colour. If the connection cannot be made, the panel will be orange.

-

If required, change the server settings using the "Select Sever" button

- - -

Project and Instrument Information

-

Either select an existing "Project", or create a new one.

-

You also need to give Tethys more information about the instrument(s) or arrays you've deployed than in previous PAMGuard versions. Again, select an - existing instrument, or set up a new one. You'll notice that this information is held with the rest of the PAMGuard array management information.

- -

Data Export

-

It's best to export data in the sequence the panels are laid out in on the PAMGuard display, i.e. Calibrations, then Deployments, and finally Detections.

- -

Calibrations

-

To export the calibration data, press the "Export..." button at the top of the "Instrument Calibration Information" panel. -

Most of the calibration data is taken from the array manager and from the sound acquisition module. However, PAMGuard will ask a few questions about - HOW the instrument was calibrated, when it was done and who is responsible. Fill in as much information as you can! - IF the export is successful, a record will show for each hydrophone (or sensor) in your instrument array in the calibration information table:

- -
- -

Deployments

-

PAMGuard will have done it's best to work out the temporal extent of your data by looking in the - PAMGuard database and - binary Store. Where data were collected on a regular duty - cycle, or continuously, there should be a single record in the table of recording periods. If data were collected on a more - "ad-hoc" basis, for instance during a boat based survey, there might be many different records in the table. - Occasionally, there may be short recording periods (e.g. while you were testing kit on deck) that you don't want to export. Either select - individual rows that you want to export, or right click on the table and "Select All".

- -

Press the "Export..." button and work through the questions to provide additional information about your data, why it was collected, etc.

-

Once the document(s) have been exported, the document names will be shown alongside each PAMGuard deployment period

-
- -

Detections

-

The bottom left panel shows a list of different types of PAMGuard data that can be exported and should also show the total numbers of each type of data that are available - within the PAMGuard storage systems. Select the datablock you want to export from and press "Export...".

-

You really don't want to try to export zillions of data to Tethys , in the options that will appear you can opt to just export certain types of detections - or summary counts.

- -
-
- - -
-
-
- - diff --git a/src/help/utilities/tethys/docs/tethys_speciescodes.html b/src/help/utilities/tethys/docs/tethys_speciescodes.html new file mode 100644 index 00000000..d3b63a50 --- /dev/null +++ b/src/help/utilities/tethys/docs/tethys_speciescodes.html @@ -0,0 +1,192 @@ + + + +Species coding + + + + +

Species and Call Type Names

+ + When exporting data from PAMGuard to Tethys, some PAMGuard records + will require additional information indicating what type of animal + or phenomena were detected. If a specific call-type was detected, + e.g. "Clicks" or "Whistles", these should be noted as well. + +

Species Names

+

+ Tethys uses the Integrated Taxonomic + Information System (ITIS) to encode species names as taxonomic + serial numbers (TSNs), unique numeric identifiers for species. + These data conform with several international coding systems which + are described on the ITIS + standards page. +

+ +

+ It is not uncommon to be unable to describe a call to the genus + level. In such cases, one can use a higher taxonomic level. For + example, beaked whale echolocation clicks are distinctive from the + clicks of other toothed whales as their pulses have a + frequency-modulated component. While they can frequently be + associated with the family Hyperodontidae, it is not always + possible to associate a click to a specific species as many of the + at least twenty-two species remain understudied. In such a case, + we would use the TSN for Hyperodontidae, 770799. While not + currently supported by PAMGuard, each species identifier has an + optional Group attribute that can be used in an ad-hoc manner to + provide additional information. This can be used to add + population markers, tentative genus groups, etc. +

+ +

+ ITIS does not describe abiotic sounds, Tethys records such sounds as follows: +

    +
  1. + For anthropogenic signals, the Tethys convention is to + use Homo sapiens, TSN 180092, for the species code + and describe the human-generated signal via a call type, + e.g. ship, mid-frequency active sonar, etc. +
  2. +
  3. + Tethys reserves the TSN -10 for geophonic signals. + The call type is used to describe the source. Examples + include ambient sound, earthquake, rain, etc. Note that negative TSNs + are not part of the ITIS standard. +
  4. +
+

+ + Note that in general, you do not have to worry about remembering + TSNs. Tethys uses TSNs internally, but will translate TSNs to/from + Latin names or user-defined abbreviations both when querying and + presenting results. + +

Call types

+ +

+ Some detectors identify specific call types. When this is the + case, users will need to specify the call type name. While + species names are standardized in Tethys, call names do not have a + well-defined standard and experts frequently use different names + for the same type of call. Consequently, Tethys does not provide a + standard coding for call types and users are free to choose the call + type names with which they feel most comfortable. +

+ +

+ That said, the authors of Tethys do however provide a list of recommend call + types for many species. These recommendations can be accessed in the + + supplemental information of the open access article "Management of acoustic metadata + for bioacoustics," Roch et al. (2016), + (DOI:10.1016/j.ecoinf.2015.12.002). +

+ +

+ Export dialog +

+ +

+ During export of records that are species-specific, a dialog will + appear that lists the types of events that were found by PAMGuard + modules. This dialog permits users to specify how the ad-hoc species + species/call encoding scheme used by PAMGuard modules can be systematically + translated to the TSNs and call types are stored in Tethys. +

+ +

+ PAMGuard events typically are a short name that represents the + species and/or potentially a call. Knowledge of the PAMGuard + modules that were run and the data on which they executed will let + a user infer what should be recorded. + + The dialog below shows a sample set of events produced by one or more PAMGuard modules + using the names: HP, DO, SON, KW, UNK, and PHP: + +

+ +
+ + The dialog above was produced from detections on data that were + recorded near the mouth of the River Tay in Scotland. Consequently, + we can infer that the harbour porpoise that is denoted by "HP" is + Phocena phocena, the only harbour porpoise endemic to + Scottish waters. +

+ + An ITIS code and call/sound type can be associated with each event. The dialog lists: +
    +
  • + Name - The ad-hoc name given by the PAMGuard module. This may not be changed. +
  • + +
  • + ITIS code -This is the ITIS TSN that is associated with PAMGuard + event code. Click on Find to inovke the TSN + search dialog whose behavior is described in the next + section. +

    + If you happen to know the TSN, you + can enter it directly. Pressing Find after typing the TNS will + populate the Latin and English vernauclar names so that you may + verify your TSN was entered correctly. +

    +
  • + +
  • Call / sound type - This will default to the code provided by + the module but should be updated to specify an appropriate call + type or left blank to indicate that the detection is not related + to a specific call type. In the case of a porpoise detector, + the likely call type would be "Clicks". + We should double check that no Call element is generated when this is blank. +
  • +
+ +

+ Once all species names and call/sound types have been identified, press the Okay button. +

+ + + + +

Searching for ITIS Taxonomic Serial Numbers (TSNs)

+ +

+ As noted above, pressing Find without typing a TSN will bring up a + search dialog: +

+ +
+ +
+ +

+ The top of this dialog has a search box where one can enter either + a Latin name or the common name for a species. The Tethys server + will search for all species that match the search-box contents. + For many species, there are common name entries in languages + other than English, and these are searched as well. +

+

+ Once you press the search button, a list will appear with all + matches for your search term. If there are too many, a scroll bar + will permit you to look through the list. Select the entry that + you wish and press OK. The TSN on the species dialog will be + populated along with the Latin name and common names from the ITIS + database. +

+ + +

+

+ + + + + diff --git a/src/tethys/TethysControl.java b/src/tethys/TethysControl.java index 446face1..83e445b7 100644 --- a/src/tethys/TethysControl.java +++ b/src/tethys/TethysControl.java @@ -491,9 +491,10 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet public ServerStatus checkServer() { ServerStatus serverState = dbxmlConnect.pingServer(); if (lastServerStatus == null || lastServerStatus.ok != serverState.ok) { + lastServerStatus = serverState; // set before sending notification! sendStateUpdate(new TethysState(StateType.UPDATESERVER)); } - lastServerStatus = serverState; +// lastServerStatus = serverState; return serverState; } @@ -699,5 +700,23 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet return calibrationHandler; } + /** + * @return the lastServerStatus + */ + public ServerStatus getLastServerStatus() { + return lastServerStatus; + } + + /** + * Quick way for any controls to see that the server is probably OK + * without actually pinging it. + * @return true if last ping of server was OK + */ + public boolean isServerOk() { + if (lastServerStatus == null) { + return false; + } + return lastServerStatus.ok; + } } diff --git a/src/tethys/calibration/CalibrationHandler.java b/src/tethys/calibration/CalibrationHandler.java index 15f3ece4..37dc186e 100644 --- a/src/tethys/calibration/CalibrationHandler.java +++ b/src/tethys/calibration/CalibrationHandler.java @@ -1,5 +1,6 @@ package tethys.calibration; +import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -45,9 +46,11 @@ import tethys.TethysTimeFuncs; import tethys.calibration.swing.CalibrationsExportWizard; import tethys.dbxml.DBXMLConnect; import tethys.dbxml.TethysException; +import tethys.niluswraps.NilusChecker; import tethys.niluswraps.NilusSettingsWrapper; import tethys.niluswraps.NilusUnpacker; import tethys.pamdata.AutoTethysProvider; +import tethys.reporter.TethysReporter; public class CalibrationHandler implements TethysStateObserver { @@ -186,6 +189,7 @@ public class CalibrationHandler implements TethysStateObserver { int nExport = 0; boolean overwrite = false; boolean exists; + TethysReporter.getTethysReporter().clear(); for (int i = 0; i < nPhone; i++) { // String docName = getHydrophoneId(i); NilusSettingsWrapper clonedWrap = wrappedSample.clone(); @@ -195,11 +199,24 @@ public class CalibrationHandler implements TethysStateObserver { calDoc.setMetadataInfo(sampleCal.getMetadataInfo()); calDoc.setProcess(sampleCal.getProcess()); calDoc.setQualityAssurance(sampleCal.getQualityAssurance()); - calDoc.setResponsibleParty(sampleCal.getResponsibleParty()); + if (NilusChecker.isEmpty(sampleCal.getResponsibleParty()) == false) { + calDoc.setResponsibleParty(sampleCal.getResponsibleParty()); + } calDoc.setTimeStamp(sampleCal.getTimeStamp()); } + // check the contact info in the metadata. + // can't so because it's required. +// MetadataInfo metaData = calDoc.getMetadataInfo(); +// if (metaData != null) { +// if (NilusChecker.isEmpty(metaData.getContact())) { +// metaData.setContact(null); +// } +// } addParameterDetails(calDoc, i); + // run some checks of completeness of the data + NilusChecker.removeEmptyFields(calDoc); +// ArrayList emptyFields = NilusChecker.checkEmptyFields(calDoc); String calDocName = createDocumentName(calDoc, i); exists = calDocumentExists(calDocName); @@ -233,6 +250,7 @@ public class CalibrationHandler implements TethysStateObserver { } } tethysControl.sendStateUpdate(new TethysState(TethysState.StateType.EXPORTRDATA, Collection.Calibrations)); + TethysReporter.getTethysReporter().showReport(true); return nExport; } @@ -405,6 +423,10 @@ public class CalibrationHandler implements TethysStateObserver { hz.add(Double.valueOf(0)); db.add(Double.valueOf(hSens+preampGain)); + if (NilusChecker.isEmpty(calibration.getResponsibleParty())) { + calibration.setResponsibleParty(null); + } + MetadataInfo metaInf = calibration.getMetadataInfo(); if (metaInf == null) { metaInf = new MetadataInfo(); @@ -417,6 +439,12 @@ public class CalibrationHandler implements TethysStateObserver { contact = new ResponsibleParty(); metaInf.setContact(contact); } + if (NilusChecker.isEmpty(metaInf.getContact())) { + metaInf.setContact(null); + } + if (NilusChecker.isEmpty(metaInf)) { + calibration.setMetadataInfo(null); + } contact.setIndividualName("Unknown"); contact.setOrganizationName("unknown"); diff --git a/src/tethys/calibration/swing/CalibrationProcessCard.java b/src/tethys/calibration/swing/CalibrationProcessCard.java index 30e37ba7..094d9d5d 100644 --- a/src/tethys/calibration/swing/CalibrationProcessCard.java +++ b/src/tethys/calibration/swing/CalibrationProcessCard.java @@ -4,6 +4,8 @@ import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; import javax.swing.JComboBox; @@ -15,6 +17,7 @@ import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.border.TitledBorder; +import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; import PamView.panel.WestAlignedPanel; import PamView.wizard.PamWizard; @@ -23,9 +26,17 @@ import nilus.AlgorithmType.Parameters; import nilus.AlgorithmType.SupportSoftware; import nilus.Calibration; import nilus.Calibration.QualityAssurance; +import nilus.Helper; import nilus.QualityValueBasic; import tethys.calibration.CalibrationHandler; +import tethys.niluswraps.NilusChecker; +/** + * Calibrations Process card attempts to fill in the + * calibration data for the Quality Assurance and Process fields. + * @author dg50 + * + */ public class CalibrationProcessCard extends CalibrationsCard { private JPanel processPanel; @@ -123,9 +134,13 @@ public class CalibrationProcessCard extends CalibrationsCard { } process.setMethod((String) calMethod.getSelectedItem()); process.setVersion(version.getText()); - process.setSoftware(software.getText()); + String soft = warnNotNull(getPamWizard(), software, "Calibration Method"); + if (soft == null) { + return false; + } + process.setSoftware(soft); if (software.getText() == null) { - getPamWizard().showWarning("You must specify the calibratin method used"); + getPamWizard().showWarning("You must specify the calibration method used"); } QualityAssurance qa = calibration.getQualityAssurance(); @@ -133,7 +148,11 @@ public class CalibrationProcessCard extends CalibrationsCard { qa = new QualityAssurance(); calibration.setQualityAssurance(qa); } - qa.setComment(qaComment.getText()); + String t = warnNotNull(getPamWizard(), qaComment, "QA Comment"); + if (t == null) { + return false; + } + qa.setComment(t); qa.setQuality(QualityValueBasic.fromValue((String) qaQuality.getSelectedItem())); // need to add a few fixed things for this to work... @@ -142,10 +161,26 @@ public class CalibrationProcessCard extends CalibrationsCard { if (params == null) { params = new Parameters(); process.setParameters(params); +// params.getAny(). } + try { + Helper.createRequiredElements(params); + } catch (IllegalArgumentException | IllegalAccessException | InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + +// if (checkEmptyFields(qa) == false) { +// return false; +// } +// if (checkEmptyFields(process) == false) { +//// return false; +// } return true; } + + @Override public void setParams(Calibration calibration) { diff --git a/src/tethys/calibration/swing/CalibrationsCard.java b/src/tethys/calibration/swing/CalibrationsCard.java index b97221d3..a59482f4 100644 --- a/src/tethys/calibration/swing/CalibrationsCard.java +++ b/src/tethys/calibration/swing/CalibrationsCard.java @@ -3,8 +3,9 @@ package tethys.calibration.swing; import PamView.wizard.PamWizard; import PamView.wizard.PamWizardCard; import nilus.Calibration; +import tethys.swing.TethysWizardCard; -abstract public class CalibrationsCard extends PamWizardCard { +abstract public class CalibrationsCard extends TethysWizardCard { public CalibrationsCard(PamWizard pamWizard, String title) { super(pamWizard, title); diff --git a/src/tethys/calibration/swing/CalibrationsContactCard.java b/src/tethys/calibration/swing/CalibrationsContactCard.java index 3adc9f33..2dc6e581 100644 --- a/src/tethys/calibration/swing/CalibrationsContactCard.java +++ b/src/tethys/calibration/swing/CalibrationsContactCard.java @@ -28,6 +28,7 @@ import nilus.MetadataInfo; import nilus.ResponsibleParty; import tethys.TethysTimeFuncs; import tethys.calibration.CalibrationHandler; +import tethys.niluswraps.NilusChecker; import tethys.swing.export.ResponsiblePartyPanel; public class CalibrationsContactCard extends CalibrationsCard { @@ -125,6 +126,11 @@ public class CalibrationsContactCard extends CalibrationsCard { } metaData.setContact(checkRPChildren(metaData.getContact())); dataManager.getParams(metaData.getContact()); + ResponsibleParty metaContact = metaData.getContact(); + NilusChecker.removeEmptyFields(metaData); + if (metaData.getContact() == null) { + return PamDialog.showWarning(getPamWizard(), "Missing data", "The Data Manager fields must be completed"); + } metaData.setUpdateFrequency((String) updateInterval.getSelectedItem()); metaData.setDate(TethysTimeFuncs.xmlGregCalFromMillis(System.currentTimeMillis())); @@ -136,6 +142,10 @@ public class CalibrationsContactCard extends CalibrationsCard { long millis = date.getTime(); cardParams.setTimeStamp(TethysTimeFuncs.xmlGregCalFromMillis(millis)); + + checkEmptyFields(rp); + checkEmptyFields(metaData); + return true; } diff --git a/src/tethys/calibration/swing/CalibrationsMainPanel.java b/src/tethys/calibration/swing/CalibrationsMainPanel.java index 385ca9ef..e13365ef 100644 --- a/src/tethys/calibration/swing/CalibrationsMainPanel.java +++ b/src/tethys/calibration/swing/CalibrationsMainPanel.java @@ -13,6 +13,7 @@ import javax.swing.border.TitledBorder; import PamView.panel.PamPanel; import tethys.TethysControl; +import tethys.TethysState; import tethys.calibration.CalibrationHandler; import tethys.swing.TethysGUIPanel; @@ -63,4 +64,14 @@ public class CalibrationsMainPanel extends TethysGUIPanel { return mainPanel; } + @Override + public void updateState(TethysState tethysState) { + super.updateState(tethysState); + enableControls(); + } + + private void enableControls() { + exportButton.setEnabled(getTethysControl().isServerOk()); + } + } diff --git a/src/tethys/dbxml/DBXMLConnect.java b/src/tethys/dbxml/DBXMLConnect.java index e13b05fd..9b12ecd5 100644 --- a/src/tethys/dbxml/DBXMLConnect.java +++ b/src/tethys/dbxml/DBXMLConnect.java @@ -22,7 +22,10 @@ import tethys.Collection; import tethys.TethysControl; import tethys.database.TethysActions; import tethys.database.TethysLogger; +import tethys.niluswraps.NilusChecker; import tethys.output.TethysExportParams; +import tethys.reporter.TethysReport; +import tethys.reporter.TethysReporter; /** * Class containing functions for managing the database connection. Opening, closing, @@ -144,6 +147,9 @@ public class DBXMLConnect { */ public boolean postAndLog(Object nilusObject, String documentName) throws TethysException { + boolean ok = NilusChecker.warnEmptyFields(tethysControl.getGuiFrame(), nilusObject); + + TethysException e = null; boolean success = false; try { @@ -207,6 +213,8 @@ public class DBXMLConnect { */ boolean error = importReturn.contains(""); + String name = tempFile.getName(); + TethysReporter.getTethysReporter().addReport(new TethysReport(success, collection, name, name)); // error = !success; might be a better options. if (error) { throw new TethysException("Error posting to Tethys", importReturn); diff --git a/src/tethys/deployment/DeploymentHandler.java b/src/tethys/deployment/DeploymentHandler.java index 85f834c3..0c435fc0 100644 --- a/src/tethys/deployment/DeploymentHandler.java +++ b/src/tethys/deployment/DeploymentHandler.java @@ -45,6 +45,9 @@ import dataMap.OfflineDataMapPoint; import generalDatabase.DBControlUnit; import metadata.MetaDataContol; import metadata.PamguardMetaData; +import nilus.AcousticDataQAType; +import nilus.AcousticDataQAType.Quality; +import nilus.AcousticDataQAType.Quality.FrequencyRange; import nilus.Audio; import nilus.ChannelInfo; import nilus.ChannelInfo.DutyCycle; @@ -84,6 +87,7 @@ import tethys.deployment.swing.RecordingGapDialog; import tethys.niluswraps.PDeployment; import tethys.output.TethysExportParams; import tethys.pamdata.AutoTethysProvider; +import tethys.reporter.TethysReporter; import tethys.swing.DeploymentTableObserver; /** @@ -387,12 +391,14 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb * @param selectedDeployments */ public void exportDeployments(ArrayList selectedDeployments) { + TethysReporter.getTethysReporter().clear(); if (deploymentExportOptions.separateDeployments) { exportSeparateDeployments(selectedDeployments); } else { exportOneDeploymnet(selectedDeployments); } + TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); } /** @@ -400,6 +406,14 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb */ private void exportOneDeploymnet(ArrayList selectedDeployments) { // do the lot, whatever ... + Float sampleRate = null; + AcquisitionControl daq = (AcquisitionControl) PamController.getInstance().findControlledUnit(AcquisitionControl.class, null); + if (daq != null) { + DaqSystem system = daq.findDaqSystem(null); + AcquisitionParameters daqParams = daq.acquisitionParameters; + sampleRate = daqParams.sampleRate; + } + selectedDeployments = getDeploymentOverview().getRecordingPeriods(); int freeId = getTethysControl().getDeploymentHandler().getFirstFreeDeploymentId(); RecordingPeriod onePeriod = new RecordingPeriod(selectedDeployments.get(0).getRecordStart(), @@ -412,11 +426,37 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb deployment.setCruise(globalMeta.getCruise()); deployment.setSite(globalMeta.getSite()); if (selectedDeployments.size() > 1) { - // now need to remove the - SamplingDetails samplingDetails = deployment.getSamplingDetails(); - samplingDetails.getChannel().clear(); - for (int i = 0; i < selectedDeployments.size(); i++) { - addSamplingDetails(deployment, selectedDeployments.get(i)); +// // now need to remove the sampling details - don't though, add invalid periods instead. +// SamplingDetails samplingDetails = deployment.getSamplingDetails(); +// samplingDetails.getChannel().clear(); +// for (int i = 0; i < selectedDeployments.size(); i++) { +// addSamplingDetails(deployment, selectedDeployments.get(i)); +// } + /* + * Instead, we're putting invalid periods into the QA section. + */ + AcousticDataQAType qa = deployment.getQualityAssurance(); + if (qa == null) { + 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(); + Quality q = new Quality(); + q.setStart(TethysTimeFuncs.xmlGregCalFromMillis(end)); + q.setEnd(TethysTimeFuncs.xmlGregCalFromMillis(start)); + q.setCategory("unusable"); + if (sampleRate != null) { + FrequencyRange f = q.getFrequencyRange(); + if (f == null) { + q.setFrequencyRange(f = new FrequencyRange()); + } + f.setLowHz(0); + f.setHighHz(sampleRate/2); + } + q.setComment("No data (probably off or out of water)"); + qualityList.add(q); } } DBXMLConnect dbxmlConnect = getTethysControl().getDbxmlConnect(); diff --git a/src/tethys/deployment/swing/DeploymentDataCard.java b/src/tethys/deployment/swing/DeploymentDataCard.java index 57c41b4d..24827107 100644 --- a/src/tethys/deployment/swing/DeploymentDataCard.java +++ b/src/tethys/deployment/swing/DeploymentDataCard.java @@ -39,11 +39,12 @@ public class DeploymentDataCard extends PamWizardCard { this.tethysControl = tethysControl; deploymentHandler = tethysControl.getDeploymentHandler(); ButtonGroup bg = new ButtonGroup(); - exportOne = new JRadioButton("Export a single detection document for all data"); - exportMany = new JRadioButton("Export separate documents for each ad-hoc recording period"); + exportOne = new JRadioButton("Export a single deployment document for all data"); + exportMany = new JRadioButton("Export separate deployment documents for each ad-hoc recording period"); bg.add(exportOne); bg.add(exportMany); + JPanel optsPanel = new JPanel(new GridBagLayout()); optsPanel.setBorder(new TitledBorder("Number of documents")); GridBagConstraints c = new PamGridBagContraints(); @@ -95,6 +96,15 @@ public class DeploymentDataCard extends PamWizardCard { } public void setParams(DeploymentExportOpts exportOptions, Deployment deployment) { + + /* + * temp code to only allow export of multiple documents. + */ +// exportOptions.separateDeployments = true; +// exportOne.setEnabled(false); +// exportOne.setToolTipText("Feature not yet enabled"); + + exportOne.setSelected(exportOptions.separateDeployments == false); exportMany.setSelected(exportOptions.separateDeployments == true); setParams(deployment); diff --git a/src/tethys/detection/DetectionsHandler.java b/src/tethys/detection/DetectionsHandler.java index b5740ea7..f797c8ee 100644 --- a/src/tethys/detection/DetectionsHandler.java +++ b/src/tethys/detection/DetectionsHandler.java @@ -43,6 +43,7 @@ import tethys.output.DatablockSynchInfo; import tethys.output.StreamExportParams; import tethys.output.TethysExportParams; import tethys.pamdata.TethysDataProvider; +import tethys.reporter.TethysReporter; import tethys.species.DataBlockSpeciesManager; import tethys.swing.export.DetectionsExportWizard; @@ -587,6 +588,7 @@ public class DetectionsHandler { this.dataBlock = dataBlock; this.exportParams = exportParams; this.exportObserver = exportObserver; + TethysReporter.getTethysReporter().clear(); } public void publish(DetectionExportProgress exportProgress) { @@ -617,6 +619,7 @@ public class DetectionsHandler { DetectionExportProgress prog = new DetectionExportProgress(null, null, 0, 0, 0, 0, DetectionExportProgress.STATE_COMPLETE); tethysControl.exportedDetections(dataBlock); exportObserver.update(prog); + TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); } @Override diff --git a/src/tethys/niluswraps/NilusChecker.java b/src/tethys/niluswraps/NilusChecker.java new file mode 100644 index 00000000..43a53e97 --- /dev/null +++ b/src/tethys/niluswraps/NilusChecker.java @@ -0,0 +1,417 @@ +package tethys.niluswraps; + +import java.awt.Window; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.renjin.methods.Methods; + +import PamView.dialog.warn.WarnOnce; +import nilus.Calibration; +import nilus.Calibration.QualityAssurance; +import nilus.Helper; +import nilus.ResponsibleParty; + +/** + * A few static checks of some nilus classes to see if it's + * worth writing them or not. + * @author dg50 + * + */ +public class NilusChecker { + + public static void main(String args[]) { + Calibration cal = new Calibration(); + try { + Helper.createRequiredElements(cal); + } catch (IllegalArgumentException | IllegalAccessException | InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + QualityAssurance qa; + cal.setQualityAssurance(qa = new QualityAssurance()); + qa.setComment("Nothing to comment on "); + + int removed = removeEmptyFields(cal); + System.out.printf("%d fields removed from object %s\n", removed, cal); + + + ArrayList missing = checkEmptyFields(cal); + for (Field field : missing) { + System.out.printf("Field %s is required but empty in %s\n", field.getName(), field.getDeclaringClass().toString()); + } + } + + public static boolean warnEmptyFields(Window owner, Object nilusObject) { + ArrayList emptyFields = findEmptyFields(nilusObject, true); + if (emptyFields == null || emptyFields.size() == 0) { + return true; + } + String msg = String.format("One or more fields in the nilus object %s are required but empty:
", nilusObject.getClass().getName()); + 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."; + String tit = "Incomplete Tethys data"; + WarnOnce.showWarning(owner, tit, msg, WarnOnce.WARNING_MESSAGE); + return false; + } + + /** + * Find empty fields + * @param nilusObject object to search + * @param onlyRequired only list required fields. + * @return list of empty, and optionally also required, fields. + */ + public static ArrayList findEmptyFields(Object nilusObject, boolean onlyRequired) { + return findEmptyFields(nilusObject, new ArrayList(), onlyRequired); + } + + private static ArrayList findEmptyFields(Object nilusObject, ArrayList found, boolean onlyRequired) { + if (nilusObject == null) { + return found; + } + int removed = 0; + + Class nilusClass = nilusObject.getClass(); + if (nilusClass.getCanonicalName().contains("java.lang")) { + return found; + } + Method[] methods = nilusClass.getDeclaredMethods(); + Field[] fields = nilusClass.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Method getter = findGetter(fields[i], methods); + if (getter == null) { +// System.out.printf("Unable to find getter for field %s in %s\n", fields[i].getName(), nilusClass.getName()); + continue; + } + boolean required = isRequired(fields[i]); +// System.out.printf("Field %30s is %s required\n", fields[i].getName(), required ? " " : "NOT"); + Object gotObj = null; + try { + gotObj = getter.invoke(nilusObject, new Object[0]); + } catch (IllegalAccessException | InvocationTargetException e) { +// System.out.printf("Unable to invoce getter %s on %s\n", getter.getName(), nilusObject); + continue; + } + boolean empty = isEmpty(gotObj); + if (empty) { + if (required || !onlyRequired) { + found.add(fields[i]); + } + } + else { + found = findEmptyFields(gotObj, found, onlyRequired); + } + } + return found; + } + + /** + * Remove empty fields from a nilus object.
+ * An empty field is a field that is null, or has a String that is empty, or + * only contains elements which are all themselves empty. i.e. an object that references + * empty objects will be considered empty. + * @param nilusObject + * @return number of empty fields removed. + */ + public static int removeEmptyFields(Object nilusObject) { + if (nilusObject == null) { + return 0; + } + int removed = 0; + + Class nilusClass = nilusObject.getClass(); + if (nilusClass.getCanonicalName().contains("java.lang")) { + return 0; + } + Method[] methods = nilusClass.getDeclaredMethods(); + Field[] fields = nilusClass.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Method getter = findGetter(fields[i], methods); + Method setter = findSetter(fields[i], methods); + if (getter == null) { +// System.out.printf("Unable to find getter for field %s in %s\n", fields[i].getName(), nilusClass.getName()); + continue; + } + if (setter == null) { +// System.out.printf("Unable to find setter for field %s in %s\n", fields[i].getName(), nilusClass.getName()); + continue; + } + boolean required = isRequired(fields[i]); +// System.out.printf("Field %30s is %s required\n", fields[i].getName(), required ? " " : "NOT"); + Object gotObj = null; + try { + gotObj = getter.invoke(nilusObject, null); + } catch (IllegalAccessException | InvocationTargetException e) { +// System.out.printf("Unable to invoce getter %s on %s\n", getter.getName(), nilusObject); + continue; + } + boolean empty = isEmpty(gotObj); + if (empty && gotObj != null && canRemove(fields[i])) { + try { +// System.out.printf("Removing empty field %s in object %s\n", fields[i].getName(), nilusObject); +// Object args = new Object[1]; + setter.invoke(nilusObject, new Object[1]); + removed++; + } catch (IllegalAccessException | InvocationTargetException e) { + System.out.printf("Unable to invoce setter %s on %s\n", getter.getName(), nilusObject); + continue; + } + } + else { + removed += removeEmptyFields(gotObj); + } + } + return removed; + } + + /** + * Fields that can be removed. + * @param field + * @return + */ + private static boolean canRemove(Field field) { + if (field == null) { + return true; + } + Class fClass = field.getType(); + if (fClass == String.class) { + return true; + } + if (List.class.isAssignableFrom(fClass)) { + return false; + } + if (fClass.isPrimitive()) { + return false; + } + String className = fClass.getCanonicalName(); + if (className.contains("nilus.")) { + return true; + } + return false; + } + + /** + * Check an object for empty and required fields. + * @param nilusObject + * @return a list of required empty fields in the nilusObjec and any objects references by that object. + */ + public static ArrayList checkEmptyFields(Object nilusObject) { + return checkEmptyFields(nilusObject, new ArrayList()); + } + + /** + * Check an object for empty and required fields. + * @param nilusObject + * @param emptyFields + * @return + */ + private static ArrayList checkEmptyFields(Object nilusObject, ArrayList emptyFields) { + if (nilusObject == null) { + return emptyFields; + } + + Class nilusClass = nilusObject.getClass(); + if (nilusClass.isPrimitive()) { + return emptyFields; + } + if (nilusClass.getCanonicalName().contains("java.lang")) { + return emptyFields; + } + Method[] methods = nilusClass.getDeclaredMethods(); + Field[] fields = nilusClass.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Method getter = findGetter(fields[i], methods); + Method setter = findSetter(fields[i], methods); + if (getter == null) { +// System.out.printf("Unable to find getter for field %s in %s\n", fields[i].getName(), nilusClass.getName()); + continue; + } + if (setter == null) { +// System.out.printf("Unable to find setter for field %s in %s\n", fields[i].getName(), nilusClass.getName()); + continue; + } + boolean required = isRequired(fields[i]); +// System.out.printf("Field %30s is %s required\n", fields[i].getName(), required ? " " : "NOT"); + Object gotObj = null; + try { + gotObj = getter.invoke(nilusObject, null); + } catch (IllegalAccessException | InvocationTargetException e) { +// System.out.printf("Unable to invoce getter %s on %s\n", getter.getName(), nilusObject); + continue; + } + boolean empty = isEmpty(gotObj); + if (empty) { + if (required) { + emptyFields.add(fields[i]); + } + } + else { + checkEmptyFields(gotObj, emptyFields); + } +// if (required == true && empty == true) { +// System.out.printf("Field %s is required but empty in %s\n", fields[i].getName(), nilusObject.toString()); +// } + } + return emptyFields; + } + + /** + * See if a field has an annotation that indicates it's required. + * @param field field + * @return required + */ + private static boolean isRequired(Field field) { + Annotation[] annots = field.getAnnotations(); + for (int a = 0; a < annots.length; a++) { +// System.out.printf("Field %s has annotation %d %s\n", fields[i].getName(), a, annots[a].toString()); + String str = annots[a].toString(); + if (str.contains("required=true")) { + return true; + } + } + return false; + } + + /** + * Find a getter for a field. This will either be get... or is... + * @param field + * @param methods list of methods to search + * @return found method or null + */ + private static Method findGetter(Field field, Method[] methods) { + String name = field.getName(); + String poss = "get"+name; + Method found = findMethod(poss, methods); + if (found != null) { + return found; + } + poss = "is" + name; + return findMethod(poss, methods); + } + + /** + * Fine a setter for a field. This will always be set... + * @param field field + * @param methods list of methods to search + * @return found method or null + */ + private static Method findSetter(Field field, Method[] methods) { + String name = field.getName(); + String poss = "set" + name; + return findMethod(poss, methods); + } + + /** + * Find a method based on it's name (case insensitive). + * @param name name of method + * @param methods list of methods to search + * @return found method or null + */ + private static Method findMethod(String name, Method[] methods) { + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equalsIgnoreCase(name)) { + return methods[i]; + } + } + return null; + } + + /** + * Test if an object is empty.
+ * An object is considered empty if any of the following criteria are met: + *
    + *
  • The object is null
  • + *
  • The object is a zero length string
  • + *
  • The object is not null, but all of it's fields satisfy this same criteria of being empty
  • + *
  • The object is a list which has no elements
  • + *
+ * Primitive types are never empty. + * @param nilusObject + * @return true if it's empty + */ + public static boolean isEmpty(Object nilusObject) { + if (nilusObject == null) { + return true; + } + if (nilusObject instanceof String) { + String str = (String) nilusObject; + return (str.length() == 0); + } + if (nilusObject instanceof List) { + return isEmptyList((List) nilusObject); + } + if (nilusObject.getClass().isPrimitive()) { + return false; + } + boolean empty = true; + // and check all getters + Class nilusClass = nilusObject.getClass(); + Method[] methods = nilusClass.getDeclaredMethods(); + // searching for getters. + int nGet = 0; + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (method.getName().startsWith("get") && method.getParameterCount() == 0) { + nGet ++; + try { + Object got = method.invoke(nilusObject, null); + if (got != null) { + if (got instanceof String) { + if (isEmptyString((String) got) == false) { + empty = false; + } + } + else if (got instanceof List) { + if (isEmptyList((List) got) == false) { + empty = false; + } + } + else if (isEmpty(got) == false) {// it's some other class type, so recurecively ask back here. + empty = false; + } + + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + return false; // better save than sorry if we don't understand. + } + } + } + if (nGet == 0) { + // there weren't any understandable getters, so assume not empty. May be some other primitive type. + empty = false; + } + + return empty; + } + + /** + * A list is considered empty if it has no elements + * @param list + * @return true if empty + */ + private static boolean isEmptyList(List list) { + if (list == null) { + return true; + } + return list.size() == 0; + } + + /** + * A String is empty if it is null or of zero length + * @param string string + * @return true if empty + */ + public static boolean isEmptyString(String string) { + if (string == null || string.length() == 0) { + return true; + } + return false; + } +} diff --git a/src/tethys/reporter/TethysReport.java b/src/tethys/reporter/TethysReport.java new file mode 100644 index 00000000..62c3164e --- /dev/null +++ b/src/tethys/reporter/TethysReport.java @@ -0,0 +1,56 @@ +package tethys.reporter; + +import tethys.Collection; + +public class TethysReport { + + private boolean success; + + private Collection collection; + + private String docName; + + private String docId; + + /** + * @param success + * @param collection + * @param docName + * @param docId + */ + public TethysReport(boolean success, Collection collection, String docName, String docId) { + this.success = success; + this.collection = collection; + this.docName = docName; + this.docId = docId; + } + + /** + * @return the success + */ + public boolean isSuccess() { + return success; + } + + /** + * @return the collection + */ + public Collection getCollection() { + return collection; + } + + /** + * @return the docName + */ + public String getDocName() { + return docName; + } + + /** + * @return the docId + */ + public String getDocId() { + return docId; + } + +} diff --git a/src/tethys/reporter/TethysReporter.java b/src/tethys/reporter/TethysReporter.java new file mode 100644 index 00000000..15228a83 --- /dev/null +++ b/src/tethys/reporter/TethysReporter.java @@ -0,0 +1,118 @@ +package tethys.reporter; + +import java.awt.Window; +import java.util.ArrayList; + +import PamController.PamController; +import PamView.dialog.warn.WarnOnce; + +/** + * Set of functions to provide mesage reports on Tethys output. This + * will work with the existing WarnOnce type pop-up, the primary purpose + * of the functions here being to collate information, possibly from + * several document writes, before issuing an overall report. + * @author dg50 + * + */ +public class TethysReporter { + + private static TethysReporter singleInstance; + + private ArrayList tethysReports; + + private TethysReporter() { + tethysReports = new ArrayList(); + } + + /** + * Get the reporter. + * @return + */ + public static final TethysReporter getTethysReporter() { + if (singleInstance == null) { + singleInstance = new TethysReporter(); + } + return singleInstance; + } + + /** + * Clear all reports + */ + synchronized public void clear() { + tethysReports.clear(); + } + + /** + * Add a report after attempting to write a document + * @param report + */ + synchronized public void addReport(TethysReport report) { + tethysReports.add(report); + } + + /** + * Get the current number of reports + * @return number of reports + */ + synchronized public int getSize() { + return tethysReports.size(); + } + + /** + * Get a summary string of all reported writes using html to separate each ont a separat eline + * @return + */ + synchronized public String getReportString() { + if (tethysReports.size() == 0) { + return "No reports"; + } + String str = ""; + for (int i = 0; i < tethysReports.size(); i++) { + TethysReport aReport = tethysReports.get(i); + String res = aReport.isSuccess() ? "Success" : "Failure"; + if (i > 0) { + str += "
"; + } + str += String.format("%s writing %s document %s to Tethys", res, aReport.getCollection().collectionName(), aReport.getDocName()); + } + + + str += ""; + return str; + } + + /** + * Get a count of failed document writes + * @return failure count + */ + public int countFails() { + int fails = 0; + for (TethysReport aReport : tethysReports) { + if (aReport.isSuccess() == false) { + fails++; + } + } + return fails; + } + + /** + * Show a report in a popup window + * @param clear clear the list of reports afterwards + */ + public void showReport(boolean clear) { + showReport(PamController.getMainFrame(), clear); + } + + /** + * Show a report on a popup window + * @param window parent frame + * @param clear clear the list of reports afterwards + */ + public void showReport(Window window, boolean clear) { + boolean probs = countFails() > 0; + WarnOnce.showNamedWarning("TethysReporter", window, "Tethys Document Writer", getReportString(), WarnOnce.WARNING_MESSAGE); + if (clear) { + clear(); + } + } +} diff --git a/src/tethys/species/DataBlockSpeciesCodes.java b/src/tethys/species/DataBlockSpeciesCodes.java index 4cb681c9..6d27bfc5 100644 --- a/src/tethys/species/DataBlockSpeciesCodes.java +++ b/src/tethys/species/DataBlockSpeciesCodes.java @@ -21,7 +21,7 @@ public class DataBlockSpeciesCodes { /** * Probably only to be used when there are no defined names, but helpful if it's set. */ - private int itisDefault = ITISTypes.UNKNOWN; + private int itisDefault = ITISTypes.OTHER; /** * A default sound type, which can be used for all 'species', but can get diff --git a/src/tethys/species/ITISFunctions.java b/src/tethys/species/ITISFunctions.java index 5e3a0709..763c99f3 100644 --- a/src/tethys/species/ITISFunctions.java +++ b/src/tethys/species/ITISFunctions.java @@ -1,14 +1,23 @@ package tethys.species; +import java.io.StringReader; +import java.util.ArrayList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; import PamController.settings.output.xml.PAMGuardXMLPreview; import PamController.settings.output.xml.PamguardXMLWriter; +import dbxml.Queries; import tethys.TethysControl; import tethys.dbxml.DBQueryResult; +import tethys.dbxml.DBXMLConnect; import tethys.dbxml.DBXMLQueries; import tethys.dbxml.TethysQueryException; @@ -81,4 +90,83 @@ public class ITISFunctions { return new TethysITISResult(itisCode, taxunit, latin, vernacular); } + + public ArrayList searchSpecies(String searchTerm) { + ArrayList items = new ArrayList(); + String xQ = "let $target := \"thespeciessearchterm\" \r\n" + + "return\r\n" + + " {\r\n" + + "\r\n" + + " for $ranks0 in collection(\"ITIS_ranks\")/ranks/rank[\r\n" + + " dbxml:contains(completename, $target) or \r\n" + + " vernacular[dbxml:contains(name,$target)]]\r\n" + + "return\r\n" + + " {\r\n" + + " $ranks0/tsn,\r\n" + + " $ranks0/completename,\r\n" + + " \r\n" + + " {string-join($ranks0/vernacular/name, \", \")}\r\n" + + " \r\n" + + " } \r\n" + + "} \r\n" + + ""; + xQ = xQ.replace("thespeciessearchterm", searchTerm); + DBXMLConnect dbXMLConnect = tethysControl.getDbxmlConnect(); + DBXMLQueries dbxmlQueries = tethysControl.getDbxmlQueries(); + Queries queries = dbXMLConnect.getTethysQueries(); + + String queryResult = null; + try { + queryResult = queries.QueryTethys(xQ); + } catch (Exception e) { + // TODO Auto-generated catch block +// e.printStackTrace(); + return items; + } + + + DocumentBuilder builder = null; + Document doc = null; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + //Create DocumentBuilder with default configuration + builder = factory.newDocumentBuilder(); + + //Parse the content to Document object + doc = builder.parse(new InputSource(new StringReader(queryResult))); + } catch (Exception e) { +// e.printStackTrace(); +// System.out.println(queryResult); + return items; + } + // print for now, then comment this out.. +// PamguardXMLWriter pamXMLWriter = PamguardXMLWriter.getXMLWriter(); +// String fDoc = pamXMLWriter.getAsString(doc, true); +// System.out.println(fDoc); + // now unpack the xml document. + NodeList els = doc.getElementsByTagName("Record"); + int n = els.getLength(); + for (int i = 0; i < n; i++) { + Node aNode = els.item(i); + if (aNode instanceof Element) { + Element anEl = (Element) aNode; + String tsn = dbxmlQueries.getElementData(anEl, "tsn"); + int nTSN = 0; + try { + nTSN = Integer.valueOf(tsn); + } + catch (NumberFormatException ex) { + System.out.println("Invalid TSN read from Tethys: " + tsn); + continue; + } + + String completeName = dbxmlQueries.getElementData(anEl, "completename"); + String vernacular = dbxmlQueries.getElementData(anEl, "vernacular"); + SpeciesMapItem mapItem = new SpeciesMapItem(nTSN, "", "", completeName, vernacular); + items.add(mapItem); + } + + } + return items; + } } diff --git a/src/tethys/species/ITISTypes.java b/src/tethys/species/ITISTypes.java index 4681cac9..771b180d 100644 --- a/src/tethys/species/ITISTypes.java +++ b/src/tethys/species/ITISTypes.java @@ -7,12 +7,12 @@ package tethys.species; */ public class ITISTypes { - public static final int UNKNOWN = 0; + public static final int OTHER = 0; public static final int ANTHROPOGENIC = 1; public static final String getName(int code) { switch (code) { - case UNKNOWN: + case OTHER: return "Unknown"; case ANTHROPOGENIC: return "Anthropogenic"; diff --git a/src/tethys/species/SpeciesTest.java b/src/tethys/species/SpeciesTest.java index 13403f8d..b96f429f 100644 --- a/src/tethys/species/SpeciesTest.java +++ b/src/tethys/species/SpeciesTest.java @@ -21,11 +21,11 @@ public class SpeciesTest { public static void main(String[] args) { SpeciesTest st = new SpeciesTest(); - st.runJson(); +// st.runJson(); // int spermWhale = 180488; // st.getCodeInfo(spermWhale); -// st.runXQuery(); + st.runXQuery(); } private void getCodeInfo(int itisCode) { @@ -86,15 +86,32 @@ public class SpeciesTest { // + " }\r\n" // + "} "; - String xQ = " {\r\n" - + " for $rank0 in collection(\"ITIS_ranks\")/rank[tsn = \"180488\"]\r\n" - + " return\r\n" - + " {\r\n" - + " $rank0/completename\r\n" - + " }\r\n" - + "} "; +// String xQ = " {\r\n" +// + " for $rank0 in collection(\"ITIS_ranks\")/rank[tsn = \"180488\"]\r\n" +// + " return\r\n" +// + " {\r\n" +// + " $rank0/completename\r\n" +// + " }\r\n" +// + "} "; + String xQ = "let $target := \"physeter\" \r\n" + + "return\r\n" + + " {\r\n" + + "\r\n" + + " for $ranks0 in collection(\"ITIS_ranks\")/ranks/rank[\r\n" + + " dbxml:contains(completename, $target) or \r\n" + + " vernacular[dbxml:contains(name,$target)]]\r\n" + + "return\r\n" + + " {\r\n" + + " $ranks0/tsn,\r\n" + + " $ranks0/completename,\r\n" + + " \r\n" + + " {string-join($ranks0/vernacular/name, \", \")}\r\n" + + " \r\n" + + " } \r\n" + + "} \r\n" + + ""; + System.out.println(xQ); - JerseyClient jerseyClient = new JerseyClient(uri); Queries queries = new Queries(jerseyClient); diff --git a/src/tethys/species/swing/DataBlockSpeciesDialog.java b/src/tethys/species/swing/DataBlockSpeciesDialog.java index adb84646..3181493f 100644 --- a/src/tethys/species/swing/DataBlockSpeciesDialog.java +++ b/src/tethys/species/swing/DataBlockSpeciesDialog.java @@ -7,12 +7,14 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.TitledBorder; import PamController.PamController; import PamView.PamGui; import PamView.dialog.PamDialog; +import PamView.panel.PamNorthPanel; import PamguardMVC.PamDataBlock; import tethys.species.SpeciesMapManager; @@ -38,7 +40,16 @@ public class DataBlockSpeciesDialog extends PamDialog { }); JPanel nPanel = new JPanel(new BorderLayout()); nPanel.setBorder(new TitledBorder("Code management")); - nPanel.add(BorderLayout.EAST, itisButton); + nPanel.add(BorderLayout.EAST, new PamNorthPanel(itisButton)); + String otherMsg = + "Specify an ITIS taxonomic serial number (coding)." + + "
Press the Find button to look up TSNs by Latin or common name. " + + "
Anthropogenic signals should be coded as Homo sapiens (180092). " + + "
Noise Measurements and geophonic sounds should be coded as " + + "\"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)); @@ -52,6 +63,7 @@ public class DataBlockSpeciesDialog extends PamDialog { mainPanel.add(BorderLayout.NORTH, nPanel); setDialogComponent(mainPanel); setResizable(true); + setHelpPoint("utilities.tethys.docs.tethys_speciescodes"); } protected void gotoITIS() { diff --git a/src/tethys/species/swing/SpeciesSearchDialog.java b/src/tethys/species/swing/SpeciesSearchDialog.java new file mode 100644 index 00000000..78d430e3 --- /dev/null +++ b/src/tethys/species/swing/SpeciesSearchDialog.java @@ -0,0 +1,282 @@ +package tethys.species.swing; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Window; +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.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import javax.swing.border.TitledBorder; +import javax.swing.table.AbstractTableModel; + +import PamUtils.worker.PamWorkDialog; +import PamUtils.worker.PamWorkProgressMessage; +import PamView.dialog.PamDialog; +import PamView.dialog.PamGridBagContraints; +import PamView.dialog.warn.WarnOnce; +import PamView.tables.SwingTableColumnWidths; +import PamView.tables.TableColumnWidthData; +import tethys.TethysControl; +import tethys.species.ITISFunctions; +import tethys.species.SpeciesMapItem; + +public class SpeciesSearchDialog extends PamDialog { + + private static final long serialVersionUID = 1L; + + private TethysControl tethysControl; + + private SpeciesMapItem selectedItem; + + private static SpeciesSearchDialog singleInstance; + + private JTextField searchText; + + private JButton searchButton; + + private JTable resultTable; + + private ArrayList speciesMapItems; + + private DataModel tableModel; + + private volatile PamWorkDialog workDialog; + + private Object synch = new Object(); + + + private SpeciesSearchDialog(Window parentFrame, TethysControl tethysControl) { + super(parentFrame, "Species search", false); + this.tethysControl = tethysControl; + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setBorder(new TitledBorder("Search Term")); + JPanel topPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new PamGridBagContraints(); + topPanel.add(new JLabel("Latin or common name ", JLabel.RIGHT), c); + c.gridx++; + topPanel.add(searchText = new JTextField(12), c); + c.gridx++; + topPanel.add(searchButton = new JButton("search"), c); + mainPanel.add(BorderLayout.NORTH, topPanel); + searchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + searchTethys(); + } + + }); + searchText.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + searchTethys(); + } + }); + + tableModel = new DataModel(); + resultTable = new JTable(tableModel); + JPanel centPanel = new JPanel(new BorderLayout()); + centPanel.add(BorderLayout.NORTH, new JLabel("Possible matches (select one)", JLabel.LEFT)); + JScrollPane scrollPane = new JScrollPane(resultTable); + centPanel.add(BorderLayout.CENTER, scrollPane); + mainPanel.add(BorderLayout.CENTER, centPanel); + + resultTable.addMouseListener(new TableMouse()); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + new SwingTableColumnWidths("Species Search Dialog Table", resultTable); + } + }); + + setResizable(true); + setDialogComponent(mainPanel); + } + public static SpeciesMapItem showDialog(Window parentFrame, TethysControl tethysControl) { + if (singleInstance == null) { + singleInstance = new SpeciesSearchDialog(parentFrame, tethysControl); + } + singleInstance.setParams(); + singleInstance.setVisible(true); + return singleInstance.selectedItem; + } + + + private void searchTethys() { + clearResults(); + String str = searchText.getText(); + if (str == null || str.length() == 0) { + return; + } + SearchWorker searchWorker = new SearchWorker(str); + searchWorker.execute(); + // then open the dialog to block this thread. + synchronized (synch) { + workDialog = new PamWorkDialog(getOwner(), 1, "Searching Tethys Database"); + workDialog.setVisible(true); + } + } + + public void setMapItems(ArrayList newMapItems) { + this.speciesMapItems = newMapItems; + tableModel.fireTableDataChanged(); + } + + private class SearchWorker extends SwingWorker { + + private String searchString; + private ArrayList newMapItems; + + public SearchWorker(String searchString) { + this.searchString = searchString; + } + + @Override + protected Integer doInBackground() throws Exception { + String msg = String.format("Searching database for names containing \"%s\"", searchString); + PamWorkProgressMessage pm = new PamWorkProgressMessage(null, msg); + publish(pm); + try { + ITISFunctions itisFunctions = tethysControl.getItisFunctions(); + this.newMapItems = itisFunctions.searchSpecies(searchString); + } + catch (Exception e) { + e.printStackTrace(); + } + if (newMapItems == null) { + return 0; + } + if (workDialog != null) { + workDialog.setVisible(false); + workDialog.dispose(); + } + return newMapItems.size(); + } + + @Override + protected void done() { + if (newMapItems == null || newMapItems.size() == 0) { + String msg = String.format("No matching ITIS types for search term %s", searchString); + WarnOnce.showNamedWarning("ITIS Lookup failure", getOwner(), "ITIS Code search", msg, WarnOnce.WARNING_MESSAGE); + + } + setMapItems(newMapItems); + } + + @Override + protected void process(List chunks) { + for (PamWorkProgressMessage msg : chunks) { + synchronized (synch) { + if (workDialog != null) { + workDialog.update(msg); + } + } + } + } + + } + + private void setParams() { + searchText.setText(null); + clearResults(); + } + + private void clearResults() { + speciesMapItems = null; + selectedItem = null; + } + @Override + public boolean getParams() { + if (selectedItem == null) { + return showWarning("You must select a row from the table of species"); + } + return selectedItem != null; + } + + @Override + public void cancelButtonPressed() { + clearResults(); + } + + @Override + public void restoreDefaultSettings() { + // TODO Auto-generated method stub + + } + + private class TableMouse extends MouseAdapter { + + @Override + public void mouseClicked(MouseEvent e) { + if (speciesMapItems == null) { + return; + } + int selectedRow = resultTable.getSelectedRow(); + if (selectedRow >= 0 && selectedRow < speciesMapItems.size()) { + selectedItem = speciesMapItems.get(selectedRow); + } + tableModel.fireTableDataChanged(); + } + + } + private class DataModel extends AbstractTableModel { + + private String[] colNames = {"Select", "TSN", "Name", "Common Name"}; + + @Override + public int getRowCount() { + if (speciesMapItems == null) { + return 0; + } + return speciesMapItems.size(); + } + + @Override + public int getColumnCount() { + return colNames.length; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + SpeciesMapItem mapItem = speciesMapItems.get(rowIndex); + switch (columnIndex) { + case 0: + return mapItem == selectedItem; + case 1: + return mapItem.getItisCode(); + case 2: + return mapItem.getLatinName(); + case 3: + return mapItem.getCommonName(); + } + return null; + } + + @Override + public String getColumnName(int column) { + return colNames[column]; + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == 0) { + return Boolean.class; + } + return super.getColumnClass(columnIndex); + } + + } + +} diff --git a/src/tethys/species/swing/SpeciesSubPanel.java b/src/tethys/species/swing/SpeciesSubPanel.java index 53495f2a..f55919f4 100644 --- a/src/tethys/species/swing/SpeciesSubPanel.java +++ b/src/tethys/species/swing/SpeciesSubPanel.java @@ -85,7 +85,8 @@ public class SpeciesSubPanel { pamguardName.setToolTipText("Internal name within PAMGuard module"); itisCode.setToolTipText("ITIS species code"); - searchButton.setToolTipText("Search for species code"); + searchButton.setToolTipText("Either enter a code manually and press \"Find\" for additional information,
" + + "or leave the code empty and press \"Find\" to search the Tethys database using common or scientific names." ); callType.setToolTipText("Descriptive name for call type or measurement"); latinName.setToolTipText("Scientific name"); commonName.setToolTipText("Common name"); @@ -112,6 +113,17 @@ public class SpeciesSubPanel { return; } ITISFunctions itisFunctions = tethysControl.getItisFunctions(); + String itisString = this.itisCode.getText(); + if (itisString == null || itisString.length() == 0) { + searchForCode(tethysControl, itisFunctions); + } + else { + getCodeInformation(tethysControl, itisFunctions, itisString); + } +// System.out.println(itisInfo); + } + + private void getCodeInformation(TethysControl tethysControl, ITISFunctions itisFunctions, String itisString) { int itisCode = 0; try { itisCode = Integer.valueOf(this.itisCode.getText()); @@ -128,8 +140,16 @@ public class SpeciesSubPanel { if (itisInfo.getVernacular() != null) { commonName.setText(itisInfo.getVernacular()); } + } + } + + private void searchForCode(TethysControl tethysControl, ITISFunctions itisFunctions) { + SpeciesMapItem speciesItem = SpeciesSearchDialog.showDialog(tethysControl.getGuiFrame(), tethysControl); + if (speciesItem != null) { + itisCode.setText(String.format("%d", speciesItem.getItisCode())); + latinName.setText(speciesItem.getLatinName()); + commonName.setText(speciesItem.getCommonName()); } -// System.out.println(itisInfo); } public JComponent getDialogComponent() { diff --git a/src/tethys/swing/DatablockSynchPanel.java b/src/tethys/swing/DatablockSynchPanel.java index f7d1df4f..e23e308f 100644 --- a/src/tethys/swing/DatablockSynchPanel.java +++ b/src/tethys/swing/DatablockSynchPanel.java @@ -147,7 +147,7 @@ public class DatablockSynchPanel extends TethysGUIPanel { if (deployments == null || deployments.size() == 0) { en = false; } - exportButton.setEnabled(en); + exportButton.setEnabled(getTethysControl().isServerOk() & en); } public void showPopup(MouseEvent e, int row) { @@ -181,6 +181,9 @@ public class DatablockSynchPanel extends TethysGUIPanel { // dataBlockSynchInfo = null; // getSychInfos(); // getTethysControl().coun + break; + case UPDATESERVER: + enableExportButton(); } synchTableModel.fireTableDataChanged(); diff --git a/src/tethys/swing/DeploymentExportPanel.java b/src/tethys/swing/DeploymentExportPanel.java index 21e42daf..4147cb38 100644 --- a/src/tethys/swing/DeploymentExportPanel.java +++ b/src/tethys/swing/DeploymentExportPanel.java @@ -154,6 +154,9 @@ public class DeploymentExportPanel extends TethysGUIPanel implements DeploymentT case UPDATEMETADATA: setInternal(); break; + case UPDATESERVER: + enableControls(); + break; } } @@ -244,7 +247,7 @@ public class DeploymentExportPanel extends TethysGUIPanel implements DeploymentT private void enableControls() { boolean enable = selectedDeployments != null && selectedDeployments.size() > 0; - bigExportButton.setEnabled(enable); + bigExportButton.setEnabled(getTethysControl().isServerOk() & enable); } } diff --git a/src/tethys/swing/DeploymentsPanel.java b/src/tethys/swing/DeploymentsPanel.java index 37d35a38..7ebb6dec 100644 --- a/src/tethys/swing/DeploymentsPanel.java +++ b/src/tethys/swing/DeploymentsPanel.java @@ -15,6 +15,7 @@ import javax.swing.border.TitledBorder; import PamView.panel.PamPanel; import tethys.TethysControl; +import tethys.TethysState; import tethys.deployment.DeploymentHandler; import tethys.deployment.RecordingPeriod; @@ -96,15 +97,23 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO public void selectionChanged() { enableExportButton(); } + + private void enableExportButton() { ArrayList selected = pamDeploymentsTable.getSelectedPeriods(); - // and see if any warnings are needed: basically if anything selected has an output. + if (selected == null) { + exportButton.setEnabled(false); + return; + } boolean existing = false; - for (RecordingPeriod aPeriod: selected) { - if (aPeriod.getMatchedTethysDeployment() != null) { - existing = true; - break; + if (selected != null) { + // and see if any warnings are needed: basically if anything selected has an output. + for (RecordingPeriod aPeriod: selected) { + if (aPeriod.getMatchedTethysDeployment() != null) { + existing = true; + break; + } } } String warning = null; @@ -113,7 +122,13 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO exportWarning.setText(warning); } - exportButton.setEnabled(selected.size()>0 & existing == false); + exportButton.setEnabled(selected.size()>0 & existing == false && getTethysControl().isServerOk()); + } + + @Override + public void updateState(TethysState tethysState) { + super.updateState(tethysState); + enableExportButton(); } diff --git a/src/tethys/swing/PAMGuardDeploymentsTable.java b/src/tethys/swing/PAMGuardDeploymentsTable.java index 6d7209b5..1b870921 100644 --- a/src/tethys/swing/PAMGuardDeploymentsTable.java +++ b/src/tethys/swing/PAMGuardDeploymentsTable.java @@ -326,6 +326,9 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { * @return */ public ArrayList getSelectedPeriods() { + if (deploymentOverview == null) { + return null; + } ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); ArrayList selPeriods = new ArrayList(); int n = allPeriods.size(); diff --git a/src/tethys/swing/TethysWizardCard.java b/src/tethys/swing/TethysWizardCard.java new file mode 100644 index 00000000..8e85576f --- /dev/null +++ b/src/tethys/swing/TethysWizardCard.java @@ -0,0 +1,80 @@ +package tethys.swing; + +import java.awt.Window; +import java.lang.reflect.Field; +import java.util.ArrayList; + +import javax.swing.JTextField; +import javax.swing.text.JTextComponent; + +import PamView.dialog.PamDialog; +import PamView.dialog.warn.WarnOnce; +import PamView.wizard.PamWizard; +import PamView.wizard.PamWizardCard; +import tethys.niluswraps.NilusChecker; + +/** + * Wizard card with a few extra functions for Tethys. + * @author dg50 + * + * @param + */ +abstract public class TethysWizardCard extends PamWizardCard { + + private static final long serialVersionUID = 1L; + + public TethysWizardCard(PamWizard pamWizard, String title) { + super(pamWizard, title); + } + + /** + * Get a string out of a text component, but if the field is + * empty, return null rather than an empty, 0 length, string. + * @param textField + * @return string, or null if it's empty. + */ + public String getText(JTextComponent textField) { + String str = textField.getText(); + if (str == null) { + return null; + } + if (str.length() == 0) { + return null; + } + return str; + } + + /** + * Get a string from a text component, and warn if it is empty or null. + * @param owner parent window for warning + * @param textComponent text component + * @param name control name for warning text + * @return String if there was one, or null if it was null or empty. + */ + public String warnNotNull(Window owner, JTextComponent textComponent, String name) { + String str = getText(textComponent); + if (str != null) { + return str; + } + String warn = String.format("The field %s cannot be empty", name); + PamDialog.showWarning(owner, "Empty or missing data", warn); + return null; + } + + /** + * Check for required empty fields. + * @param nilusObject + * @return true if all OK, false if there are required fields + */ + public boolean checkEmptyFields(Object nilusObject) { + NilusChecker.removeEmptyFields(nilusObject); + ArrayList emptyList = NilusChecker.checkEmptyFields(nilusObject); + if (emptyList.size() == 0) { + return true; + } + Field first = emptyList.get(0); + String msg = String.format("The field \"%s\" in \"%s\" is required by Tethys and must be completed", first.getName(), first.getDeclaringClass().getCanonicalName()); + return PamDialog.showWarning(getPamWizard(), "Missing required data", msg); + } + +}