/* -*- mode: java; c-basic-offset: 8; indent-tabs-mode: t; tab-width: 8 -*- */ /* Copyright 2006, 2007, 2008, 2009 Mark Longair */ /* This file is part of the ImageJ plugin "Simple Neurite Tracer". The ImageJ plugin "Simple Neurite Tracer" is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. The ImageJ plugin "Simple Neurite Tracer" is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package tracing; import ij.*; import ij.process.*; import ij.gui.*; import ij.plugin.*; import ij.plugin.filter.*; import ij.text.*; import ij.measure.Calibration; import ij.io.*; import ij3d.Image3DUniverse; import ij3d.Image3DMenubar; import ij3d.Content; import ij3d.Pipe; import ij3d.Mesh_Maker; import javax.vecmath.Color3f; import javax.vecmath.Point3f; import ij.gui.GUI; import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.awt.image.IndexColorModel; import java.io.*; import java.util.Set; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import client.ArchiveClient; import stacks.ThreePanes; import util.BatchOpener; import util.RGB_to_Luminance; import features.GaussianGenerationCallback; import features.ComputeCurvatures; import amira.AmiraMeshDecoder; import amira.AmiraParameters; import features.Sigma_Palette; import features.TubenessProcessor; /* Note on terminology: "traces" files are made up of "paths". Paths are non-branching sequences of adjacent points (including diagonals) in the image. Branches and joins are supported by attributes of paths that specify that they begin on (or end on) other paths. */ public class Simple_Neurite_Tracer extends SimpleNeuriteTracer implements PlugIn { public void run( String ignoredArguments ) { /* The useful macro options are: imagefilename= tracesfilename= use_3d use_three_pane */ String macroOptions = Macro.getOptions(); String macroImageFilename = null; String macroTracesFilename = null; if( macroOptions != null ) { macroImageFilename = Macro.getValue( macroOptions, "imagefilename", null ); macroTracesFilename = Macro.getValue( macroOptions, "tracesfilename", null ); } Applet applet = IJ.getApplet(); if( applet != null ) { archiveClient = new ArchiveClient( applet, macroOptions ); } if( archiveClient != null ) archiveClient.closeChannelsWithTag("nc82"); try { ImagePlus currentImage = null; if( macroImageFilename == null ) { currentImage = IJ.getImage(); } else { currentImage = BatchOpener.openFirstChannel( macroImageFilename ); if( currentImage == null ) { IJ.error("Opening the image file specified in the macro parameters ("+macroImageFilename+") failed."); return; } currentImage.show(); } if( currentImage == null ) { IJ.error( "There's no current image to trace." ); return; } // Check this isn't a composite image or hyperstack: if( currentImage.getNFrames() > 1 ) { IJ.error("This plugin only works with single images, not multiple images in a time series."); return; } if( currentImage.getNChannels() > 1 ) { IJ.error("This plugin only works with single channel images: use 'Image>Color>Split Channels' and choose a channel"); return; } imageType = currentImage.getType(); if( imageType == ImagePlus.COLOR_RGB ) { YesNoCancelDialog queryRGB = new YesNoCancelDialog( IJ.getInstance(), "Convert RGB image", "Convert this RGB image to an 8 bit luminance image first?\n" + "(If you want to trace a particular channel instead, cancel and \"Split Channels\" first.)" ); if( ! queryRGB.yesPressed() ) { return; } currentImage = RGB_to_Luminance.convertToLuminance(currentImage); currentImage.show(); imageType = currentImage.getType(); } if( currentImage.getStackSize() == 1 ) singleSlice = true; width = currentImage.getWidth(); height = currentImage.getHeight(); depth = currentImage.getStackSize(); Calibration calibration = currentImage.getCalibration(); if( calibration != null ) { x_spacing = calibration.pixelWidth; y_spacing = calibration.pixelHeight; z_spacing = calibration.pixelDepth; spacing_units = calibration.getUnits(); if( spacing_units == null || spacing_units.length() == 0 ) spacing_units = "" + calibration.getUnit(); } pathAndFillManager = new PathAndFillManager(this); file_info = currentImage.getOriginalFileInfo(); // Turn it grey, since I find that helpful: { ImageProcessor imageProcessor = currentImage.getProcessor(); byte [] reds = new byte[256]; byte [] greens = new byte[256]; byte [] blues = new byte[256]; for( int i = 0; i < 256; ++i ) { reds[i] = (byte)i; greens[i] = (byte)i; blues[i] = (byte)i; } IndexColorModel cm = new IndexColorModel(8, 256, reds, greens, blues); imageProcessor.setColorModel( cm ); if( currentImage.getStackSize() > 1 ) currentImage.getStack().setColorModel( cm ); currentImage.updateAndRepaintWindow(); } if( file_info != null ) { String originalFileName=file_info.fileName; if (verbose) System.out.println("originalFileName was: "+originalFileName); if( originalFileName != null ) { int lastDot=originalFileName.lastIndexOf("."); if( lastDot > 0 ) { String beforeExtension=originalFileName.substring(0, lastDot); String tubesFileName=beforeExtension+".tubes.tif"; ImagePlus tubenessImage = null; File tubesFile=new File(file_info.directory,tubesFileName); if (verbose) System.out.println("Testing for the existence of "+tubesFile.getAbsolutePath()); if( tubesFile.exists() ) { long megaBytesExtra = ( ((long)width) * height * depth * 4 ) / (1024 * 1024); String extraMemoryNeeded = megaBytesExtra + "MiB"; YesNoCancelDialog d = new YesNoCancelDialog( IJ.getInstance(), "Confirm", "A tubeness file ("+tubesFile.getName()+") exists. Load this file?\n"+ "(This would use an extra "+extraMemoryNeeded+" of memory.)"); if( d.cancelPressed() ) return; else if( d.yesPressed() ) { IJ.showStatus("Loading tubes file."); tubenessImage=BatchOpener.openFirstChannel(tubesFile.getAbsolutePath()); if (verbose) System.out.println("Loaded the tubeness file"); if( tubenessImage == null ) { IJ.error("Failed to load tubes image from "+tubesFile.getAbsolutePath()+" although it existed"); return; } if( tubenessImage.getType() != ImagePlus.GRAY32 ) { IJ.error("The tubeness file must be a 32 bit float image - "+tubesFile.getAbsolutePath()+" was not."); return; } int width = tubenessImage.getWidth(); int height = tubenessImage.getHeight(); int depth = tubenessImage.getStackSize(); ImageStack tubenessStack = tubenessImage.getStack(); tubeness = new float[depth][]; for( int z = 0; z < depth; ++z ) { FloatProcessor fp = (FloatProcessor)tubenessStack.getProcessor( z + 1 ); tubeness[z] = (float[])fp.getPixels(); } } } } } } single_pane = true; Image3DUniverse universeToUse = null; String [] choices3DViewer = null;; if( ! singleSlice ) { boolean java3DAvailable = haveJava3D(); boolean showed3DViewerOption = false; GenericDialog gd = new GenericDialog("Simple Neurite Tracer (v" + PLUGIN_VERSION + ")"); gd.addMessage("Tracing the image: "+currentImage.getTitle()); String extraMemoryNeeded = " (will use an extra: "; int bitDepth = currentImage.getBitDepth(); int byteDepth = bitDepth == 24 ? 4 : bitDepth / 8; long megaBytesExtra = ( ((long)width) * height * depth * byteDepth * 2 ) / (1024 * 1024); extraMemoryNeeded += megaBytesExtra + "MiB of memory)"; gd.addCheckbox("Use_three_pane view?"+extraMemoryNeeded, false); if( ! java3DAvailable ) { String message = "(Java3D classes don't seem to be available, so no 3D viewer option is available.)"; System.out.println(message); gd.addMessage(message); } else if( currentImage.getBitDepth() != 8 ) { String message = "(3D viewer option is only currently available for 8 bit images)"; System.out.println(message); gd.addMessage(message); } else { showed3DViewerOption = true; choices3DViewer = new String[Image3DUniverse.universes.size()+2]; String no3DViewerString = "No 3D view"; String useNewString = "Create New 3D Viewer"; choices3DViewer[choices3DViewer.length-2] = useNewString; choices3DViewer[choices3DViewer.length-1] = no3DViewerString; for( int i = 0; i < choices3DViewer.length - 2; ++i ) { String contentsString = Image3DUniverse.universes.get(i).allContentsString(); String shortContentsString; if( contentsString.length() == 0 ) shortContentsString = "[Empty]"; else shortContentsString = contentsString.substring(0,Math.min(40,contentsString.length()-1)); choices3DViewer[i] = "Use 3D viewer ["+i+"] containing " + shortContentsString; } gd.addChoice( "Choice of 3D Viewer:", choices3DViewer, useNewString ); } gd.showDialog(); if (gd.wasCanceled()) return; single_pane = ! gd.getNextBoolean(); if( showed3DViewerOption ) { String chosenViewer = gd.getNextChoice(); int chosenIndex; for( chosenIndex = 0; chosenIndex < choices3DViewer.length; ++chosenIndex ) if( choices3DViewer[chosenIndex].equals(chosenViewer) ) break; if( chosenIndex == choices3DViewer.length - 2 ) { use3DViewer = true; universeToUse = null; } else if( chosenIndex == choices3DViewer.length - 1 ) { use3DViewer = false; universeToUse = null; } else { use3DViewer = true; universeToUse = Image3DUniverse.universes.get(chosenIndex);; } } } initialize(currentImage); xy_tracer_canvas = (InteractiveTracerCanvas)xy_canvas; xz_tracer_canvas = (InteractiveTracerCanvas)xz_canvas; zy_tracer_canvas = (InteractiveTracerCanvas)zy_canvas; setupTrace = true; resultsDialog = new NeuriteTracerResultsDialog( "Tracing for: " + xy.getShortTitle(), this, applet != null ); /* FIXME: the first could be changed to add 'this', and move the small implementation out of NeuriteTracerResultsDialog into this class. */ pathAndFillManager.addPathAndFillListener(resultsDialog); pathAndFillManager.addPathAndFillListener(resultsDialog.pw); pathAndFillManager.addPathAndFillListener(resultsDialog.fw); if( (x_spacing == 0.0) || (y_spacing == 0.0) || (z_spacing == 0.0) ) { IJ.error( "One dimension of the calibration information was zero: (" + x_spacing + "," + y_spacing + "," + z_spacing + ")" ); return; } { ImageStack s = xy.getStack(); switch(imageType) { case ImagePlus.GRAY8: case ImagePlus.COLOR_256: slices_data_b = new byte[depth][]; for( int z = 0; z < depth; ++z ) slices_data_b[z] = (byte []) s.getPixels( z + 1 ); stackMin = 0; stackMax = 255; break; case ImagePlus.GRAY16: slices_data_s = new short[depth][]; for( int z = 0; z < depth; ++z ) slices_data_s[z] = (short []) s.getPixels( z + 1 ); IJ.showStatus("Finding stack minimum / maximum"); for( int z = 0; z < depth; ++z ) { for( int y = 0; y < height; ++y ) for( int x = 0; x < width; ++x ) { short v = slices_data_s[z][y*width+x]; if( v < stackMin ) stackMin = v; if( v > stackMax ) stackMax = v; } IJ.showProgress( z / (float)depth ); } IJ.showProgress(1.0); break; case ImagePlus.GRAY32: slices_data_f = new float[depth][]; for( int z = 0; z < depth; ++z ) slices_data_f[z] = (float []) s.getPixels( z + 1 ); IJ.showStatus("Finding stack minimum / maximum"); for( int z = 0; z < depth; ++z ) { for( int y = 0; y < height; ++y ) for( int x = 0; x < width; ++x ) { float v = slices_data_f[z][y*width+x]; if( v < stackMin ) stackMin = v; if( v > stackMax ) stackMax = v; } IJ.showProgress( z / (float)depth ); } IJ.showProgress(1.0); break; } } xy_tracer_canvas.addKeyListener( xy_tracer_canvas ); xy_window.addKeyListener( xy_tracer_canvas ); if( ! single_pane ) { xz_tracer_canvas.addKeyListener( xz_tracer_canvas ); xz_window.addKeyListener( xz_tracer_canvas ); zy_tracer_canvas.addKeyListener( zy_tracer_canvas ); zy_window.addKeyListener( zy_tracer_canvas ); } if( use3DViewer ) { boolean reusing; if( universeToUse == null ) { reusing = false; univ = new Image3DUniverse(512, 512); } else { reusing = true; univ = universeToUse; } univ.setUseToFront(false); univ.addUniverseListener(pathAndFillManager); if( ! reusing ) { univ.show(); GUI.center(univ.getWindow()); } boolean [] channels = { true, true, true }; String title = "Image for tracing ["+currentImage.getTitle()+"]"; String contentName = univ.getSafeContentName( title ); // univ.resetView(); Content c = univ.addContent(xy, new Color3f(Color.white), contentName, 10, // threshold channels, guessResamplingFactor(), // resampling factor Content.VOLUME); c.setLocked(true); c.setTransparency(0.5f); if( ! reusing ) univ.resetView(); univ.setAutoAdjustView(false); } File tracesFileToLoad = null; if( macroTracesFilename != null ) { tracesFileToLoad = new File( macroTracesFilename ); if( tracesFileToLoad.exists() ) pathAndFillManager.load( tracesFileToLoad.getAbsolutePath() ); else IJ.error("The traces file suggested by the macro parameters ("+macroTracesFilename+") does not exist"); } resultsDialog.displayOnStarting(); } finally { IJ.getInstance().addKeyListener( IJ.getInstance() ); } } }