diff --git a/src/main/java/tracing/InteractiveTracerCanvas.java b/src/main/java/tracing/InteractiveTracerCanvas.java index fed763391..1d661b855 100644 --- a/src/main/java/tracing/InteractiveTracerCanvas.java +++ b/src/main/java/tracing/InteractiveTracerCanvas.java @@ -184,7 +184,8 @@ public void mouseClicked( MouseEvent e ) { int currentState = tracerPlugin.resultsDialog.getState(); if( currentState == NeuriteTracerResultsDialog.LOADING || - currentState == NeuriteTracerResultsDialog.SAVING ) { + currentState == NeuriteTracerResultsDialog.SAVING || + currentState == NeuriteTracerResultsDialog.IMAGE_CLOSED) { // Do nothing diff --git a/src/main/java/tracing/NeuriteTracerResultsDialog.java b/src/main/java/tracing/NeuriteTracerResultsDialog.java index 85dfef75f..1031c7c46 100644 --- a/src/main/java/tracing/NeuriteTracerResultsDialog.java +++ b/src/main/java/tracing/NeuriteTracerResultsDialog.java @@ -28,26 +28,34 @@ package tracing; import sc.fiji.skeletonize3D.Skeletonize3D_; +import sholl.Sholl_Analysis; +import stacks.ThreePanes; import features.SigmaPalette; import ij.IJ; import ij.ImageListener; import ij.ImagePlus; +import ij.Prefs; import ij.WindowManager; import ij.gui.GenericDialog; +import ij.gui.HTMLDialog; +import ij.gui.StackWindow; import ij.gui.WaitForUserDialog; import ij.gui.YesNoCancelDialog; import ij.io.FileInfo; import ij.io.OpenDialog; import ij.io.SaveDialog; import ij.measure.Calibration; +import ij.plugin.frame.RoiManager; import java.awt.BorderLayout; +import java.awt.Checkbox; import java.awt.Color; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.Point; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -60,6 +68,7 @@ import java.io.File; import java.io.IOException; import java.text.DecimalFormat; +import java.util.Vector; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -100,11 +109,18 @@ public class NeuriteTracerResultsDialog protected JMenuItem analyzeSkeletonMenuItem; protected JMenuItem makeLineStackMenuItem; + protected JMenuItem addPathsToOverlayMenuItem; + protected JMenuItem addPathsToManagerMenuItem; protected JMenuItem exportCSVMenuItemAgain; protected JMenuItem sendToTrakEM2; protected JCheckBoxMenuItem mipOverlayMenuItem; protected JCheckBoxMenuItem drawDiametersXYMenuItem; + protected JCheckBoxMenuItem xyCanvasMenuItem; + protected JCheckBoxMenuItem zyCanvasMenuItem; + protected JCheckBoxMenuItem xzCanvasMenuItem; + protected JMenuItem arrangeWindowsMenuItem; + // These are the states that the UI can be in: @@ -121,6 +137,7 @@ public class NeuriteTracerResultsDialog static final int SAVING = 10; static final int LOADING = 11; static final int FITTING_PATHS = 12; + static final int IMAGE_CLOSED = 13; static final String [] stateNames = { "WAITING_TO_START_PATH", "PARTIAL_PATH", @@ -134,7 +151,8 @@ public class NeuriteTracerResultsDialog "WAITING_FOR_SIGMA_CHOICE", "SAVING", "LOADING", - "FITTING_PATHS" }; + "FITTING_PATHS", + "IMAGE CLOSED" }; static final String SEARCHING_STRING = "Searching for path between points..."; @@ -211,10 +229,13 @@ public void run() { // Called when an image is closed @Override - public void imageClosed(ImagePlus imp) { + public void imageClosed(final ImagePlus imp) { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { updateColorImageChoice(); + if (plugin.getImagePlus() == imp) + changeState(NeuriteTracerResultsDialog.IMAGE_CLOSED); } }); } @@ -455,40 +476,46 @@ protected void exitRequested() { plugin.closeAndReset(); } - protected void disableEverything() { + protected void disableImageDependentComponents() { assert SwingUtilities.isEventDispatchThread(); - - fw.setEnabledNone(); - pw.setButtonsEnabled(false); - - statusText.setEnabled(false); + loadMenuItem.setEnabled(false); + loadLabelsMenuItem.setEnabled(false); keepSegment.setEnabled(false); junkSegment.setEnabled(false); cancelSearch.setEnabled(false); completePath.setEnabled(false); cancelPath.setEnabled(false); - editSigma.setEnabled(false); sigmaWizard.setEnabled(false); + preprocess.setEnabled(false); + useTubularGeodesics.setEnabled(false); + } + + protected void disableEverything() { + + assert SwingUtilities.isEventDispatchThread(); + disableImageDependentComponents(); + + fw.setEnabledNone(); + pw.setButtonsEnabled(false); + + statusText.setEnabled(false); viewPathChoice.setEnabled(false); paths3DChoice.setEnabled(false); - preprocess.setEnabled(false); - useTubularGeodesics.setEnabled(false); exportCSVMenuItem.setEnabled(false); exportAllSWCMenuItem.setEnabled(false); exportCSVMenuItemAgain.setEnabled(false); sendToTrakEM2.setEnabled(false); + addPathsToManagerMenuItem.setEnabled(false); + addPathsToOverlayMenuItem.setEnabled(false); analyzeSkeletonMenuItem.setEnabled(false); saveMenuItem.setEnabled(false); - loadMenuItem.setEnabled(false); if( uploadButton != null ) { uploadButton.setEnabled(false); fetchButton.setEnabled(false); } - loadLabelsMenuItem.setEnabled(false); - quitMenuItem.setEnabled(false); } @@ -530,7 +557,9 @@ public void run() { exportCSVMenuItemAgain.setEnabled(true); sendToTrakEM2.setEnabled(plugin.anyListeners()); analyzeSkeletonMenuItem.setEnabled(true); - if( uploadButton != null ) { + addPathsToManagerMenuItem.setEnabled(true); + addPathsToOverlayMenuItem.setEnabled(true); + if (uploadButton != null) { uploadButton.setEnabled(true); fetchButton.setEnabled(true); } @@ -640,6 +669,12 @@ public void run() { disableEverything(); break; + case IMAGE_CLOSED: + updateStatusText("Tracing image is no longer available..."); + disableImageDependentComponents(); + quitMenuItem.setEnabled(true); + break; + default: IJ.error("BUG: switching to an unknown state"); return; @@ -716,7 +751,7 @@ public NeuriteTracerResultsDialog( String title, menuBar.add(helpMenu()); - loadMenuItem = new JMenuItem("Load traces / SWC file..."); + loadMenuItem = new JMenuItem("Load traces / (e)SWC file..."); loadMenuItem.addActionListener(this); fileMenu.add(loadMenuItem); @@ -752,6 +787,17 @@ public NeuriteTracerResultsDialog( String title, makeLineStackMenuItem.addActionListener(this); analysisMenu.add(makeLineStackMenuItem); + analysisMenu.addSeparator(); + addPathsToOverlayMenuItem = new JMenuItem("Add paths to overlay..."); + addPathsToOverlayMenuItem.addActionListener(this); + analysisMenu.add(addPathsToOverlayMenuItem); + addPathsToManagerMenuItem = new JMenuItem("Export paths to ROI Manager"); + addPathsToManagerMenuItem.addActionListener(this); + analysisMenu.add(addPathsToManagerMenuItem); + analysisMenu.addSeparator(); + + analysisMenu.add(shollAnalysisHelpMenuItem()); + analysisMenu.addSeparator(); exportCSVMenuItemAgain = new JMenuItem("Export as CSV..."); exportCSVMenuItemAgain.addActionListener(this); analysisMenu.add(exportCSVMenuItemAgain); @@ -767,6 +813,24 @@ public NeuriteTracerResultsDialog( String title, drawDiametersXYMenuItem.addItemListener(this); viewMenu.add(drawDiametersXYMenuItem); + viewMenu.addSeparator(); + xyCanvasMenuItem = new JCheckBoxMenuItem("Hide XY plane"); + xyCanvasMenuItem.addItemListener(this); + viewMenu.add(xyCanvasMenuItem); + zyCanvasMenuItem = new JCheckBoxMenuItem("Hide ZY plane"); + zyCanvasMenuItem.setEnabled(!plugin.getSinglePane()); + zyCanvasMenuItem.addItemListener(this); + viewMenu.add(zyCanvasMenuItem); + xzCanvasMenuItem = new JCheckBoxMenuItem("Hide XZ plane"); + xzCanvasMenuItem.setEnabled(!plugin.getSinglePane()); + xzCanvasMenuItem.addItemListener(this); + viewMenu.add(xzCanvasMenuItem); + viewMenu.addSeparator(); + arrangeWindowsMenuItem = new JMenuItem("Arrange planes"); + arrangeWindowsMenuItem.setEnabled(!plugin.getSinglePane()); + arrangeWindowsMenuItem.addActionListener(this); + viewMenu.add(arrangeWindowsMenuItem); + setJMenuBar(menuBar); addWindowListener(this); @@ -1053,7 +1117,7 @@ public void actionPerformed( ActionEvent e ) { plugin.uploadTracings(); } else if( source == fetchButton ) { plugin.getTracings( true ); - } else */ if( source == saveMenuItem ) { + } else */ if( source == saveMenuItem && !noPathsError()) { FileInfo info = plugin.file_info; SaveDialog sd; @@ -1127,7 +1191,7 @@ public void actionPerformed( ActionEvent e ) { plugin.loadTracings(); changeState( preLoadingState ); - } else if( source == exportAllSWCMenuItem ) { + } else if( source == exportAllSWCMenuItem && !noPathsError()) { FileInfo info = plugin.file_info; SaveDialog sd; @@ -1164,7 +1228,7 @@ public void actionPerformed( ActionEvent e ) { return; pathAndFillManager.exportAllAsSWC( savePath ); - } else if( source == exportCSVMenuItem || source == exportCSVMenuItemAgain ) { + } else if( (source == exportCSVMenuItem || source == exportCSVMenuItemAgain) && !noPathsError()) { FileInfo info = plugin.file_info; SaveDialog sd; @@ -1261,29 +1325,79 @@ public void actionPerformed( ActionEvent e ) { plugin.loadLabels(); - } else if( source == makeLineStackMenuItem ) { + } else if (source == makeLineStackMenuItem && !noPathsError()) { - if( pathAndFillManager.size() == 0 ) { - IJ.error("There are no paths traced yet - the stack would be empty"); - } else { - ImagePlus imagePlus = plugin.makePathVolume(); - imagePlus.show(); - } + final ImagePlus imagePlus = plugin.makePathVolume(); + imagePlus.show(); + + } else if (source == analyzeSkeletonMenuItem && !noPathsError()) { - } else if( source == analyzeSkeletonMenuItem ) { + final ImagePlus imagePlus = plugin.makePathVolume(); + final Skeletonize3D_ skeletonizer = new Skeletonize3D_(); + skeletonizer.setup("", imagePlus); + skeletonizer.run(imagePlus.getProcessor()); + final AnalyzeSkeleton_ analyzer = new AnalyzeSkeleton_(); + analyzer.setup("", imagePlus); + analyzer.run(imagePlus.getProcessor()); + imagePlus.show(); - if( pathAndFillManager.size() == 0 ) { - IJ.error("There are no paths traced yet!"); + } else if (source == addPathsToOverlayMenuItem && !noPathsError()) { + + if (plugin.getSinglePane()) { + if (currentState == NeuriteTracerResultsDialog.IMAGE_CLOSED) { + IJ.error("Image is no longer available."); + addPathsToOverlayMenuItem.setEnabled(false); + } else + plugin.addPathsToOverlay(); } else { - ImagePlus imagePlus = plugin.makePathVolume(); - Skeletonize3D_ skeletonizer = new Skeletonize3D_(); - skeletonizer.setup("",imagePlus); - skeletonizer.run(imagePlus.getProcessor()); - AnalyzeSkeleton_ analyzer = new AnalyzeSkeleton_(); - analyzer.setup("",imagePlus); - analyzer.run(imagePlus.getProcessor()); - imagePlus.show(); + final InteractiveTracerCanvas[] canvases = { plugin.xy_tracer_canvas, plugin.xz_tracer_canvas, + plugin.zy_tracer_canvas }; + plugin.xy_tracer_canvas.isShowing(); + final int[] planes = { ThreePanes.XY_PLANE, ThreePanes.XZ_PLANE, ThreePanes.ZY_PLANE }; + final String[] options = { "Main (XY) view", "XZ view", "YZ view" }; + final boolean[] choices = new boolean[3]; + for (int i = 0; i < planes.length; i++) + choices[i] = getImagePlusFromPane(planes[i]) != null; + if (!choices[0] && !choices[1] && !choices[2]) { + IJ.error("Tracing panes are no longer available."); + addPathsToOverlayMenuItem.setEnabled(false); + return; + } + final GenericDialog gd = new GenericDialog("Paths to Overlay"); + gd.addCheckboxGroup(3, 1, options, choices, new String[] { "Add ROI paths to the overlay of:" }); + final Vector cbxs = gd.getCheckboxes(); + for (int i = 0; i < choices.length; i++) + ((Checkbox) cbxs.get(i)).setEnabled(choices[i]); + gd.showDialog(); + if (gd.wasCanceled()) + return; + int i = 0; + for (final InteractiveTracerCanvas canvas : canvases) { + if (gd.getNextBoolean() && canvas != null) + plugin.addPathsToOverlay(canvas.getImage(), planes[i]); + i++; + } + } + + } else if (source == addPathsToManagerMenuItem && !noPathsError()) { + + RoiManager rm = RoiManager.getInstance2(); + if (rm == null) + rm = new RoiManager(); + if (plugin.singleSlice) + Prefs.showAllSliceOnly = false; + if (rm.getCount() > 0) { + final YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), "Reset Manager?", + "Delete existing ROIs in the ROI Manager list?"); + if (d.cancelPressed()) + return; + else if (d.yesPressed()) + rm.reset(); } + rm.setEditMode(plugin.getImagePlus(), false); + plugin.addPathsToManager(rm); + rm.setEditMode(plugin.getImagePlus(), true); + rm.runCommand("show all without labels"); } else if( source == cancelSearch ) { @@ -1364,7 +1478,53 @@ public void actionPerformed( ActionEvent e ) { if( ! ignoreColorImageChoiceEvents ) checkForColorImageChange(); + + } else if (source == arrangeWindowsMenuItem) { + arrangeWindows(); + } + } + + private void arrangeWindows() { + final StackWindow xy_window = plugin.getWindow(ThreePanes.XY_PLANE); + if (xy_window == null) + return; + if (!plugin.getSinglePane()) { + final Point loc = xy_window.getLocation(); + final StackWindow zy_window = plugin.getWindow(ThreePanes.ZY_PLANE); + final StackWindow xz_window = plugin.getWindow(ThreePanes.XZ_PLANE); + if (zy_window != null) { + zy_window.setLocation(loc.x + xy_window.getWidth(), loc.y); + zy_window.toFront(); + } + if (xz_window != null) { + xz_window.setLocation(loc.x, loc.y + xy_window.getHeight()); + xz_window.toFront(); + } + } + xy_window.toFront(); + } + + private void toggleWindowVisibility(final int pane, final JCheckBoxMenuItem menuItem, final boolean setVisible) { + if (getImagePlusFromPane(pane) == null) { + IJ.error("Image Closed", "Pane is no longer accessible."); + menuItem.setEnabled(false); + menuItem.setSelected(false); + } else { // NB: WindowManager list won't be notified + plugin.getWindow(pane).setVisible(setVisible); + } + } + + private ImagePlus getImagePlusFromPane(final int pane) { + final StackWindow win = plugin.getWindow(pane); + return (win == null) ? null : win.getImagePlus(); + } + + private boolean noPathsError() { + final boolean noPaths = pathAndFillManager.size() == 0; + if (noPaths) { + IJ.error("Simple Neurite Tracer", "There are no traced paths."); } + return noPaths; } @Override @@ -1468,6 +1628,13 @@ public void itemStateChanged( ItemEvent e ) { } else if( source == drawDiametersXYMenuItem ) { plugin.setDrawDiametersXY(e.getStateChange() == ItemEvent.SELECTED); + + } else if (source == xyCanvasMenuItem && xyCanvasMenuItem.isEnabled()) { + toggleWindowVisibility(ThreePanes.XY_PLANE, xyCanvasMenuItem, e.getStateChange() == ItemEvent.DESELECTED); + } else if (source == zyCanvasMenuItem && zyCanvasMenuItem.isEnabled()) { + toggleWindowVisibility(ThreePanes.ZY_PLANE, zyCanvasMenuItem, e.getStateChange() == ItemEvent.DESELECTED); + } else if (source == xzCanvasMenuItem && xzCanvasMenuItem.isEnabled()) { + toggleWindowVisibility(ThreePanes.XZ_PLANE, xzCanvasMenuItem, e.getStateChange() == ItemEvent.DESELECTED); } } @@ -1534,9 +1701,20 @@ private JMenu helpMenu() { mi = menuItemTrigerringURL("List of shortcuts", URL + ":_Key_Shortcuts"); helpMenu.add(mi); helpMenu.addSeparator(); - mi = menuItemTrigerringURL("Sholl analysis: Online help", URL + ":_Sholl_analysis"); + mi = menuItemTrigerringURL("Sholl analysis walkthrough", URL + ":_Sholl_analysis"); + helpMenu.add(mi); + helpMenu.addSeparator(); + mi = menuItemTrigerringURL("Ask a question", "http://forum.imagej.net"); + helpMenu.add(mi); + helpMenu.addSeparator(); + mi = menuItemTrigerringURL("Citing SNT...", URL + "#Citing_Simple_Neurite_Tracer"); helpMenu.add(mi); - mi = new JMenuItem("Sholl analysis: Offline help"); + return helpMenu; + } + + private JMenuItem shollAnalysisHelpMenuItem() { + JMenuItem mi; + mi = new JMenuItem("Sholl Analysis..."); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { @@ -1545,26 +1723,29 @@ public void actionPerformed(final ActionEvent e) { public void run() { String modKey = IJ.isMacOSX() ? "Alt" : "Ctrl"; modKey += "+Shift"; - final String instructions = "To manually select the center of analysis:\n" - + " 1. Mouse over the path of interest and press \"G\" to activate it\n" - + " 2. Press \"" + modKey + "\" to select a point along the path\n" - + " 3. Press \"" + modKey + "+A\" to initiate Sholl analysis\n \n" - + "For batch processing run \"Sholl Analysis (Tracings)...\"."; - final WaitForUserDialog wd = new WaitForUserDialog("Sholl Analysis Cheat Sheet", instructions); - wd.show(); + final String url1 = Sholl_Analysis.URL + "#Analysis_of_Traced_Cells"; + final String url2 = "http://imagej.net/Simple_Neurite_Tracer/:_Sholl_analysis"; + final StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append("
"); + sb.append("To initiate Sholl Analysis, "); + sb.append("you must first select a focal point:"); + sb.append("
    "); + sb.append("
  1. Mouse over the path of interest. Press \"G\" to activate it
  2. "); + sb.append("
  3. Press \"").append(modKey).append("\" to select a point along the path
  4. "); + sb.append("
  5. Press \"").append(modKey).append("+A\" to start analysis
  6. "); + sb.append("
"); + sb.append("A detailed walkthrough is also available online. "); + sb.append("For batch processing, run Analyze>Sholl>Sholl Analysis (Tracings).... "); + new HTMLDialog("Sholl Analysis How-to", sb.toString(), false); } }); newThread.start(); } }); - helpMenu.add(mi); - helpMenu.addSeparator(); - mi = menuItemTrigerringURL("Ask a question", "http://forum.imagej.net"); - helpMenu.add(mi); - helpMenu.addSeparator(); - mi = menuItemTrigerringURL("Citing SNT...", URL + "#Citing_Simple_Neurite_Tracer"); - helpMenu.add(mi); - return helpMenu; + return mi; } public static JMenuItem menuItemTrigerringURL(final String label, final String URL) { diff --git a/src/main/java/tracing/Path.java b/src/main/java/tracing/Path.java index b9cfdb8b3..bfc7fd3cb 100644 --- a/src/main/java/tracing/Path.java +++ b/src/main/java/tracing/Path.java @@ -30,8 +30,12 @@ import ij.IJ; import ij.ImagePlus; import ij.ImageStack; +import ij.gui.Overlay; +import ij.gui.PolygonRoi; +import ij.gui.Roi; import ij.gui.StackWindow; import ij.measure.Calibration; +import ij.process.FloatPolygon; import ij.process.FloatProcessor; import ij3d.Content; import ij3d.Image3DUniverse; @@ -833,6 +837,76 @@ public void drawPathAsPoints( TracerCanvas canvas, Graphics g, java.awt.Color c, } } + public void drawPathAsPoints(final Overlay overlay, final java.awt.Color c) { + drawPathAsPoints(overlay, c, ThreePanes.XY_PLANE); + } + + public void drawPathAsPoints(final Overlay overlay, final java.awt.Color c, final int plane) { + + overlay.setStrokeColor(c); + FloatPolygon polygon = new FloatPolygon(); + int current_roi_slice = Integer.MIN_VALUE; + int roi_identifier = 1; + + for (int i = 0; i < points; ++i) { + + double x = Integer.MIN_VALUE; + double y = Integer.MIN_VALUE; + int slice_of_point = Integer.MIN_VALUE; + + switch (plane) { + case ThreePanes.XY_PLANE: + x = getXUnscaledDouble(i); + y = getYUnscaledDouble(i); + slice_of_point = getZUnscaled(i); + break; + case ThreePanes.XZ_PLANE: + x = getXUnscaledDouble(i); + y = getZUnscaledDouble(i); + slice_of_point = getYUnscaled(i); + break; + case ThreePanes.ZY_PLANE: + x = getZUnscaledDouble(i); + y = getYUnscaledDouble(i); + slice_of_point = getXUnscaled(i); + break; + default: + throw new RuntimeException("BUG: Unknown plane! (" + plane + ")"); + } + + if (current_roi_slice == slice_of_point || i == 0) { + polygon.addPoint(x, y); + } else { + addPolyLineToOverlay(polygon, current_roi_slice, roi_identifier++, overlay); + polygon = new FloatPolygon(); // reset ROI + polygon.addPoint(x, y); + } + current_roi_slice = slice_of_point; + + } + + // Create ROI from any remaining points + addPolyLineToOverlay(polygon, current_roi_slice, roi_identifier, overlay); + + } + + private void addPolyLineToOverlay(final FloatPolygon p, final int z_position, final int roi_id, + final Overlay overlay) { + if (p.npoints > 0) { + if (p.npoints == 1) { + // create 1-pixel length lines for single points + p.xpoints[0] -= 0.5f; + p.ypoints[0] -= 0.5f; + p.addPoint(p.xpoints[0] + 0.5f, p.ypoints[0] + 0.5f); + } + final PolygonRoi polyline = new PolygonRoi(p, Roi.FREELINE); + polyline.enableSubPixelResolution(); + // polyline.fitSplineForStraightening(); + polyline.setName(String.format("Path%04d-%04d-Z%d", id, roi_id, z_position)); + polyline.setPosition(z_position + 1); // index 1 + overlay.add(polyline); + } + } public int indexNearestTo( double x, double y, double z ) { diff --git a/src/main/java/tracing/PathAndFillManager.java b/src/main/java/tracing/PathAndFillManager.java index 7c5039c3c..4edcf3929 100644 --- a/src/main/java/tracing/PathAndFillManager.java +++ b/src/main/java/tracing/PathAndFillManager.java @@ -1670,7 +1670,7 @@ public boolean importSWC( BufferedReader br, boolean assumeCoordinatesIndexVoxel if( mEmpty.matches() ) continue; String [] fields = line.split("\\s+"); - if( fields.length != 7 ) { + if( fields.length < 7 ) { IJ.error("Wrong number of fields ("+fields.length+") in line: "+line); return false; } diff --git a/src/main/java/tracing/PathWindow.java b/src/main/java/tracing/PathWindow.java index 532328d5e..7934d1444 100644 --- a/src/main/java/tracing/PathWindow.java +++ b/src/main/java/tracing/PathWindow.java @@ -32,6 +32,8 @@ import ij.io.SaveDialog; import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -61,6 +63,7 @@ import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; @@ -525,7 +528,7 @@ public PathWindow(PathAndFillManager pathAndFillManager, SimpleNeuriteTracer plu this.pathAndFillManager = pathAndFillManager; this.plugin = plugin; - setBounds(x,y,700,300); + setBounds(x,y,600,240); root = new DefaultMutableTreeNode("All Paths"); tree = new HelpfulJTree(root); // tree.setRootVisible(false); @@ -535,6 +538,7 @@ public PathWindow(PathAndFillManager pathAndFillManager, SimpleNeuriteTracer plu add(scrollPane, BorderLayout.CENTER); buttonPanel = new JPanel(); + buttonPanel.setBorder(new EmptyBorder(0, 0, 0, 0)); add(buttonPanel, BorderLayout.PAGE_END); @@ -577,13 +581,12 @@ public PathWindow(PathAndFillManager pathAndFillManager, SimpleNeuriteTracer plu popup.add(swcTypeMenu); // Create all the menu items: - - renameButton = new JButton("Rename"); - fitVolumeButton = new JButton("Fit Volume"); - fillOutButton = new JButton("Fill Out"); - makePrimaryButton = new JButton("Make Primary"); - deleteButton = new JButton("Delete"); - exportAsSWCButton = new JButton("Export as SWC"); + renameButton = smallButton("Rename"); + fitVolumeButton = smallButton("Fit Volume"); + fillOutButton = smallButton("Fill Out"); + makePrimaryButton = smallButton("Make Primary"); + deleteButton = smallButton("Delete"); + exportAsSWCButton = smallButton("Export as SWC"); buttonPanel.add(renameButton); buttonPanel.add(fitVolumeButton); @@ -616,6 +619,17 @@ protected void maybeShowPopup(MouseEvent me) { tree.addMouseListener(ml); } + private JButton smallButton(final String text) { + final double SCALE = .85; + final JButton button = new JButton(text); + final Font font = button.getFont(); + button.setFont(font.deriveFont((float) (font.getSize() * SCALE))); + final Insets insets = button.getMargin(); + button.setMargin(new Insets((int) (insets.top * SCALE), (int) (insets.left * SCALE), + (int) (insets.bottom * SCALE), (int) (insets.right * SCALE))); + return button; + } + protected void showPopup(MouseEvent me) { assert SwingUtilities.isEventDispatchThread(); // Possibly adjust the selection here: diff --git a/src/main/java/tracing/ShollAnalysisPlugin.java b/src/main/java/tracing/ShollAnalysisPlugin.java index 080a0fbd0..b1e394b2c 100644 --- a/src/main/java/tracing/ShollAnalysisPlugin.java +++ b/src/main/java/tracing/ShollAnalysisPlugin.java @@ -106,7 +106,7 @@ public void run(final String ignoredArgument) { imp = (impRequired) ? IJ.openImage(imgPath) : null; if (impRequired && imp == null || !validTracesFile(new File(tracesPath))) { - IJ.error("Invalid image or invalid Traces/SWC file\n \n" + imgPath + "\n" + tracesPath); + IJ.error("Invalid image or invalid Traces/(e)SWC file\n \n" + imgPath + "\n" + tracesPath); return; } @@ -254,7 +254,7 @@ private boolean showDialog() { guessInitialPaths(); gd = new EnhancedGenericDialog("Sholll Analysis (Tracings)..."); - gd.addFileField("Traces/SWC file", tracesPath, 32); + gd.addFileField("Traces/(e)SWC file", tracesPath, 32); gd.addFileField("Image file", imgPath, 32); gd.setInsets(0, 40, 20); gd.addCheckbox("Load tracings without image", !impRequired); @@ -365,7 +365,7 @@ public boolean dialogItemChanged(final GenericDialog arg0, final AWTEvent event) } if (!validTracesFile(new File(tracesPath))) { enableOK = false; - warning += "Not a valid .traces/.swc file"; + warning += "Not a valid .traces/.(e)swc file"; } if (!warning.isEmpty()) { infoMsg.setForeground(Utils.warningColor()); @@ -426,7 +426,7 @@ private boolean expectedImageFile(final File file) { } private boolean tracingsFile(final File file) { - final String[] tracingsExts = new String[] { ".traces", ".swc" }; + final String[] tracingsExts = new String[] { ".traces", ".swc", ".eswc" }; for (final String ext : tracingsExts) if (file.getName().toLowerCase().endsWith(ext)) return true; diff --git a/src/main/java/tracing/SimpleNeuriteTracer.java b/src/main/java/tracing/SimpleNeuriteTracer.java index b0cf7cded..aa858ff76 100644 --- a/src/main/java/tracing/SimpleNeuriteTracer.java +++ b/src/main/java/tracing/SimpleNeuriteTracer.java @@ -41,10 +41,12 @@ import ij.gui.ImageRoi; import ij.gui.Overlay; import ij.gui.Roi; +import ij.gui.StackWindow; import ij.gui.YesNoCancelDialog; import ij.io.FileInfo; import ij.io.OpenDialog; import ij.plugin.ZProjector; +import ij.plugin.frame.RoiManager; import ij.process.ByteProcessor; import ij.text.TextWindow; import ij3d.Content; @@ -58,6 +60,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -489,7 +492,7 @@ synchronized public void loadTracings( ) { OpenDialog od; - od = new OpenDialog("Select .traces or .swc file...", + od = new OpenDialog("Select .traces or .(e)swc file...", directory, null ); @@ -1458,6 +1461,84 @@ public void setShowOnlySelectedPaths(boolean showOnlySelectedPaths) { repaintAllPanes(); } + public void addPathsToManager(final RoiManager rm) { + if (rm != null) { + final Overlay overlay = new Overlay(); + addAllPathsToOverlay(overlay); + for (final Roi path : overlay.toArray()) + rm.addRoi(path); + rm.runCommand("sort"); + } + } + + public void addAllPathsToOverlay(final Overlay overlay) { + if (overlay != null && pathAndFillManager != null) { + for (int i = 0; i < pathAndFillManager.size(); ++i) { + final Path p = pathAndFillManager.getPath(i); + if (p == null) + continue; + if (p.fittedVersionOf != null) + continue; + // Prefer fitted version when drawing path + final Path drawPath = (p.useFitted) ? p.fitted : p; + drawPath.drawPathAsPoints(overlay, deselectedColor); + } + } + } + + public void addPathsToOverlay() { + if (xy != null) + addPathsToOverlay(xy, ThreePanes.XY_PLANE); + } + + public void addPathsToOverlay(final ImagePlus imp, final int plane) { + + Overlay overlay = imp.getOverlay(); + if (overlay == null) + overlay = new Overlay(); + + if (pathAndFillManager != null) { + for (int i = 0; i < pathAndFillManager.size(); ++i) { + + final Path p = pathAndFillManager.getPath(i); + if (p == null) + continue; + + if (p.fittedVersionOf != null) + continue; + + // If the path suggests using the fitted version, draw that + // instead + final Path drawPath = (p.useFitted) ? p.fitted : p; + + Color color = deselectedColor; + if (pathAndFillManager.isSelected(p)) + color = selectedColor; + else if (showOnlySelectedPaths) + continue; + drawPath.drawPathAsPoints(overlay, color, plane); + } + imp.setOverlay(overlay); + } + } + + public StackWindow getWindow(final int plane) { + switch (plane) { + case ThreePanes.XY_PLANE: + return xy_window; + case ThreePanes.XZ_PLANE: + return (single_pane) ? null : xz_window; + case ThreePanes.ZY_PLANE: + return (single_pane) ? null : zy_window; + default: + return null; + } + } + + public boolean getSinglePane() { + return single_pane; + } + public boolean getShowOnlySelectedPaths() { return showOnlySelectedPaths; } @@ -1610,6 +1691,7 @@ public void clickAtMaxPoint( int x_in_pane, int y_in_pane, int plane ) { } public static final int OVERLAY_OPACITY_PERCENT = 20; + private static final String OVERLAY_IDENTIFIER = "SNT-MIP-OVERLAY"; public void showMIPOverlays(boolean show) { ArrayList allImages = new ArrayList(); @@ -1619,6 +1701,9 @@ public void showMIPOverlays(boolean show) { allImages.add(zy); } for( ImagePlus imagePlus : allImages ) { + if (imagePlus == null || imagePlus.getImageStackSize() == 1) + continue; + Overlay overlayList = imagePlus.getOverlay(); if( show ) { // Create a MIP project of the stack: @@ -1631,16 +1716,27 @@ public void showMIPOverlays(boolean show) { // Add display it as an overlay. // (This logic is taken from OverlayCommands.) Roi roi = new ImageRoi(0, 0, overlay.getProcessor()); - roi.setName(overlay.getShortTitle()); + roi.setName(OVERLAY_IDENTIFIER); ((ImageRoi)roi).setOpacity(OVERLAY_OPACITY_PERCENT/100.0); - Overlay overlayList = imagePlus.getOverlay(); if (overlayList==null) overlayList = new Overlay(); overlayList.add(roi); - imagePlus.setOverlay(overlayList); } else { - imagePlus.setOverlay(null); + removeMIPfromOverlay(overlayList); + } + imagePlus.setOverlay(overlayList); + } + } + + private void removeMIPfromOverlay(Overlay overlay) { + if (overlay != null && overlay.size() > 0) { + for (int i = overlay.size() - 1; i >= 0; i--) { + final String roiName = overlay.get(i).getName(); + if (roiName != null && roiName.equals(OVERLAY_IDENTIFIER)) { + overlay.remove(i); + return; + } } } } @@ -1657,4 +1753,33 @@ public boolean getDrawDiametersXY() { return drawDiametersXY; } + @Override + public void closeAndReset() { + // Dispose xz/zy images unless the user stored some annotations (ROIs) + // on the image overlay or modified them somehow. In that case, restore + // them to the user + if (!single_pane) { + final ImagePlus[] impPanes = { xz, zy }; + final StackWindow[] winPanes = { xz_window, zy_window }; + for (int i = 0; i < impPanes.length; ++i) { + final Overlay overlay = impPanes[i].getOverlay(); + removeMIPfromOverlay(overlay); + if (!impPanes[i].changes && (overlay == null || impPanes[i].getOverlay().size() == 0)) + impPanes[i].close(); + else { + winPanes[i] = new StackWindow(impPanes[i]); + removeMIPfromOverlay(overlay); + impPanes[i].setOverlay(overlay); + } + } + } + // Restore main view + final Overlay overlay = (xy == null) ? null : xy.getOverlay(); + if (original_xy_canvas != null && xy != null && xy.getImage() != null) { + xy_window = new StackWindow(xy, original_xy_canvas); + removeMIPfromOverlay(overlay); + xy.setOverlay(overlay); + } + } + } diff --git a/src/main/java/tracing/Simple_Neurite_Tracer.java b/src/main/java/tracing/Simple_Neurite_Tracer.java index 414f271f0..416038fe0 100644 --- a/src/main/java/tracing/Simple_Neurite_Tracer.java +++ b/src/main/java/tracing/Simple_Neurite_Tracer.java @@ -34,6 +34,7 @@ import ij.Macro; import ij.gui.GUI; import ij.gui.GenericDialog; +import ij.gui.Overlay; import ij.gui.YesNoCancelDialog; import ij.measure.Calibration; import ij.plugin.PlugIn; @@ -330,7 +331,9 @@ else if( d.yesPressed() ) { } } + final Overlay currentImageOverlay = currentImage.getOverlay(); initialize(currentImage); + xy.setOverlay(currentImageOverlay); xy_tracer_canvas = (InteractiveTracerCanvas)xy_canvas; xz_tracer_canvas = (InteractiveTracerCanvas)xz_canvas;