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 deleted file mode 100644 index 578114f..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