From 16abc2b6e8de2bd4c63d4a51552168ccb95141f5 Mon Sep 17 00:00:00 2001 From: JonathanCSmith Date: Thu, 26 Sep 2024 08:57:28 +0100 Subject: [PATCH 1/2] Initial work after first major round of feedback. Improved dialogs Improved user feedback Consolidated and improved generateSummaries Altered configurator UX Improved preview UX Altered input params Added views for all output images --- .../twombli/FileUtils.java | 6 +- .../twombli/Outputs.java | 59 ++++ .../twombli/ProgressCancelListener.java | 5 + .../twombli/ProgressDialog.java | 110 ++++++ .../twombli/TWOMBLIBatchRunner.java | 45 +-- .../twombli/TWOMBLIConfigurator.java | 1 - .../twombli/TWOMBLIRunner.java | 36 +- .../twombli/TWOMBLIWindow.java | 320 +++++++++++++----- .../twombli/YesNoDialog.java | 130 +++++++ src/main/resources/Quant_Black_Space.ijm | 2 +- 10 files changed, 578 insertions(+), 136 deletions(-) create mode 100644 src/main/java/uk/ac/franciscrickinstitute/twombli/Outputs.java create mode 100644 src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressCancelListener.java create mode 100644 src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressDialog.java create mode 100644 src/main/java/uk/ac/franciscrickinstitute/twombli/YesNoDialog.java diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/FileUtils.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/FileUtils.java index 47fc1e4..9386ceb 100644 --- a/src/main/java/uk/ac/franciscrickinstitute/twombli/FileUtils.java +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/FileUtils.java @@ -40,12 +40,12 @@ public static boolean verifyOutputDirectoryIsEmpty(String potential) { return true; } - YesNoCancelDialog dialog = new YesNoCancelDialog( + YesNoDialog dialog = new YesNoDialog( IJ.getInstance(), "Output Directory Not Empty", - "The output directory is not empty. Continue and overwrite?", + "The output directory is not empty and this is currently a requirement for the plugin. Would you like to continue and overwrite? If you are unsure, simply make a new directory when selecting your output directory!", "Delete Contents", "Cancel"); - if (dialog.cancelPressed()) { + if (!dialog.yesPressed()) { return false; } diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/Outputs.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/Outputs.java new file mode 100644 index 0000000..b942cd5 --- /dev/null +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/Outputs.java @@ -0,0 +1,59 @@ +package uk.ac.franciscrickinstitute.twombli; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +public class Outputs { + + public static void generateSummaries(Path twombli_csv_path, double alignment, int dimension, Path anamorfSummaryPath, Path hdmSummaryPath, Path gapAnalysisPath, boolean doHeader, boolean gapAnalysis, Path gap_csv_path) { + // Write to our twombli summary + try { + List lines = new ArrayList<>(); + List anamorfEntries = Files.readAllLines(anamorfSummaryPath); + List hdmEntries = Files.readAllLines(hdmSummaryPath); + + // Conditionally write out our header + if (doHeader) { + String headerItems = anamorfEntries.get(0); + String[] hdmHeaderItems = hdmEntries.get(0).split(","); + String header = headerItems + "," + hdmHeaderItems[hdmHeaderItems.length - 1] + ",Alignment (Coherency [%]),Size"; + lines.add(header); + } + + // Get the data + String anamorfData = anamorfEntries.get(anamorfEntries.size() - 1); + String[] hdmData = hdmEntries.get(hdmEntries.size() - 1).split(","); + double hdmValue = 1 - Double.parseDouble(hdmData[hdmData.length - 1]); + lines.add(anamorfData + "," + hdmValue + "," + alignment + "," + dimension); + + // Write + Files.write(twombli_csv_path, lines, StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } + + catch (IOException e) { + throw new RuntimeException(e); + } + + // Write to our gap analysis summary + if (gapAnalysis) { + try (BufferedReader reader = Files.newBufferedReader(gapAnalysisPath); + BufferedWriter writer = Files.newBufferedWriter(gap_csv_path, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { + + String line; + while ((line = reader.readLine()) != null) { + writer.write(line.replace(" ", ",")); + writer.newLine(); // Ensure proper newline after each row + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressCancelListener.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressCancelListener.java new file mode 100644 index 0000000..4ad7c57 --- /dev/null +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressCancelListener.java @@ -0,0 +1,5 @@ +package uk.ac.franciscrickinstitute.twombli; + +public interface ProgressCancelListener { + void handleProgressBarCancelled(); +} diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressDialog.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressDialog.java new file mode 100644 index 0000000..ea68f4c --- /dev/null +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/ProgressDialog.java @@ -0,0 +1,110 @@ +package uk.ac.franciscrickinstitute.twombli; + +import java.awt.*; +import java.awt.event.*; + +import ij.gui.GUI; + +public class ProgressDialog extends Dialog implements WindowListener { + + private final int maxProgress; + private final ProgressCanvas progressCanvas; + private final ProgressCancelListener progressCancelListener; + + public ProgressDialog(Frame parent, String title, int maxProgress, ProgressCancelListener listener) { + super(parent, title, true); + this.maxProgress = maxProgress; + this.progressCancelListener = listener; + + this.setModal(false); + + // Create a canvas to draw the progress bar + this.progressCanvas = new ProgressCanvas(this.maxProgress); + this.progressCanvas.setSize(300, 30); + + // Set up the dialog layout + setLayout(new BorderLayout()); + add(this.progressCanvas, BorderLayout.CENTER); + + // Create a cancel button + Button cancelButton = new Button("Cancel"); + cancelButton.addActionListener(e -> this.handleProgressBarCancelled()); + add(cancelButton, BorderLayout.SOUTH); + + GUI.scale(this); + pack(); + GUI.centerOnImageJScreen(this); + this.setVisible(true); + } + + // Method to update the progress bar + public void updateProgress(int newProgress) { + this.progressCanvas.setProgress(newProgress); + } + + private void handleProgressBarCancelled() { + this.progressCancelListener.handleProgressBarCancelled(); + this.dispose(); + } + + @Override + public void windowOpened(WindowEvent e) {} + + @Override + public void windowClosing(WindowEvent e) {} + + @Override + public void windowClosed(WindowEvent e) { + this.handleProgressBarCancelled(); + } + + @Override + public void windowIconified(WindowEvent e) {} + + @Override + public void windowDeiconified(WindowEvent e) {} + + @Override + public void windowActivated(WindowEvent e) {} + + @Override + public void windowDeactivated(WindowEvent e) {} + + // Canvas for drawing the progress bar + private class ProgressCanvas extends Canvas { + private final int maxProgress; + private int currentProgress = 0; + + public ProgressCanvas(int maxProgress) { + this.maxProgress = maxProgress; + this.currentProgress = 0; + } + + public void setProgress(int progress) { + this.currentProgress = progress; + repaint(); + } + + @Override + public void paint(Graphics g) { + // Clear the canvas + g.setColor(Color.WHITE); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Draw the progress bar background + g.setColor(Color.GRAY); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Draw the progress based on the current value + g.setColor(Color.GREEN); + int barWidth = (int) ((getWidth() * currentProgress) / (float) maxProgress); + g.fillRect(0, 0, barWidth, getHeight()); + + // Draw the progress percentage + g.setColor(Color.BLACK); + int percent = (int) ((currentProgress / (float) maxProgress) * 100); + String progressText = percent + "%"; + g.drawString(progressText, getWidth() / 2 - g.getFontMetrics().stringWidth(progressText) / 2, getHeight() / 2 + 5); + } + } +} diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIBatchRunner.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIBatchRunner.java index e1a1e71..a065c39 100644 --- a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIBatchRunner.java +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIBatchRunner.java @@ -1,5 +1,7 @@ package uk.ac.franciscrickinstitute.twombli; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -143,49 +145,12 @@ private void generateSummaries() { // Gather our basic info String filePrefix = runner.filePrefix; double alignment = runner.alignment; - double dimension = runner.dimension; + int dimension = runner.dimension; Path anamorfSummaryPath = Paths.get(this.outputPath, "masks", filePrefix + "_results.csv"); Path hdmSummaryPath = Paths.get(this.outputPath, "hdm_csvs", filePrefix + "_ResultsHDM.csv"); Path gapAnalysisPath = Paths.get(this.outputPath, "gap_analysis", filePrefix + "_gaps.csv"); - - // Write to our twombli summary - try { - List lines = new ArrayList<>(); - List anamorfEntries = Files.readAllLines(anamorfSummaryPath); - List hdmEntries = Files.readAllLines(hdmSummaryPath); - - // Conditionally write out our header - if (doHeader) { - String headerItems = anamorfEntries.get(0); - String[] hdmHeaderItems = hdmEntries.get(0).split(","); - String header = headerItems + "," + hdmHeaderItems[hdmHeaderItems.length - 1] + ",Alignment (Coherency [%]),Size"; - lines.add(header); - doHeader = false; - } - - // Get the data - String anamorfData = anamorfEntries.get(anamorfEntries.size() - 1); - String[] hdmData = hdmEntries.get(hdmEntries.size() - 1).split(","); - double hdmValue = 1 - Double.parseDouble(hdmData[hdmData.length - 1]); - lines.add(anamorfData + "," + hdmValue + "," + alignment + "," + dimension); - - // Write - Files.write(twombliOutputPath, lines, StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } - - catch (IOException e) { - throw new RuntimeException(e); - } - - // Write to our gap analysis summary - if (this.performGapAnalysis) { - try { - List lines = Files.readAllLines(gapAnalysisPath); - Files.write(gapsOutputPath, lines, StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + Outputs.generateSummaries(twombliOutputPath, alignment, dimension, anamorfSummaryPath, hdmSummaryPath, gapAnalysisPath, doHeader, this.performGapAnalysis, gapsOutputPath); + doHeader = false; } } } diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIConfigurator.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIConfigurator.java index 39fb777..f629455 100644 --- a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIConfigurator.java +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIConfigurator.java @@ -1,7 +1,6 @@ package uk.ac.franciscrickinstitute.twombli; import javax.swing.*; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIRunner.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIRunner.java index e1f4b35..dcdfd17 100644 --- a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIRunner.java +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIRunner.java @@ -74,15 +74,21 @@ public class TWOMBLIRunner implements Command { @Parameter public int minimumGapDiameter = 0; - @Parameter(type = ItemIO.OUTPUT) - public ImagePlus output; - - @Parameter(type = ItemIO.OUTPUT) + @Parameter(type=ItemIO.OUTPUT) public double alignment; - @Parameter(type = ItemIO.OUTPUT) + @Parameter(type=ItemIO.OUTPUT) public int dimension; + @Parameter(type=ItemIO.OUTPUT) + public ImagePlus maskImage; + + @Parameter(type=ItemIO.OUTPUT) + public ImagePlus hdmImage; + + @Parameter(type=ItemIO.OUTPUT) + public ImagePlus gapImage; + // Magic number declarations private static final double HIGH_CONTRAST_THRESHOLD = 120.0; private static final double LOW_CONTRAST_THRESHOLD = 0.0; @@ -188,16 +194,14 @@ public void run() { ImagePlus maskImage = IJ.openImage(new File(finalMaskImagePath).getAbsolutePath()); this.runOrientationJ(maskImage); - // Close non-essential windows -// this.closeNonImages(); - // Gap analysis maskImage = IJ.openImage(new File(finalMaskImagePath).getAbsolutePath()); this.performGapAnalysis(gapAnalysisDirectory, maskImage); // Output handling -// this.closeNonImages(); - this.output = maskImage; + this.maskImage = maskImage; + this.hdmImage = IJ.openImage(hdmDirectory + File.separator + this.filePrefix + "_hdm.png"); + this.gapImage = IJ.openImage(gapAnalysisDirectory + File.separator + this.filePrefix + "_gap.png"); } private void detectRidges(ImagePlus inputImage, String maskImage) { @@ -317,7 +321,9 @@ private void runAnamorf(String maskImageDirectory) { File propsFile = new File(this.anamorfPropertiesFile); props.loadFromXML(Files.newInputStream(propsFile.toPath())); } - } catch (IOException ex) { + } + + catch (IOException ex) { ex.printStackTrace(); return; } @@ -468,18 +474,20 @@ private void performGapAnalysis(String gapAnalysisDirectory, ImagePlus maskImage File individualGapAnalysisFile = new File(individualGapAnalysisFilePath); try (BufferedWriter bw = new BufferedWriter(new FileWriter(individualGapAnalysisFile))) { bw.write(this.filePrefix + " " + mean + " " + standardDeviation + " " + fivePercentile + " " + fiftyPercentile + " " + ninetyFivePercentile); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } // Write the array to file - String individualGapAnalysisArrayFilePath = gapAnalysisDirectory + File.separator + this.filePrefix + "_area_arrays.csv"; + String individualGapAnalysisArrayFilePath = gapAnalysisDirectory + File.separator + this.filePrefix + "_area_arrays.csv"; File individualGapAnalysisArrayFile = new File(individualGapAnalysisArrayFilePath); try (BufferedWriter bw = new BufferedWriter(new FileWriter(individualGapAnalysisArrayFile))) { for (double area : areas) { bw.write(area + "\n"); } - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } WindowManager.setTempCurrentImage(null); diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIWindow.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIWindow.java index f525e07..9736bcd 100644 --- a/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIWindow.java +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/TWOMBLIWindow.java @@ -5,6 +5,8 @@ import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -23,15 +25,16 @@ import ij.ImagePlus; import ij.gui.ImageCanvas; import ij.gui.StackWindow; +import ij.process.ImageProcessor; import org.scijava.command.CommandModule; // Logservice integration -public class TWOMBLIWindow extends StackWindow { +public class TWOMBLIWindow extends StackWindow implements ProgressCancelListener { private static final double MAX_MAGNIFICATION = 32.0; private static final double MIN_MAGNIFICATION = 1/72.0; - private final ImagePlus originalImage; + private ImagePlus originalImage; final TWOMBLIConfigurator plugin; private final JFormattedTextField minimumLineWidthField; private final JFormattedTextField maximumLineWidthField; @@ -46,14 +49,27 @@ public class TWOMBLIWindow extends StackWindow { private final JFormattedTextField gapAnalysisDiameterField; private final JButton anamorfButton; private final JButton infoButton; + private final JButton changePreviewButton; + private final JLabel selectedPreviewPathField; + private final JButton resetViewButton; private final JButton selectOutputButton; private final JLabel selectedOutputField; private final JButton runPreviewButton; - private final JButton revertPreview; +// private final JButton revertPreview; private final JButton selectBatchButton; private JLabel selectedBatchField; private final JButton runButton; + // Preview image controls + private JLabel currentlySelectedLabel; + private Checkbox originalRadioButton; + private Checkbox hdmRadioButton; + private ImagePlus hdmImage; + private Checkbox fibreRadioButton; + private ImagePlus fibreImage; + private Checkbox micRadioButton; + private ImagePlus micImage; + private String outputDirectory; private String anamorfPropertiesFile; private String batchPath; @@ -64,6 +80,7 @@ public class TWOMBLIWindow extends StackWindow { private List finishedFutures = new ArrayList<>(); private int progressBarCurrent; private int progressBarMax;; + private ProgressDialog customProgressBar; /* TODO: UX: @@ -84,6 +101,34 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl FlowLayout panelLayout = new FlowLayout(); panelLayout.setAlignment(FlowLayout.LEFT); + // Info button + this.infoButton = new JButton("Information!"); + this.infoButton.setToolTipText("Get information about TWOMBLI."); + ActionListener infoListener = e -> this.showInfo(); + this.infoButton.addActionListener(infoListener); + + // Change preview button + this.changePreviewButton = new JButton("Change Preview Image"); + this.changePreviewButton.setToolTipText("Change the preview image to a different image."); + ActionListener changePreviewListener = e -> this.changePreviewImage(); + this.changePreviewButton.addActionListener(changePreviewListener); + + // Current preview path + this.selectedPreviewPathField = new JLabel(this.originalImage.getOriginalFileInfo().getFilePath()); + + // Reset View button + this.resetViewButton = new JButton("Reset View"); + this.resetViewButton.setToolTipText("Reset the view to the original image."); + ActionListener resetViewListener = e -> this.resetView(); + this.resetViewButton.addActionListener(resetViewListener); + + // Select output directory button + this.selectOutputButton = new JButton("Select Output Directory (Required!)"); + this.selectOutputButton.setToolTipText("Choose a directory to output all the data. This includes preview data!"); + ActionListener selectOutputListener = e -> this.getOutputDirectory(); + this.selectOutputButton.addActionListener(selectOutputListener); + this.selectedOutputField = new JLabel("No output directory selected."); + // Minimum line width panel JLabel minimLineWidthInfo = new JLabel("Minimum Line Width (px):"); NumberFormat intFormat = NumberFormat.getIntegerInstance(); @@ -115,7 +160,7 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl this.darklinesCheckbox.setToolTipText("Check this box if the lines are darker as opposed to light."); // Minimum branch length - JLabel minimumBranchLengthInfo = new JLabel("Maximum Branch Length (px):"); + JLabel minimumBranchLengthInfo = new JLabel("Minimum Branch Length (px):"); this.minimumBranchLengthField = new JFormattedTextField(intFormat); this.minimumBranchLengthField.setValue(10); JPanel minimumBranchLengthPanel = new JPanel(); @@ -201,30 +246,17 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl ActionListener anamorfListener = e -> this.getAnamorfProperties(); this.anamorfButton.addActionListener(anamorfListener); - // Info button - this.infoButton = new JButton("Information!"); - this.infoButton.setToolTipText("Get information about TWOMBLI."); - ActionListener infoListener = e -> this.showInfo(); - this.infoButton.addActionListener(infoListener); - - // Select output directory button - this.selectOutputButton = new JButton("Select Output Directory (Required!)"); - this.selectOutputButton.setToolTipText("Choose a directory to output all the data. This includes preview data!"); - ActionListener selectOutputListener = e -> this.getOutputDirectory(); - this.selectOutputButton.addActionListener(selectOutputListener); - this.selectedOutputField = new JLabel("No output directory selected."); - // Run Preview button this.runPreviewButton = new JButton("Run Preview"); this.runPreviewButton.setToolTipText("Run TWOMBLI with the current configuration on the preview image."); ActionListener runPreviewListener = e -> this.runPreviewProcess(); this.runPreviewButton.addActionListener(runPreviewListener); - // Revert preview button - this.revertPreview = new JButton("Revert Preview"); - this.revertPreview.setToolTipText("Revert the preview image to the original."); - ActionListener revertPreviewListener = e -> this.revertPreview(); - this.revertPreview.addActionListener(revertPreviewListener); +// // Revert preview button +// this.revertPreview = new JButton("Revert Preview"); +// this.revertPreview.setToolTipText("Revert the preview image to the original."); +// ActionListener revertPreviewListener = e -> this.revertPreview(); +// this.revertPreview.addActionListener(revertPreviewListener); // Select batch button this.selectBatchButton = new JButton("Select Batch"); @@ -313,13 +345,17 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl sidePanelConstraints.gridy = 0; sidePanelConstraints.insets = new Insets(5, 5, 5, 5); - // Configuration panel - sidePanel.add(configPanel, sidePanelConstraints); - // Help button - sidePanelConstraints.gridy++; sidePanel.add(this.infoButton, sidePanelConstraints); + // Change preview button + sidePanelConstraints.gridy++; + sidePanel.add(this.changePreviewButton, sidePanelConstraints); + + // Preview path + sidePanelConstraints.gridy++; + sidePanel.add(this.selectedPreviewPathField, sidePanelConstraints); + // Select Output Directory sidePanelConstraints.gridy++; sidePanel.add(this.selectOutputButton, sidePanelConstraints); @@ -328,13 +364,69 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl sidePanelConstraints.gridy++; sidePanel.add(this.selectedOutputField, sidePanelConstraints); + // Configuration panel + sidePanelConstraints.gridy++; + sidePanel.add(configPanel, sidePanelConstraints); + // Insert run preview button sidePanelConstraints.gridy++; sidePanel.add(this.runPreviewButton, sidePanelConstraints); - // Revert preview button + // Configuration panel + GridBagLayout previewPanelLayout = new GridBagLayout(); + JPanel previewPanel = new JPanel(); + previewPanel.setBorder(BorderFactory.createTitledBorder("Preview Images")); + previewPanel.setLayout(previewPanelLayout); + GridBagConstraints previewPanelConstraints = new GridBagConstraints(); + previewPanelConstraints.anchor = GridBagConstraints.NORTH; + previewPanelConstraints.fill = GridBagConstraints.HORIZONTAL; + previewPanelConstraints.gridwidth = 1; + previewPanelConstraints.gridheight = 1; + previewPanelConstraints.gridx = 0; + previewPanelConstraints.gridy = 0; + previewPanelConstraints.insets = new Insets(5, 5, 5, 5); + + // Selected info + this.currentlySelectedLabel = new JLabel("Currently Selected: None"); + + // Button group + CheckboxGroup radioGroup = new CheckboxGroup(); + + // Original Image + this.originalRadioButton = new Checkbox("Original Image", radioGroup, false); + this.originalRadioButton.addItemListener(e -> this.handleOriginalButtonPressed()); + this.originalRadioButton.setEnabled(false); + previewPanelConstraints.gridy++; + previewPanel.add(this.originalRadioButton, previewPanelConstraints); + + // HDM Image + this.hdmRadioButton = new Checkbox("HDM Image", radioGroup, false); + this.hdmRadioButton.addItemListener(e -> this.handleHDMButtonPressed()); + this.hdmRadioButton.setEnabled(false); + previewPanelConstraints.gridy++; + previewPanel.add(this.hdmRadioButton, previewPanelConstraints); + + // Fibre Overlay + this.fibreRadioButton = new Checkbox("Fibre Overlay", radioGroup, false); + this.fibreRadioButton.addItemListener(e -> this.handleFibreButtonPressed()); + this.fibreRadioButton.setEnabled(false); + previewPanelConstraints.gridy++; + previewPanel.add(this.fibreRadioButton, previewPanelConstraints); + + // MIC + this.micRadioButton = new Checkbox("Gap Analysis", radioGroup, false); + this.micRadioButton.addItemListener(e -> this.handleMICButtonPressed()); + this.micRadioButton.setEnabled(false); + previewPanelConstraints.gridy++; + previewPanel.add(this.micRadioButton, previewPanelConstraints); + + // Preview panel sidePanelConstraints.gridy++; - sidePanel.add(this.revertPreview, sidePanelConstraints); + sidePanel.add(previewPanel, sidePanelConstraints); + +// // Revert preview button +// sidePanelConstraints.gridy++; +// sidePanel.add(this.revertPreview, sidePanelConstraints); // Select Batch sidePanelConstraints.gridy++; @@ -394,6 +486,83 @@ public TWOMBLIWindow(TWOMBLIConfigurator plugin, ImagePlus previewImage, ImagePl this.setMinimumSize(this.getPreferredSize()); } + private void handleOriginalButtonPressed() { + this.currentlySelectedLabel.setText("Currently Selected: Original Image"); + ImagePlus preview; + if (this.originalImage.getImageStackSize() == 1) { + preview = ImageUtils.duplicateImage(this.originalImage); + } + + // Only take the 'currently selected' file for our preview. + else { + preview = this.originalImage.crop(); + } + + this.setImage(preview); + } + + private void handleHDMButtonPressed() { + this.currentlySelectedLabel.setText("Currently Selected: HDM Image"); + ImagePlus preview; + if (this.hdmImage.getImageStackSize() == 1) { + preview = ImageUtils.duplicateImage(this.hdmImage); + } + else { + preview = this.hdmImage.crop(); + } + this.setImage(preview); + } + + private void handleFibreButtonPressed() { + this.currentlySelectedLabel.setText("Currently Selected: Fibre Overlay"); + ImagePlus base; + if (this.originalImage.getImageStackSize() == 1) { + base = ImageUtils.duplicateImage(this.originalImage); + } + else { + base = this.originalImage.crop(); + } + + ImagePlus overlay; + if (this.fibreImage.getImageStackSize() == 1) { + overlay = ImageUtils.duplicateImage(this.fibreImage); + } + else { + overlay = this.fibreImage.crop(); + } + + // Get the image processors + ImageProcessor baseProcessor = base.getProcessor(); + ImageProcessor overlayProcessor = overlay.getProcessor(); + + // Overlay the images + for (int y = 0; y < base.getHeight(); y++) { + for (int x = 0; x < base.getWidth(); x++) { + int maskPixel = overlayProcessor.get(x, y); + if (maskPixel != 0) { + continue; + } + + baseProcessor.putPixel(x, y, 16711680); + } + } + + base.updateAndDraw(); + this.setImage(base); + } + + private void handleMICButtonPressed() { + this.currentlySelectedLabel.setText("Currently Selected: MIC Overlay"); + ImagePlus preview; + if (this.micImage.getImageStackSize() == 1) { + preview = ImageUtils.duplicateImage(this.micImage); + } + else { + preview = this.micImage.crop(); + } + this.setImage(preview); + } + @Override public void windowClosing(WindowEvent e) { super.windowClosing(e); @@ -413,7 +582,7 @@ private void toggleOutputAvailableInteractions(boolean state) { this.gapAnalysisDiameterField.setEnabled(state); this.anamorfButton.setEnabled(state); this.runPreviewButton.setEnabled(state); - this.revertPreview.setEnabled(state); +// this.revertPreview.setEnabled(state); this.selectBatchButton.setEnabled(state); this.toggleRunButton(state); } @@ -476,6 +645,22 @@ private void showInfo() { IJ.showMessage(info); } + private void changePreviewImage() { + ImagePlus newPreview = IJ.openImage(); + if (newPreview == null) { + return; + } + + this.setImage(newPreview); + this.originalImage = newPreview; + this.selectedPreviewPathField.setText(newPreview.getOriginalFileInfo().getFilePath()); + } + + private void resetView() { + this.setImage(this.originalImage); + this.zoomImage(); + } + private void getOutputDirectory() { String potential = IJ.getDirectory("Get output directory"); if (!Files.isDirectory(Paths.get(potential))) { @@ -558,7 +743,7 @@ private HashMap getInputs() { private void runPreviewProcess() { this.preparePreview(); - this.startProcessing(); + this.startProcessing(true); } private void preparePreview() { @@ -594,7 +779,7 @@ private void revertPreview() { } private void runProcess() { - this.preparePreview(); +// this.preparePreview(); // Skip if we don't have a batch path if (this.batchPath == null) { @@ -620,10 +805,10 @@ private void runProcess() { this.processQueue.add(img); } - this.startProcessing(); + this.startProcessing(false); } - private void startProcessing() { + private void startProcessing(boolean hasPreview) { this.inputs = this.getInputs(); // Empty our output directory (which should only contain previous run data) @@ -638,12 +823,17 @@ private void startProcessing() { this.progressBarMax = this.processQueue.size(); IJ.showMessage("Processing Images. This may take a while. (Press OK to start.)"); IJ.showProgress(this.progressBarCurrent, this.progressBarMax); + this.customProgressBar = new ProgressDialog(IJ.getInstance(), "Processing Images", this.progressBarMax, this); // Process our first image - this.processNext(true); + this.processNext(hasPreview); } private void processNext(boolean isPreview) { + if (this.processQueue.isEmpty()) { + return; + } + ImagePlus img = this.processQueue.remove(); this.inputs.put("img", img); Future future = this.plugin.commandService.run(TWOMBLIRunner.class, false, inputs); @@ -665,8 +855,14 @@ public void handleFutureComplete(Future future, boolean isPreview // Update our preview image with our output if (isPreview) { CommandModule output = future.get(); - this.setImage((ImagePlus) output.getOutput("output")); - this.imp.setTitle("TWOMBLI Preview"); + this.originalRadioButton.setEnabled(true); + this.hdmRadioButton.setEnabled(true); + this.hdmImage = (ImagePlus) output.getOutput("hdmImage"); + this.fibreRadioButton.setEnabled(true); + this.fibreImage = (ImagePlus) output.getOutput("maskImage"); + this.micRadioButton.setEnabled(true); + this.micImage = (ImagePlus) output.getOutput("gapImage"); + this.handleFibreButtonPressed(); } // Store our output for collation @@ -680,15 +876,17 @@ public void handleFutureComplete(Future future, boolean isPreview } // Check if we have more to process. - if (!this.processQueue.isEmpty()) { + if (this.progressBarCurrent + 1 != this.progressBarMax) { this.progressBarCurrent += 1; IJ.showProgress(this.progressBarCurrent, this.progressBarMax); + this.customProgressBar.updateProgress(this.progressBarCurrent); this.processNext(false); } // Restore our gui functionality & close progress bars else { IJ.showProgress(1, 1); + this.customProgressBar.dispose(); this.generateSummaries(); // Reopen us because someone closed everything with a hammer? @@ -706,49 +904,17 @@ private void generateSummaries() { // Gather our basic info String filePrefix = (String) output.getInput("filePrefix"); double alignment = (double) output.getOutput("alignment"); - double dimension = (int) output.getOutput("dimension"); + int dimension = (int) output.getOutput("dimension"); Path anamorfSummaryPath = Paths.get(this.outputDirectory, "masks", filePrefix + "_results.csv"); Path hdmSummaryPath = Paths.get(this.outputDirectory, "hdm_csvs", filePrefix + "_ResultsHDM.csv"); Path gapAnalysisPath = Paths.get(this.outputDirectory, "gap_analysis", filePrefix + "_gaps.csv"); - - // Write to our twombli summary - try { - List lines = new ArrayList<>(); - List anamorfEntries = Files.readAllLines(anamorfSummaryPath); - List hdmEntries = Files.readAllLines(hdmSummaryPath); - - // Conditionally write out our header - if (doHeader) { - String headerItems = anamorfEntries.get(0); - String[] hdmHeaderItems = hdmEntries.get(0).split(","); - String header = headerItems + "," + hdmHeaderItems[hdmHeaderItems.length - 1] + ",Alignment (Coherency [%]),Size"; - lines.add(header); - doHeader = false; - } - - // Get the data - String anamorfData = anamorfEntries.get(anamorfEntries.size() - 1); - String[] hdmData = hdmEntries.get(hdmEntries.size() - 1).split(","); - double hdmValue = 1 - Double.parseDouble(hdmData[hdmData.length - 1]); - lines.add(anamorfData + "," + hdmValue + "," + alignment + "," + dimension); - - // Write - Files.write(twombliOutputPath, lines, StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } - - catch (IOException e) { - throw new RuntimeException(e); - } - - // Write to our gap analysis summary - if (this.gapAnalysisCheckbox.isSelected()) { - try { - List lines = Files.readAllLines(gapAnalysisPath); - Files.write(gapsOutputPath, lines, StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + Outputs.generateSummaries(twombliOutputPath, alignment, dimension, anamorfSummaryPath, hdmSummaryPath, gapAnalysisPath, doHeader, this.gapAnalysisCheckbox.isSelected(), gapsOutputPath); + doHeader = false; } } + + @Override + public void handleProgressBarCancelled() { + this.processQueue.clear(); + } } diff --git a/src/main/java/uk/ac/franciscrickinstitute/twombli/YesNoDialog.java b/src/main/java/uk/ac/franciscrickinstitute/twombli/YesNoDialog.java new file mode 100644 index 0000000..96f1a1b --- /dev/null +++ b/src/main/java/uk/ac/franciscrickinstitute/twombli/YesNoDialog.java @@ -0,0 +1,130 @@ +package uk.ac.franciscrickinstitute.twombli; + +import java.awt.*; +import java.awt.event.*; + +import ij.IJ; +import ij.Prefs; +import ij.gui.GUI; +import ij.gui.MultiLineLabel; + +public class YesNoDialog extends Dialog implements ActionListener, KeyListener, WindowListener { + private Button yesB, noB; + private boolean cancelPressed, yesPressed; + private boolean firstPaint = true; + + public YesNoDialog(Frame parent, String title, String msg) { + this(parent, title, msg, " Yes ", " No "); + } + + public YesNoDialog(Frame parent, String title, String msg, String yesLabel, String noLabel) { + super(parent, title, true); + setLayout(new BorderLayout()); + Panel panel = new Panel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 10)); + MultiLineLabel message = new MultiLineLabel(msg); + message.setFont(new Font("Dialog", Font.PLAIN, 14)); + panel.add(message); + add("North", panel); + + panel = new Panel(); + panel.setLayout(new FlowLayout(FlowLayout.RIGHT, 15, 8)); + if (msg.startsWith("Save")) { + yesB = new Button(" Save "); + noB = new Button("Don't Save"); + } else { + yesB = new Button(yesLabel); + noB = new Button(noLabel); + } + yesB.addActionListener(this); + noB.addActionListener(this); + yesB.addKeyListener(this); + noB.addKeyListener(this); + if (IJ.isWindows() || Prefs.dialogCancelButtonOnRight) { + panel.add(yesB); + panel.add(noB); + } else { + panel.add(noB); + panel.add(yesB); + } + if (IJ.isMacintosh()) + setResizable(false); + add("South", panel); + addWindowListener(this); + GUI.scale(this); + pack(); + yesB.requestFocusInWindow(); + GUI.centerOnImageJScreen(this); + show(); + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource()==yesB) + yesPressed = true; + closeDialog(); + } + + /** Returns true if the user dismissed dialog by pressing "Cancel". */ + public boolean cancelPressed() { + return cancelPressed; + } + + /** Returns true if the user dismissed dialog by pressing "Yes". */ + public boolean yesPressed() { + return yesPressed; + } + + void closeDialog() { + dispose(); + } + + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + IJ.setKeyDown(keyCode); + if (keyCode==KeyEvent.VK_ENTER) { + if (noB.isFocusOwner()) { + closeDialog(); + } else { + yesPressed = true; + closeDialog(); + } + } else if (keyCode==KeyEvent.VK_Y||keyCode==KeyEvent.VK_S) { + yesPressed = true; + closeDialog(); + } else if (keyCode==KeyEvent.VK_N || keyCode==KeyEvent.VK_D) { + closeDialog(); + } else if (keyCode==KeyEvent.VK_ESCAPE||keyCode==KeyEvent.VK_C) { + cancelPressed = true; + closeDialog(); + IJ.resetEscape(); + } + } + + public void keyReleased(KeyEvent e) { + int keyCode = e.getKeyCode(); + IJ.setKeyUp(keyCode); + } + + public void keyTyped(KeyEvent e) {} + + public void paint(Graphics g) { + super.paint(g); + if (firstPaint) { + yesB.requestFocus(); + firstPaint = false; + } + } + + public void windowClosing(WindowEvent e) { + cancelPressed = true; + closeDialog(); + } + + public void windowActivated(WindowEvent e) {} + public void windowOpened(WindowEvent e) {} + public void windowClosed(WindowEvent e) {} + public void windowIconified(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {} + public void windowDeactivated(WindowEvent e) {} + +} diff --git a/src/main/resources/Quant_Black_Space.ijm b/src/main/resources/Quant_Black_Space.ijm index 578114f..837df43 100644 --- a/src/main/resources/Quant_Black_Space.ijm +++ b/src/main/resources/Quant_Black_Space.ijm @@ -41,7 +41,7 @@ macro "Quant Black Space"{ } setResult("Image Name", nResults(), file); setResult("Series", nResults() - 1, s); - setResult("% Black", nResults() - 1, minZero / imageSize); + setResult("% HDM", nResults() - 1, minZero / imageSize); } //Ext.close(); From 89368f8cf2c897ea18055ce987abeefe99cc3e98 Mon Sep 17 00:00:00 2001 From: JonathanCSmith Date: Fri, 27 Sep 2024 10:30:42 +0100 Subject: [PATCH 2/2] Remove Quant_Black_Space.ijm --- src/main/resources/Quant_Black_Space.ijm | 51 ------------------------ 1 file changed, 51 deletions(-) delete mode 100644 src/main/resources/Quant_Black_Space.ijm diff --git a/src/main/resources/Quant_Black_Space.ijm b/src/main/resources/Quant_Black_Space.ijm deleted file mode 100644 index 837df43..0000000 --- a/src/main/resources/Quant_Black_Space.ijm +++ /dev/null @@ -1,51 +0,0 @@ -// Dave Barry, Francis Crick Institute -// 2018.01.08 -// david.barry@crick.ac.uk - -// Calculates the proportion of black space in all images in a directory. -// Altered by Jon to remove aggresive window closing and to work on a file directly - -macro "Quant Black Space"{ - file = getArgument(); - - run("Bio-Formats Macro Extensions"); - setBatchMode(true); - IJ.log("\nProcessing " + file); - Ext.setId(file); - Ext.getSizeC(sizeC); - Ext.getSeriesCount(sCount); - IJ.log("Number of series: " + sCount); - IJ.log("Number of channels: " + sizeC); - - for(s=1;s<=sCount;s++){ - run("Bio-Formats Importer", "open=[" + file + "] color_mode=Default rois_import=[ROI manager] view=Hyperstack stack_order=XYCZT use_virtual_stack series_" + s); - if(sizeC > 1){ - run("Split Channels"); - } - imageSize = getWidth() * getHeight(); - minZero = imageSize; - titles = getList("image.titles"); - for(j=0; j