DOWNLOAD
package AngioTool; import Pixels.Rgb;import Utils.*;import Xlsx.*;import java.awt.Color;import java.awt.BorderLayout;import java.awt.Desktop;import java.awt.Dimension;import java.awt.Font;import java.awt.event.*;import javax.swing.*;import javax.swing.border.EmptyBorder;import javax.swing.filechooser.FileFilter;import java.io.File;import java.io.IOException;import java.nio.file.Files;import java.nio.file.StandardCopyOption;import java.util.ArrayList;import java.util.concurrent.Future;import java.util.concurrent.atomic.AtomicBoolean; public class BatchWindow extends JFrame implements BatchProcessing.IProgressToken{ static final int UNITS_GAP = 4; static final double BALANCED_FACTOR = 0.75; final AngioToolGui2 mainWindow; final JLabel labelData = new JLabel(); final JLabel labelInputFolders = new JLabel(); final JButton btnInputFolders = new JButton(); final JTextField textInputFolders = new JTextField(); final JLabel labelExcel = new JLabel(); final JButton btnExcel = new JButton(); final JTextField textExcel = new JTextField(); final ButtonGroup groupSaveResults = new ButtonGroup(); final JRadioButton rbNoOutput = new JRadioButton(); final JRadioButton rbSameOutput = new JRadioButton(); final JRadioButton rbSaveResultsTo = new JRadioButton(); final JButton btnSaveResultsFolder = new JButton(); final JTextField textSaveResultsFolder = new JTextField(); final JLabel labelResultsImageFormat = new JLabel(); final JTextField textResultsImageFormat = new JTextField(); final JCheckBox cbWorkerCount = new JCheckBox(); final JTextField textWorkerCount = new JTextField(); final JLabel labelWorkerCountHelp = new JLabel(); final JSeparator sepProgress = new JSeparator(); final JLabel labelProgress = new JLabel(); final JLabel overallLabel = new JLabel(); final JProgressBar overallProgress = new JProgressBar(); final JLabel labelCurrentFile = new JLabel(); final JButton analyzeBtn = new JButton(); final JButton cancelBtn = new JButton(); final String workerCountHelpString; int storedWorkerCount; String defaultPath; ArrayList<XlsxReader.SheetCells> originalSheets = new ArrayList<>(); int nErrors = 0; public Future<Void> analysisTaskFuture = null; public final AtomicBoolean isClosed = new AtomicBoolean(false); public BatchWindow(AngioToolGui2 mainWindow, BatchParameters params) { super("Batch Analysis"); this.mainWindow = mainWindow; this.defaultPath = params.defaultPath; this.setIconImage(AngioTool.ATIcon.getImage()); labelData.setText("Data"); Misc.setNewFontSizeOn(labelData, 20); labelInputFolders.setText("Select input folders:"); btnInputFolders.setIcon(AngioTool.ATFolderSmall); btnInputFolders.addActionListener((ActionEvent e) -> BatchWindow.this.selectInputFolders()); //textInputFolders labelExcel.setText("Excel spreadsheet:"); btnExcel.setIcon(AngioTool.ATExcelSmall); btnExcel.addActionListener((ActionEvent e) -> BatchWindow.this.selectExcelFile()); //textExcel rbNoOutput.setText("No output"); rbNoOutput.addActionListener((ActionEvent e) -> BatchWindow.this.toggleSaveResults()); rbNoOutput.setSelected(!params.shouldSaveResultImages); rbSameOutput.setText("Same folders as inputs"); rbSameOutput.addActionListener((ActionEvent e) -> BatchWindow.this.toggleSaveResults()); rbSameOutput.setSelected(params.shouldSaveResultImages && !params.shouldSaveImagesToSpecificFolder); rbSaveResultsTo.setText("Save result images to:"); rbSaveResultsTo.addActionListener((ActionEvent e) -> BatchWindow.this.toggleSaveResults()); rbSaveResultsTo.setSelected(params.shouldSaveResultImages && params.shouldSaveImagesToSpecificFolder); groupSaveResults.add(rbNoOutput); groupSaveResults.add(rbSameOutput); groupSaveResults.add(rbSaveResultsTo); btnSaveResultsFolder.setIcon(AngioTool.ATFolderSmall); btnSaveResultsFolder.addActionListener((ActionEvent e) -> BatchWindow.this.selectResultFolder()); //textSaveResultsFolder toggleSaveResults(); labelResultsImageFormat.setText("Result image format: "); textResultsImageFormat.setText(params.resultImageFormat); cbWorkerCount.setText("Override job count: "); cbWorkerCount.setSelected(params.shouldOverrideWorkerCount); cbWorkerCount.addActionListener((ActionEvent e) -> BatchWindow.this.toggleWorkerCount()); int nProcessors = Runtime.getRuntime().availableProcessors(); int defaultWorkerCount = (int)(nProcessors * BALANCED_FACTOR); this.workerCountHelpString = "<html>Recommended job count:<br>" + nProcessors + " for maximum throughput,<br>" + defaultWorkerCount + " (default) for balancing resources</html>"; Misc.setNewFontStyleOn(labelWorkerCountHelp, Font.PLAIN); if (params.shouldOverrideWorkerCount) { textWorkerCount.setText("" + params.workerCount); labelWorkerCountHelp.setText(workerCountHelpString); } else { textWorkerCount.setText(""); } storedWorkerCount = params.workerCount > 0 ? params.workerCount : defaultWorkerCount; //sepProgress labelProgress.setText("Progress"); Misc.setNewFontSizeOn(labelProgress, 20); //overallLabel overallProgress.setValue(0); overallProgress.setStringPainted(true); Misc.setNewFontStyleOn(labelCurrentFile, Font.ITALIC); analyzeBtn.setText("Run"); analyzeBtn.addActionListener((ActionEvent e) -> BatchWindow.this.startAnalysis()); cancelBtn.setText("Cancel"); cancelBtn.addActionListener((ActionEvent e) -> BatchWindow.this.cancel()); cancelBtn.setEnabled(false); } public void showDialog() { JPanel dialogPanel = new JPanel(); GroupLayout layout = new GroupLayout(dialogPanel); dialogPanel.setLayout(layout); dialogPanel.setBorder(new EmptyBorder(0, 2, 12, 2)); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); arrangeUi(layout); this.getContentPane().add(dialogPanel); this.pack(); this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent evt) { ATPreferences.savePreferences(buildBatchParamsFromUi(), AngioTool.BATCH_TXT); } }); int extraWidth = cbWorkerCount.isSelected() ? 150 : 250; Dimension preferredSize = this.getPreferredSize(); this.setMinimumSize(preferredSize); this.setSize(new Dimension(preferredSize.width + extraWidth, preferredSize.height + 10)); this.setLocation(700, 300); this.setVisible(true); } private void arrangeUi(GroupLayout layout) { layout.setHorizontalGroup(layout.createParallelGroup() .addComponent(labelData) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup() .addComponent(labelInputFolders) .addComponent(labelExcel) ) .addGroup(layout.createParallelGroup() .addComponent(btnInputFolders) .addComponent(btnExcel) ) .addGroup(layout.createParallelGroup() .addComponent(textInputFolders) .addComponent(textExcel) ) ) .addComponent(rbNoOutput) .addComponent(rbSameOutput) .addGroup(layout.createSequentialGroup() .addComponent(rbSaveResultsTo) .addComponent(btnSaveResultsFolder) .addComponent(textSaveResultsFolder) ) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addComponent(labelResultsImageFormat) .addComponent(textResultsImageFormat, 0, 0, 80) ) .addGroup(layout.createSequentialGroup() .addComponent(cbWorkerCount) .addComponent(textWorkerCount, 0, 0, 80) ) ) .addComponent(labelWorkerCountHelp) ) .addComponent(sepProgress) .addComponent(labelProgress) .addComponent(overallLabel) .addComponent(overallProgress) .addComponent(labelCurrentFile) .addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(analyzeBtn) .addComponent(cancelBtn) ) ); final int MIN_TEXT_HEIGHT = 18; final int TEXT_HEIGHT = 30; layout.setVerticalGroup(layout.createSequentialGroup() .addComponent(labelData) .addGap(8) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(labelInputFolders) .addComponent(btnInputFolders) .addComponent(textInputFolders, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) ) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(labelExcel) .addComponent(btnExcel) .addComponent(textExcel, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) ) .addComponent(rbNoOutput) .addComponent(rbSameOutput) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(rbSaveResultsTo) .addComponent(btnSaveResultsFolder) .addComponent(textSaveResultsFolder, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) ) .addGap(12) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addGroup(layout.createSequentialGroup() .addComponent(labelResultsImageFormat, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) .addComponent(cbWorkerCount, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) ) .addGroup(layout.createSequentialGroup() .addComponent(textResultsImageFormat, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) .addComponent(textWorkerCount, MIN_TEXT_HEIGHT, TEXT_HEIGHT, TEXT_HEIGHT) ) .addComponent(labelWorkerCountHelp) ) .addGap(12) .addComponent(sepProgress) .addComponent(labelProgress) .addComponent(overallLabel) .addComponent(overallProgress) .addComponent(labelCurrentFile, 48, 48, Short.MAX_VALUE) .addGroup(layout.createParallelGroup() .addComponent(analyzeBtn) .addComponent(cancelBtn) ) ); } void toggleSaveResults() { boolean enabled = rbSaveResultsTo.isSelected(); btnSaveResultsFolder.setEnabled(enabled); textSaveResultsFolder.setEnabled(enabled); } void toggleWorkerCount() { boolean enabled = cbWorkerCount.isSelected(); labelWorkerCountHelp.setText(workerCountHelpString); if (enabled) { int workerCount = storedWorkerCount > 0 ? storedWorkerCount : getDefaultWorkerCount(); textWorkerCount.setText("" + workerCount); } else { try { storedWorkerCount = Integer.parseInt(textWorkerCount.getText()); } catch (Exception ignored) { storedWorkerCount = 0; } textWorkerCount.setText(""); } } void selectInputFolders() { JFileChooser fc = Misc.createFileChooser(); fc.setDialogTitle("Select Folders"); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fc.setMultiSelectionEnabled(true); fc.setCurrentDirectory(new File(defaultPath)); if (fc.showOpenDialog(this) != 0) return; File[] folderList = fc.getSelectedFiles(); if (folderList == null || folderList.length == 0) return; StringBuilder sb = new StringBuilder(); for (int i = 0; i < folderList.length; i++) { if (i > 0) sb.append(";"); sb.append(folderList[i].getAbsolutePath().replace(";", "" + File.separatorChar + ";")); } textInputFolders.setText(sb.toString()); } void selectExcelFile() { String[] outStrings = new String[2]; ArrayList<XlsxReader.SheetCells> sheets = Misc.openSpreadsheetForAppending(outStrings, null, defaultPath, this); if (sheets != null) { originalSheets = sheets; defaultPath = outStrings[1]; textExcel.setText(outStrings[0]); } } void selectResultFolder() { JFileChooser fc = Misc.createFileChooser(); fc.setDialogTitle("Select Folders"); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fc.setCurrentDirectory(new File(defaultPath)); if (fc.showOpenDialog(this) == 0) { File file = fc.getSelectedFile(); if (file != null) textSaveResultsFolder.setText(file.getAbsolutePath()); } } public BatchParameters buildBatchParamsFromUi() { boolean shouldSaveImages; boolean shouldUseSpecificOutputFolder; ButtonModel saveImageType = groupSaveResults.getSelection(); if (saveImageType == rbSaveResultsTo.getModel()) { shouldSaveImages = true; shouldUseSpecificOutputFolder = true; } else if (saveImageType == rbNoOutput.getModel()) { shouldSaveImages = false; shouldUseSpecificOutputFolder = false; } else { shouldSaveImages = true; shouldUseSpecificOutputFolder = false; } boolean shouldOverrideWorkerCount = cbWorkerCount.isSelected(); int workerCount = shouldOverrideWorkerCount ? Integer.parseInt(textWorkerCount.getText()) : getDefaultWorkerCount(); return new BatchParameters( defaultPath, Misc.splitPaths(textInputFolders.getText(), ';', File.separatorChar), textExcel.getText(), shouldSaveImages, shouldUseSpecificOutputFolder, textSaveResultsFolder.getText(), textResultsImageFormat.getText(), shouldOverrideWorkerCount, workerCount ); } public void startAnalysis() { if (analysisTaskFuture != null && !analysisTaskFuture.isDone()) { Misc.showDialogBox( "Analysis Still in Progress", "A batch analysis is still running. Either cancel it or wait for it to complete." ); return; } nErrors = 0; AnalyzerParameters params; try { params = mainWindow.buildAnalyzerParamsFromUi(); } catch (Throwable t) { Misc.showDialogBox("Parsing Error", "Invalid data in the analysis form (" + t.getClass().getSimpleName() + ")"); return; } RefVector<String> errors = params.validate(); BatchParameters batchParams; try { batchParams = buildBatchParamsFromUi(); } catch (Throwable t) { Misc.showDialogBox("Parsing Error", "Invalid data in the batch form (" + t.getClass().getSimpleName() + ")"); return; } RefVector<String> batchErrors = batchParams.validate(); errors.extend(batchErrors); if (errors.size > 0) { int nErrors = errors.size; String header = nErrors > 1 ? ("There were " + nErrors + " errors:\n") : ""; Misc.showDialogBox( "Validation Error" + (nErrors > 1 ? "s" : ""), header + errors.makeJoinedString("\n") ); return; } cancelBtn.setEnabled(true); ATPreferences.savePreferences(params, AngioTool.PREFS_TXT); ATPreferences.savePreferences(batchParams, AngioTool.BATCH_TXT); analysisTaskFuture = (Future<Void>)Analyzer.threadPool.submit( () -> BatchProcessing.doBatchAnalysis(params, batchParams, BatchWindow.this, originalSheets) ); } void updateWindowSize() { Dimension preferred = this.getPreferredSize(); Dimension curSize = this.getSize(); if (preferred.height > curSize.height) this.setSize(new Dimension(curSize.width, preferred.height)); } @Override public boolean isClosed() { return this.isClosed.get(); } @Override public void notifyNoImages() { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; overallLabel.setText("No images were found!"); cancelBtn.setEnabled(false); updateWindowSize(); }); } @Override public void onEnumerationStart() { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; overallLabel.setText("Finding every image to be analyzed..."); updateWindowSize(); }); } @Override public void onBatchStatsKnown(int nImages) { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; overallLabel.setText("Analyzing images..."); overallProgress.setValue(0); overallProgress.setMaximum(nImages); updateWindowSize(); }); } @Override public void notifyImageWasInvalid() { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; int nImages = overallProgress.getMaximum(); overallProgress.setMaximum(nImages > 1 ? (nImages-1) : 1); }); } @Override public void onImageDone(String path, Throwable error) { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; if (error != null) nErrors++; int current = overallProgress.getValue() + 1; String status = "" + current + "/" + overallProgress.getMaximum(); if (nErrors > 0) status += ", " + nErrors + (nErrors == 1 ? " error." : " errors."); overallLabel.setText(status); overallProgress.setValue(overallProgress.getValue() + 1); labelCurrentFile.setText("<html><p>" + path + "</p></html>"); updateWindowSize(); }); } @Override public void onFinished(SpreadsheetWriter sw) { SwingUtilities.invokeLater(() -> { if (isClosed.get()) return; if (sw.currentSheetIdx >= 0 && sw.currentSheetIdx < sw.sheets.size) originalSheets.add(new XlsxReader.SheetCells(sw.sheets.buf[sw.currentSheetIdx].valueRows)); int nImages = overallProgress.getMaximum(); String status = "" + nImages + "/" + nImages; if (nErrors > 0) { status += " Finished with " + nErrors + " error"; if (nErrors != 1) status += "s"; } else { status += " Done!"; } overallLabel.setText(status); cancelBtn.setEnabled(false); updateWindowSize(); final File xlsxFile = new File(sw.parentFolder, sw.fileName); Analyzer.threadPool.submit(() -> { try { Desktop.getDesktop().open(xlsxFile); } catch (IOException ex) { ex.printStackTrace(); } }); }); } public void cancel() { SwingUtilities.invokeLater(() -> { if (!isClosed.getAndSet(true)) this.dispose(); }); } public static int getDefaultWorkerCount() { return (int)(Runtime.getRuntime().availableProcessors() * BALANCED_FACTOR); }}