PAMGuard/src/Acquisition/FolderInputSystem.java
Jamie Mac 211ca7ab91
MacOS version of PAMGuard and minor UI updates (#170)
* Update exporter_help.md

Updated help for exporter

* Update .gitignore

* Bug fixes for deep learning classifier.

Updated the symbol options to make sure opacity is passed through the symbol chooser.

* Updates to the data map FX display

* Updates to DelphinID and Data ModelFX

Used a new writable image for much faster drawing in FX

* bb

* Delete C:\Users\Jamie Macaulay\MATLAB Drive\MATLAB\PAMGUARD\deep_learning\delphinID\whistle_image_example_java.mat

* Updates to DelphinID

* Updates to ReadMe and JavaFX GUI

* Update readme.md

Updates to ReadMe

* Updates to CPOD module and also delphinID classifier

* Fix colour scaling in FX data map

* Create cpod_help.md

* Update cpod_help.md

* Update cpod_help.md

* Updates to CPOD module

Added ability to export CPOD clicks
Added some extra features to data selector
Swing GUI for data selector (in progress)

* Updates to data map FX GUI

* Updates to CPOD module

* Added help files resources for CPOD

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Update cpod_help.md

* Update cpod_help.md

* Bug fixes for deep learning classifier.

Updated the symbol options to make sure opacity is passed through the symbol chooser.

* Updates to the data map FX display

* Updates to DelphinID and Data ModelFX

Used a new writable image for much faster drawing in FX

* Updates to ReadMe and JavaFX GUI

* bb

* Delete C:\Users\Jamie Macaulay\MATLAB Drive\MATLAB\PAMGUARD\deep_learning\delphinID\whistle_image_example_java.mat

* Updates to DelphinID

* Update readme.md

Updates to ReadMe

* Fix colour scaling in FX data map

* Updates to CPOD module and also delphinID classifier

* Updates to CPOD module

Added ability to export CPOD clicks
Added some extra features to data selector
Swing GUI for data selector (in progress)

* Create cpod_help.md

* Update cpod_help.md

* Update cpod_help.md

* Updates to data map FX GUI

* Updates to CPOD module

* Added help files resources for CPOD

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Add CPOD resources for help

* Update cpod_help.md

* Update cpod_help.md

* Bug fix to BT display

The BT display was not selecting clicks properly.

* Import bug fix

* Updates to data map FX GUI

* Updates to datamap FX

* Got date axis working properly.

* Added some arrows to the scroll bar for data map FX pane.

* Working on getting datagrams in FX saving

* Updates to DataMapFX and exporting annotations

* Update MLAnnotationsManager.java

* Working on adding annotations to exporter

* Bug fix for processing files and annotations for exporter

Rebase with main
Bug fix for processing files - stops only the last file processing when "Start normally" selected on data processing
R and MATLAB export of data annotation added.

* Add a ttoltip to the exporter

* Minor text change

* X3 fix and building for MACOS

* Update PAMGuard to build installer for MacOS

* Some fixes to Sound Acquisition dialog layout.

* Squashed commit of the following:

commit 1acddb4cc6
Author: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com>
Date:   Tue Oct 8 15:03:32 2024 +0100

    MErge from DG (#168)

    * Localization output

    * update localiser output

    * Start effort management system

    * Start of Effort plotting

    Strat of effort plotting on map. Framework for using Effort data in other areas (such as Tethys output).

    * Logger forms update

    Effort and Symbol selectors working with Logger forms. Also functions to add, edit and delete form rows in Viewer mode.

    * Update LoggerFormGraphics.java

    add in correct symbol managemet to forms graphics.

    * Effort lines on map

    Sort of working OK in real time mode.

    * Working effort system

    Currently only for map, but seems to work OK

    * Update Tethys to latest nilus schema

    * Raven importer

    Start of a system for a raven importer. Not quite working yet.

    * Raven import

    Basic functionality working. Not nice to use though.

    * Tethys Localization work

    Abstracting out writing of localization objects and document header information so that individual localisers can give fine scale control of this stuff.

    * updated Nilus

    A few updates around track and target motion measures.

    * FX Plot for raven data

    Also sorted out symbols a bit and improved symbol selector in Generic plots.

    * Update spectrogram mark bearing display

    Remove the 90-angle bit

    * Raven extra columns

    Logging of data from additional Raven table columns

    * Start effort management system

    * Start of Effort plotting

    Strat of effort plotting on map. Framework for using Effort data in other areas (such as Tethys output).

    * Logger forms update

    Effort and Symbol selectors working with Logger forms. Also functions to add, edit and delete form rows in Viewer mode.

    * Effort lines on map

    Sort of working OK in real time mode.

    * Update Tethys to latest nilus schema

    * Tethys Localization work

    Abstracting out writing of localization objects and document header information so that individual localisers can give fine scale control of this stuff.

    * Ishmael Tethys output

    Added necessaries for Tethys output from Ishmael detectors. Also found a pretty major bug in the spectrogram correlation detector, where for each block of data it was only testing the first sample of each block, not all samples, for being over threshold.

    * Speed and algorithm improvements to Ish matched filter

    Seems to be errors in correlation, didn't support multiple channels and
    also used very old and slow FFT, so working to fix all three issues.

    * Updated matched filter

    Updated and working Matched filter, though still some thinking to do about how the scaling of this works, since currently scaled by the template, so whole thing is dependent on the input. Need to think of a better way to do this.

    * Update match filt normalisation

    Normalisation now correctly using both the template and the signal for normalisation so that it will be data amplitude independent.

    * invFFT improvements

    Use faster inverse FFT of real data in correlation / delay functions.

    * Improve ifft's in other modules to improve TDOA speeds

    * Sorting mess of spec plugin graphics

    Have got the Ishmael ones scrolling, but when scrolling, there is an offset in the data due to the lag of the correlation functions. Quite hard to fix with available timing data

    * Improve ish spectrogram plugin

    Sorted scaling and scrollling problems.

    * Improve startup checks

    Rethread startup checks so that a progress bar shows when PAMGuard is checking input and output files at start up. Also include single file processing in checks.

    * Apply all spectrogram overlays

    Apply to all function on spectrogram overlays so changes to overlays affect all panels.

commit f2f9870b6f
Author: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com>
Date:   Wed Oct 2 15:13:12 2024 +0100

    V2.02.13c

    Release for installation on teaching lab machines.

commit 7533a7cfbe
Author: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com>
Date:   Tue Oct 1 15:00:51 2024 +0100

    Tethys output of Group3DLocalisations

    Output of Group3D localiser data. Localisations only, no Detections output for this one

commit ef494c0d0e
Author: Jamie Mac <macster110@gmail.com>
Date:   Mon Sep 30 13:10:59 2024 +0100

    Sud bug fix and updates to CPOD module.  (#162)

    * Update exporter_help.md

    Updated help for exporter

    * Update .gitignore

    * Bug fixes for deep learning classifier.

    Updated the symbol options to make sure opacity is passed through the symbol chooser.

    * Updates to the data map FX display

    * Updates to DelphinID and Data ModelFX

    Used a new writable image for much faster drawing in FX

    * bb

    * Delete C:\Users\Jamie Macaulay\MATLAB Drive\MATLAB\PAMGUARD\deep_learning\delphinID\whistle_image_example_java.mat

    * Updates to DelphinID

    * Updates to ReadMe and JavaFX GUI

    * Update readme.md

    Updates to ReadMe

    * Updates to CPOD module and also delphinID classifier

    * Fix colour scaling in FX data map

    * Create cpod_help.md

    * Update cpod_help.md

    * Update cpod_help.md

    * Updates to CPOD module

    Added ability to export CPOD clicks
    Added some extra features to data selector
    Swing GUI for data selector (in progress)

    * Updates to data map FX GUI

    * Updates to CPOD module

    * Added help files resources for CPOD

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Update cpod_help.md

    * Update cpod_help.md

    * Bug fixes for deep learning classifier.

    Updated the symbol options to make sure opacity is passed through the symbol chooser.

    * Updates to the data map FX display

    * Updates to DelphinID and Data ModelFX

    Used a new writable image for much faster drawing in FX

    * Updates to ReadMe and JavaFX GUI

    * bb

    * Delete C:\Users\Jamie Macaulay\MATLAB Drive\MATLAB\PAMGUARD\deep_learning\delphinID\whistle_image_example_java.mat

    * Updates to DelphinID

    * Update readme.md

    Updates to ReadMe

    * Fix colour scaling in FX data map

    * Updates to CPOD module and also delphinID classifier

    * Updates to CPOD module

    Added ability to export CPOD clicks
    Added some extra features to data selector
    Swing GUI for data selector (in progress)

    * Create cpod_help.md

    * Update cpod_help.md

    * Update cpod_help.md

    * Updates to data map FX GUI

    * Updates to CPOD module

    * Added help files resources for CPOD

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Add CPOD resources for help

    * Update cpod_help.md

    * Update cpod_help.md

    * Bug fix to BT display

    The BT display was not selecting clicks properly.

    * Import bug fix

    * Updates to data map FX GUI

    * Updates to datamap FX

    * Got date axis working properly.

    * Added some arrows to the scroll bar for data map FX pane.

    * Working on getting datagrams in FX saving

    * Updates to DataMapFX and exporting annotations

    * Update MLAnnotationsManager.java

    * Working on adding annotations to exporter

    * Bug fix for processing files and annotations for exporter

    Rebase with main
    Bug fix for processing files - stops only the last file processing when "Start normally" selected on data processing
    R and MATLAB export of data annotation added.

    * Add a ttoltip to the exporter

    * Minor text change

* Small UI improvements for Sound Acquisition and MacOS

* Bug fix to exporter

* Update to help points

Updated help points for following modules
Deep learning
CPOD
Matched click classifier
Exporter

---------

Co-authored-by: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com>
2024-10-09 12:39:26 +01:00

1053 lines
32 KiB
Java

package Acquisition;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.border.TitledBorder;
import Acquisition.filedate.FileDateDialogStrip;
import Acquisition.layoutFX.AcquisitionPaneFX;
import Acquisition.layoutFX.DAQSettingsPane;
import Acquisition.layoutFX.FolderInputPane;
import Acquisition.pamAudio.PamAudioFileFilter;
import Acquisition.pamAudio.PamAudioFileLoader;
import Acquisition.pamAudio.PamAudioFileManager;
import PamController.DataInputStore;
import PamController.InputStoreInfo;
import PamController.PamControlledUnitSettings;
import PamController.PamController;
import PamController.PamSettings;
import PamUtils.PamCalendar;
import PamUtils.PamFileChooser;
import PamUtils.PamFileFilter;
import PamUtils.worker.PamWorker;
import PamUtils.worker.filelist.FileListData;
import PamUtils.worker.filelist.WavFileType;
import PamUtils.worker.filelist.WavListUser;
import PamUtils.worker.filelist.WavListWorker;
import PamView.dialog.PamGridBagContraints;
import PamView.dialog.PamLabel;
import PamView.panel.PamPanel;
import PamView.panel.PamProgressBar;
import PamguardMVC.debug.Debug;
import javafx.application.Platform;
import pamguard.GlobalArguments;
/**
* Read multiple files in sequence. Options exist to either pause and
* restart analysis after each file, or to merge files into one long
* continuous sound stream.
*
* @author Doug Gillespie
*
*/
public class FolderInputSystem extends FileInputSystem implements PamSettings, DataInputStore {
// Timer timer;
public static final String daqType = "File Folder Acquisition System";
public static final String sysType = "Audio file folder or multiple files";
private boolean running = false;
protected ArrayList<WavFileType> allFiles = new ArrayList<>();
protected int currentFile;
private PamFileFilter audioFileFilter = getFolderFileFilter();
private Timer newFileTimer;
private JCheckBox subFolders, mergeFiles;
private JButton checkFiles;
protected long eta = -1;
private FolderInputParameters folderInputParameters;
public static final String GlobalWavFolderArg = "-wavfilefolder";
/**
* Text field for skipping initial few seconds of a file.
*/
private JTextField skipSecondsField;
/**
* Panel which shows bespoke settings for certain audio loaders. Contains nothing
* if the audio loader has no settings or no file is selected.
*/
protected PamPanel audioLoaderHolder;
@Override
public boolean runFileAnalysis() {
currentFileStart = System.currentTimeMillis();
return super.runFileAnalysis();
}
@Override
public boolean prepareInputFile() {
boolean ans = super.prepareInputFile();
if (!ans && ++currentFile < allFiles.size()) {
System.out.println("Failed to open sound file. Try again with file " + allFiles.get(currentFile).getName());
/*
* jumping striaght to the next file messes it up if it thinks the files
* are continuous, so we HAVE to stop and restart.
*/
// return prepareInputFile();
PamController.getInstance().pamStop();
PamController.getInstance().startLater(false);
}
return ans;
}
long currentFileStart;
public FolderInputSystem(AcquisitionControl acquisitionControl) {
super(acquisitionControl);
if (folderInputParameters == null)
setFolderInputParameters(new FolderInputParameters(getSystemType()));
// PamSettingManager.getInstance().registerSettings(this); //calling super already registers this in the FileInputSystem constructor
// checkComandLine();
makeSelFileList();
newFileTimer = new Timer(1000, new RestartTimer());
newFileTimer.setRepeats(false);
// timer = new Timer(1000, new TimerAction());
}
/**
* Check to see if acquisition source folder was set in the command line.
*/
private String[] checkComandLineFolder() {
String globalFolder = GlobalArguments.getParam(GlobalWavFolderArg);
Debug.out.println("Checking -wavfilefolder option: is " + globalFolder);
if (globalFolder == null) {
return null;
}
// see if it at least exists, though will we want to do this for Network folders ?
File aFile = new File(globalFolder);
if (!aFile.exists()) {
System.err.printf("Command line wav folder \"%s\" does not exist", globalFolder);
// return null;
}
String[] selList = {globalFolder};
// folderInputParameters.setSelectedFiles(selList);
// need to immediately make the allfiles list since it's about to get used by the reprocess manager
// need to worry about how to wait for this since it's starting in a different thread.
//makeSelFileList();
return selList;
}
/**
* Restarts after a file has ended when processing multiple files.
* 27 Jan 2011 - this now reschedules in the AWT thread
* @author Doug Gillespie
*
*/
class RestartTimer implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// System.out.println("Restart later time action");
newFileTimer.stop();
PamController.getInstance().startLater(false); //don't save settings on restarts
}
}
@Override
protected JPanel createDaqDialogPanel() {
JPanel p = new JPanel();
p.setBorder(new TitledBorder("Select sound file folder or multiple files"));
GridBagLayout layout = new GridBagLayout();
layout.columnWidths = new int[]{100, 100, 10};
p.setLayout(layout);
GridBagConstraints constraints = new PamGridBagContraints();
constraints.insets = new Insets(2,2,2,2);
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 3;
constraints.fill = GridBagConstraints.HORIZONTAL;
addComponent(p, fileNameCombo = new JComboBox(), constraints);
fileNameCombo.addActionListener(this);
fileNameCombo.setMinimumSize(new Dimension(30,2));
fileNameCombo.addActionListener(new FileComboListener());
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 2;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.WEST;
addComponent(p, subFolders = new JCheckBox("Include sub folders"), constraints);
constraints.gridx = 2;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.EAST;
addComponent(p, fileSelect = new JButton("Select Folder or Files"), constraints);
fileSelect.addActionListener(new FindAudioFolder());
repeat = new JCheckBox("Repeat: At end of file list, start again");
constraints.gridy++;
constraints.gridx = 0;
constraints.gridwidth = 3;
constraints.anchor = GridBagConstraints.WEST;
addComponent(p, repeat, constraints);
constraints.gridy++;
constraints.gridx = 0;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.WEST;
// constraints.gridy++;
constraints.gridx = 0;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridwidth = 3;
fileDateStrip = new FileDateDialogStrip(acquisitionControl.getFileDate(), acquisitionControl.getGuiFrame());
p.add(fileDateStrip.getDialogComponent(), constraints);
fileDateStrip.addObserver(this);
// addComponent(p, new JLabel("File date :"), constraints);
// constraints.gridx++;
// constraints.gridwidth = 2;
// constraints.fill = GridBagConstraints.HORIZONTAL;
// addComponent(p, fileDateText = new JTextField(), constraints);
// fileDateText.setEnabled(false);
constraints.gridy++;
constraints.gridx = 0;
constraints.gridwidth = 2;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.WEST;
addComponent(p, mergeFiles = new JCheckBox("Merge contiguous files"), constraints);
if (PamController.getInstance().getRunMode() == PamController.RUN_PAMVIEW) {
constraints.gridx+=2;
constraints.gridwidth = 1;
addComponent(p, checkFiles = new JButton("Check File Headers..."), constraints);
checkFiles.addActionListener(new CheckFiles());
}
// if (SMRUEnable.isEnable()) {
// no reason to hide this option from users.
constraints.gridy++;
constraints.gridx = 0;
constraints.gridwidth = 1;
//panel which allows the users to skip the first section of a file.
PamPanel skipPanel = new PamPanel(new GridBagLayout());
constraints.insets = new Insets(0,0,0,5);
addComponent(skipPanel, new JLabel("Skip initial "), constraints);
constraints.gridx++;
addComponent(skipPanel, skipSecondsField = new JTextField(4), constraints);
constraints.gridx++;
addComponent(skipPanel, new JLabel("seconds"), constraints);
constraints.anchor = GridBagConstraints.WEST;
constraints.gridwidth = 3;
constraints.gridx = 0;
addComponent(p, skipPanel, constraints);
//panel to show bespoke settings for certain audio loaders.
constraints.anchor = GridBagConstraints.WEST;
constraints.gridx = 0;
constraints.gridy++;
constraints.gridwidth = 3;
addComponent(p, audioLoaderHolder = new PamPanel(), constraints);
GridBagLayout layout2 = new GridBagLayout();
audioLoaderHolder.setLayout(layout2);
return p;
}
class FileComboListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent arg0) {
String fileName = (String) fileNameCombo.getSelectedItem();
if (fileName != null) {
// System.out.println(fileName);
String[] str = new String[1];
str[0] = fileName;
folderInputParameters.setSelectedFiles(str);
makeSelFileList(str);
}
}
}
class CheckFiles implements ActionListener {
@Override
public void actionPerformed(ActionEvent arg0) {
checkFileHeaders();
}
}
/**
* Checks file length matched actual file data length and repairs if necessary.
*/
public void checkFileHeaders() {
CheckWavFileHeaders.showDialog(acquisitionDialog, this);
}
class FindAudioFolder implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
selectFolder();
}
}
@Override
public void setSelected(boolean select) {
super.setSelected(select);
if (select) {
makeSelFileList();
}
}
/**
* Make a list of wav files within a folder. In some circumstances this can be a list
* of actual files in a folder. Also needs to handle the possibility of it using
* a globally set folder name.
* @return flag to indicate...nothing?
*/
public int makeSelFileList() {
String[] selection = checkComandLineFolder();
if (selection == null) {
if (fileInputParameters.recentFiles == null || fileInputParameters.recentFiles.size() < 1) {
return 0;
}
selection = folderInputParameters.getSelectedFiles();
}
if (selection == null) {
return 0;
}
if (selection.length > 0) {
System.out.println("FolderInputSystem.makeSelFileList(): Searching for sound files in " + selection[0]);
}
return makeSelFileList(selection);
}
/**
* Make a list of wav files within a folder.
* @param rootList
* @return
*/
public int makeSelFileList(String[] rootList) {
// File[] selectedFiles = folderInputParameters.getSelectedFiles();
// if (selectedFiles.length == 1 && selectedFiles[0].isDirectory()) {
// String folderName = fileInputParameters.recentFiles.get(0);
//Swing calls a dialog with progress bar from the wavListWorker
wavListStart = System.currentTimeMillis();
if (folderInputPane==null) {
//Swing way
wavListWorker.startFileListProcess(PamController.getMainFrame(), rootList,
folderInputParameters.subFolders, true);
}
else {
//FX system
PamWorker<FileListData<WavFileType>> worker = wavListWorker.makeFileListProcess(rootList, folderInputParameters.subFolders, true);
folderInputPane.setFileWorker(worker);
if (worker!=null) worker.start();
}
return 0;
}
// private int makeSelFileList(String fileOrFolder) {
// File[] file = new File[1];
// file[0] = new File(fileOrFolder);
// return makeSelFileList(file);
//
// }
//
// public int makeSelFileList(File[] fileList) {
//
// allFiles.clear();
//
// currentFile = 0;
//
// if (fileInputParameters.recentFiles == null || fileInputParameters.recentFiles.size() < 1) return 0;
//
// String folderName = fileInputParameters.recentFiles.get(0);
//
// if (folderName == null) return 0;
//
// File currentFolder = new File(folderName);
//
// for (int i = 0; i < fileList.length; i++) {
// if (fileList[i].isDirectory()) {
// addFolderFiles(currentFolder);
// }
// else if (fileList[i].isFile() && !fileList[i].isHidden()) {
// allFiles.add(fileList[i]);
// }
// }
//
// if (allFiles.size() > 0) {
//
// }
// folderProgress.setMinimum(0);
// folderProgress.setMaximum(allFiles.size());
// folderProgress.setValue(0);
//
// Collections.sort(allFiles);
//
// return allFiles.size();
//
// }
//
// void addFolderFiles(File folder) {
// File[] files = folder.listFiles(getFolderFileFilter());
// if (files == null) return;
// boolean includeSubFolders = folderInputParameters.subFolders;
// File file;
// for (int i = 0; i < files.length; i++) {
// file = files[i];
// if (file.isDirectory() && includeSubFolders) {
//// System.out.println(file.getAbsoluteFile());
// addFolderFiles(file.getAbsoluteFile());
// }
// else if (file.isFile()) {
// allFiles.add(file);
// }
// }
// }
public PamFileFilter getFolderFileFilter() {
PamAudioFileFilter filter = new PamAudioFileFilter();
filter.setAcceptFolders(true);
return filter;
}
protected void selectFolder() {
JFileChooser fc = null;
if (fc == null) {
fc = new PamFileChooser();
fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fc.setMultiSelectionEnabled(true);
fc.setFileFilter(getFolderFileFilter());
}
if (fileNameCombo.getSelectedIndex() >= 0) {
fc.setCurrentDirectory(new File(fileNameCombo.getSelectedItem().toString()));
}
if (folderInputParameters.getSelectedFiles() != null) {
fc.setSelectedFiles(folderInputParameters.getSelectedFileFiles());
}
int ans = fc.showDialog(null, "Select files and folders");
if (ans == JFileChooser.APPROVE_OPTION) {
/*
* if it's a single directory that's been selected, then
* set that with setNewFile. If multiple files and directories
* are accepted, select the parent directory of all of them.
*/
File[] files = fc.getSelectedFiles();
if (files.length <= 0) return;
else if (files.length == 1) {
setNewFile(fc.getSelectedFile().toString());
}
else {
// take the folder name from the first file
File aFile = files[0];
setNewFile(aFile.getAbsolutePath());
}
/*
* The file chooser is returning sub classes of File which are not
* serialisable, so we can't use them. We need to convert their
* names to strings, which can be safely serialized. This will
* all happen in FolderInputParameters.
*/
folderInputParameters.setSelectedFiles(fc.getSelectedFiles());
makeSelFileList();
// makeSelFileList(fc.getSelectedFiles());
}
}
public String getCurrentFolder() {
if (folderInputParameters.recentFiles.size() == 0) {
return null;
}
return folderInputParameters.recentFiles.get(0);
}
long wavListStart;
/**
* Creates a list of wav files.
*/
WavListWorker wavListWorker = new WavListWorker(new WavListReceiver());
private class WavListReceiver implements WavListUser {
@Override
public void newFileList(FileListData<WavFileType> fileListData) {
FolderInputSystem.this.newFileList(fileListData);
}
}
@Override
public void interpretNewFile(String newFile) {
if (newFile == null) {
return;
}
/*
* don't actually need to do anything ? Could make a new list, but do it from what's in the
* folder parameters, not the file parameters. do nothing here, or it gets too complicated.
* Call the search function from the file select part of the dialot.
*/
// test the new Wav list worker ...
// wavListStart = System.currentTimeMillis();
// wavListWorker.startFileListProcess(PamController.getMainFrame(), newFile, true, true);
// makeSelFileList(newFile);
}
/**
* Callback when the file list has completed it's background task.
* @param fileListData
*/
public void newFileList(FileListData<WavFileType> fileListData) {
// System.out.printf("Wav list recieved with %d files after %d millis\n",
// fileListData.getFileCount(), System.currentTimeMillis() - wavListStart);
allFiles = fileListData.getListCopy();
List<WavFileType> asList = allFiles;
setSelectedFileTypes(acquisitionControl.soundFileTypes.getUsedTypes(allFiles));
//set the date of the first file.
setFileDateText();
//set any bespoke options for the files to be laoded.
setFileOptionPanel();
// also open up the first file and get the sample rate and number of channels from it
// and set these
File file = getCurrentFile();
if (file == null) return;
AudioInputStream audioStream;
/****Swing GUI stuff****/
if (file.isFile() && !file.isHidden() && acquisitionDialog != null) {
//Hidden files should not be used in analysis...
try {
audioStream = PamAudioFileManager.getInstance().getAudioInputStream(file);
AudioFormat audioFormat = audioStream.getFormat();
fileSamples = audioStream.getFrameLength();
acquisitionDialog.setSampleRate(audioFormat.getSampleRate());
acquisitionDialog.setChannels(fudgeNumChannels(audioFormat.getChannels()));
audioStream.close();
//prevent the dialog from going nuts when components are resized.
acquisitionDialog.validate();
acquisitionDialog.pack();
}
catch (Exception Ex) {
// Ex.printStackTrace();
System.err.println("Error in file " + file.getAbsolutePath() + " " + Ex.getLocalizedMessage());
}
}
// set the min and max of the folder progress bar
folderProgress.setMinimum(0);
folderProgress.setMaximum(allFiles.size());
/****FX GUI stuff****/
if (folderInputPane!=null) {
Platform.runLater(()->{
folderInputPane.newFileList(fileListData);
});
}
}
/**
* Fudge function so that the RonaInputsystem can always fudge the number
* of channels to be 7.
* @param nChannels
* @return
*/
protected int fudgeNumChannels(int nChannels) {
return nChannels;
}
/**
* Set bespoke options for certain file types.
*/
public void setFileOptionPanel() {
getDialogPanel(); // make sure panel is created
audioLoaderHolder.removeAll();
if (allFiles.size() > 0) {
//Get all the audio file laoders that will be used for this list of files. Usually
//just one but possible that there can be mixed files.
ArrayList<PamAudioFileLoader> loaders = PamAudioFileManager.getInstance().getAudioFileLoaders(allFiles);
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
for (PamAudioFileLoader loader : loaders) {
if (loader.getSettingsPane()!=null) {
// System.out.println("ADD AUDIO PANEL: " +loader.getSettingsPane().getAudioLoaderPanel());
//gridbag layout
addComponent(audioLoaderHolder, loader.getSettingsPane().getAudioLoaderPanel(), constraints);
loader.getSettingsPane().setParams();
constraints.gridy++;
}
}
}
}
/**
* Show the date of the first file in the dialog.
*/
public void setFileDateText() {
if (allFiles.size() > 0) {
long fileTime = getFileStartTime(getCurrentFile());
// fileDateText.setText(PamCalendar.formatDateTime(fileTime));
getDialogPanel(); // make sure it's created
fileDateStrip.setDate(fileTime);
fileDateStrip.setFormat(acquisitionControl.getFileDate().getFormat());
}
}
@Override
public String getSystemType() {
return sysType;
}
@Override
public String getUnitName() {
// return "File Folder Analysis";
return acquisitionControl.getUnitName();
}
@Override
public String getUnitType() {
return daqType;
}
@Override
public File getCurrentFile() {
//System.out.println("All files: " + allFiles);
// System.out.printf("Folder: getCurrentfile. on %d of %d\n", currentFile, allFiles.size());
if (allFiles != null && allFiles.size() > currentFile) {
return allFiles.get(currentFile);
}
return null;
}
// private float currentSampleRate;
@Override
protected boolean openNextFile(long totalSamples) {
boolean ans = false;
if (!folderInputParameters.mergeFiles) return false;
long currFileStart = 0;
long currFileLength = 0;
long currFileEnd = 0;
if (currentFile >= 0) {
try {
WavFileType currentWav = allFiles.get(currentFile);
currFileStart = getFileStartTime(currentWav.getAbsoluteFile());
if (audioStream != null) {
fileSamples = audioStream.getFrameLength();
currFileLength = (long) (fileSamples * 1000 / audioStream.getFormat().getFrameRate());
currFileEnd = currFileStart + currFileLength;
}
}
catch (Exception e) {
}
}
if (currFileEnd == 0) {
// System.out.println("OpenNextfile " + currentFile + " " + allFiles.get(currentFile).getName());
// also check to see if the start time of the next file is the same as the
// end time of the current file.
currFileEnd = PamCalendar.getTimeInMillis();
long lastBit = (long) ((blockSamples * 1000L) / getSampleRate());
currFileEnd += lastBit;
}
if (++currentFile < allFiles.size()) {
calculateETA();
long newStartTime = getFileStartTime(getCurrentFile());
long diff = newStartTime - currFileEnd;
if (diff > 2000 || diff < -5000 || newStartTime == 0) {
currentFile--;
return false;
/*
* Return since it's not possible to merge this file into the
* next one. In this instance, DAQ will restart, and the currentfile
* counter will increment elsewhere.
*/
}
setFolderProgress();
// sayEta();
/*
* I think that here, we just need a check of the file. the prepareInputFile in
* this class will (on failure) move straight to the next file and also issue a
* stop/start, which is not good if it's trying a continuous file, where this is
* being called, if false is returned it should manage moving onto the next file by
* itself if we use the super.prep ....
*/
ans = super.prepareInputFile();
if (!ans) {
return false;
}
currentFileStart = System.currentTimeMillis();
// if (ans && audioFormat.getSampleRate() != currentSampleRate && currentFile > 0) {
// acquisitionControl.getDaqProcess().setSampleRate(currentSampleRate = audioFormat.getSampleRate(), true);
// }
/**
* Send a dataunit to the database to mark the file changeover.
*/
DaqStatusDataUnit daqStatusDataUnit = new DaqStatusDataUnit(currentFileStart, currentFileStart, currentFileStart,
totalSamples, null, "NextFile", "File End",
acquisitionControl.acquisitionParameters, getSystemName(), totalSamples/getSampleRate(), 0);
acquisitionControl.getAcquisitionProcess().getDaqStatusDataBlock().addPamData(daqStatusDataUnit);
}
return ans;
}
@Override
public void daqHasEnded() {
currentFile++;
if (folderInputParameters.repeatLoop && currentFile >= allFiles.size()) {
currentFile = 0;
}
if (currentFile < allFiles.size()) {
// only restart if the file ended - not if it stopped
if (getStreamStatus() == STREAM_ENDED && !PamController.getInstance().isManualStop()) {
// System.out.println(String.format("Start new file timer (file %d/%d)",currentFile+1,allFiles.size()));
newFileTimer.start();
}
}
calculateETA();
setFolderProgress();
if (currentFile > 0 && currentFile >= allFiles.size()) {
fileListComplete();
}
// System.out.println("FolderinputSytem: daqHasEnded");
}
private void setFolderProgress() {
folderProgress.setValue(currentFile);
folderProgress.setString(String.format("%d/%d", currentFile, folderProgress.getMaximum()));
}
protected void calculateETA() {
long now = System.currentTimeMillis();
eta = now-currentFileStart;
eta *= (allFiles.size()-currentFile);
eta += now;
}
JPanel barBit;
PamProgressBar folderProgress = new PamProgressBar(PamProgressBar.defaultColor);
private FolderInputPane folderInputPane;
@Override
public Component getStatusBarComponent() {
if (barBit == null) {
barBit = new PamPanel();
barBit.setLayout(new BoxLayout(barBit, BoxLayout.X_AXIS));
barBit.add(new PamLabel("Folder "));
barBit.add(folderProgress);
barBit.add(new PamLabel(" "));
barBit.add(super.getStatusBarComponent());
folderProgress.setToolTipText("Process through file folder(s)");
}
return barBit;
}
@Override
public long getEta() {
if (currentFile == allFiles.size()-1) {
return super.getEta();
}
return eta;
}
@Override
public Serializable getSettingsReference() {
return folderInputParameters;
}
@Override
public long getSettingsVersion() {
return FolderInputParameters.serialVersionUID;
}
@Override
public boolean dialogGetParams() {
folderInputParameters.subFolders = subFolders.isSelected();
folderInputParameters.mergeFiles = mergeFiles.isSelected();
folderInputParameters.repeatLoop = repeat.isSelected();
currentFile = 0;
if (skipSecondsField!=null) {
try {
Double skipSeconds = Double.valueOf(skipSecondsField.getText())*1000.; // saved in millis.
folderInputParameters.skipStartFileTime = skipSeconds.longValue();
}
catch (Exception e) {
return false;
}
}
//get bespoke paramters from selected audio loaders.
ArrayList<PamAudioFileLoader> loaders = PamAudioFileManager.getInstance().getAudioFileLoaders(allFiles);
for (PamAudioFileLoader loader : loaders) {
if (loader.getSettingsPane()!=null) {
loader.getSettingsPane().getParams();
}
}
return super.dialogGetParams();
}
@Override
public void dialogSetParams() {
// do a quick check to see if the system type is stored in the parameters. This field was added
// to the FileInputParameters class on 23/11/2020, so any psfx created before this time
// would hold a null. The system type is used by the getParameterSet method to decide
// whether or not to include the parameters in the XML output
if (fileInputParameters.systemType==null) fileInputParameters.systemType=getSystemType();
super.dialogSetParams();
subFolders.setSelected(folderInputParameters.subFolders);
mergeFiles.setSelected(folderInputParameters.mergeFiles);
repeat.setSelected(folderInputParameters.repeatLoop);
if (skipSecondsField!=null) {
skipSecondsField.setText(String.format("%.1f", fileInputParameters.skipStartFileTime/1000.));
}
}
@Override
public boolean restoreSettings(PamControlledUnitSettings pamControlledUnitSettings) {
FolderInputParameters newParams;
try {
newParams = (FolderInputParameters) pamControlledUnitSettings.getSettings();
}
catch (ClassCastException ex) {
return false;
}
setFolderInputParameters(newParams);
return true;
}
public FolderInputParameters getFolderInputParameters() {
return folderInputParameters;
}
public void setFolderInputParameters(FolderInputParameters folderInputParameters) {
this.folderInputParameters = folderInputParameters;
fileInputParameters = this.folderInputParameters;
}
@Override
public boolean startSystem(AcquisitionControl daqControl) {
// System.out.println("Start system");
setFolderProgress();
return super.startSystem(daqControl);
}
// /**
// * @param audioFileFilter the audioFileFilter to set
// */
// public void setAudioFileFilter(PamFileFilter audioFileFilter) {
// this.audioFileFilter = audioFileFilter;
// }
//
// /**
// * @return the audioFileFilter
// */
// public PamFileFilter getAudioFileFilter() {
// return audioFileFilter;
// }
@Override
public String getDeviceName() {
if (fileInputParameters.recentFiles == null || fileInputParameters.recentFiles.size() < 1) {
return null;
}
return fileInputParameters.recentFiles.get(0);
}
public PamFileFilter getAudioFileFilter() {
return audioFileFilter;
}
public void setAudioFileFilter(PamFileFilter audioFileFilter) {
this.audioFileFilter = audioFileFilter;
}
/****JavaFX bits***/
@Override
public DAQSettingsPane getDAQSpecificPane(AcquisitionPaneFX acquisitionPaneFX) {
if (folderInputPane==null) this.folderInputPane = new FolderInputPane(this, acquisitionPaneFX);
return folderInputPane;
}
/**
* Called by AcquisitionDialog.SetParams so that the dialog node can update it's
* fields.
*/
public void dialogFXSetParams() {
folderInputPane.setParams(folderInputParameters);
}
@Override
public InputStoreInfo getStoreInfo(boolean detail) {
System.out.println("FolderInputSystem: Get store info start:");
if (allFiles == null || allFiles.size() == 0) {
return null;
}
WavFileType firstFile = allFiles.get(0);
long firstFileStart = getFileStartTime(firstFile.getAbsoluteFile());
WavFileType lastFile = allFiles.get(allFiles.size()-1);
long lastFileStart = getFileStartTime(lastFile.getAbsoluteFile());
lastFile.getAudioInfo();
long lastFileEnd = (long) (lastFileStart + lastFile.getDurationInSeconds()*1000.);
InputStoreInfo storeInfo = new InputStoreInfo(acquisitionControl, allFiles.size(), firstFileStart, lastFileStart, lastFileEnd);
if (detail) {
long[] allFileStarts = new long[allFiles.size()];
long[] allFileEnds = new long[allFiles.size()];
for (int i = 0; i < allFiles.size(); i++) {
WavFileType aFile = allFiles.get(i);
allFileStarts[i] = getFileStartTime(aFile.getAbsoluteFile());
aFile.getAudioInfo();
allFileEnds[i] = (allFileStarts[i] + (long) (aFile.getDurationInSeconds()*1000.));
if (allFileStarts[i] < firstFileStart) {
// System.out.printf("Swap first file from %s to %s\n", firstFile.getName(), allFiles.get(i).getName());
firstFile = allFiles.get(i);
firstFileStart = allFileStarts[i];
}
if (allFileStarts[i] > lastFileEnd) {
// System.out.printf("Swap last file from %s to %s\n", lastFile.getName(), allFiles.get(i).getName());
lastFile = allFiles.get(i);
lastFileEnd = allFileStarts[i] + (long) (lastFile.getDurationInSeconds()*1000.);
}
}
storeInfo.setFirstFileStart(firstFileStart); // just incase changed.
storeInfo.setLastFileEnd(lastFileEnd); // just incase changed
storeInfo.setFileStartTimes(allFileStarts);
storeInfo.setFileEndTimes(allFileEnds);
}
System.out.println("FolderInputSystem: Get store info complete:");
return storeInfo;
}
@Override
public boolean setAnalysisStartTime(long startTime) {
/**
* Called from the reprocess manager just before PAMGuard starts with a time
* we want to process from. This should be equal to the start of one of the files
* so all we have to do (in principle) is to set the currentfile to that index and
* processing will continue from there.
*/
if (allFiles == null || allFiles.size() == 0) {
System.out.println("Daq setanal start time: no files to check against");
return false;
}
System.out.printf("setAnalysisStarttTime: checking %d files for start time of %s\n", allFiles.size(), PamCalendar.formatDBDateTime(startTime));
/*
* If the starttime is maxint then there is nothing to do, but we do need to set the file index
* correctly to not over confuse the batch processing system.
*/
long lastFileTime = getFileStartTime(allFiles.get(allFiles.size()-1).getAbsoluteFile());
if (startTime > lastFileTime) {
currentFile = allFiles.size();
System.out.println("Folder Acquisition processing is complete and no files require processing");
return true;
}
for (int i = 0; i < allFiles.size(); i++) {
long fileStart = getFileStartTime(allFiles.get(i).getAbsoluteFile());
if (fileStart >= startTime) {
currentFile = i;
PamCalendar.setSoundFile(true);
if (startTime > 0) {
PamCalendar.setSessionStartTime(startTime);
System.out.printf("Sound Acquisition start processing at file %s time %s\n", allFiles.get(i).getName(),
PamCalendar.formatDBDateTime(fileStart));
}
return true;
}
}
return false;
}
/**
* Get a status update for batch processing.
*/
@Override
public String getBatchStatus() {
int nFiles = 0;
if (allFiles != null) {
nFiles = allFiles.size();
}
int generalStatus = PamController.getInstance().getPamStatus();
File currFile = getCurrentFile();
String bs = String.format("%d,%d,%d,%s", nFiles,currentFile,generalStatus,currFile);
return bs;
}
}