package cz.cuni.lf1.lge.ThunderSTORM; import static cz.cuni.lf1.lge.ThunderSTORM.util.ImageMath.subtract; import static cz.cuni.lf1.lge.ThunderSTORM.util.ImageMath.add; import cz.cuni.lf1.lge.ThunderSTORM.UI.AnalysisOptionsDialog; import cz.cuni.lf1.lge.ThunderSTORM.UI.GUI; import cz.cuni.lf1.lge.ThunderSTORM.UI.MacroParser; import cz.cuni.lf1.lge.ThunderSTORM.UI.RenderingOverlay; import cz.cuni.lf1.lge.ThunderSTORM.UI.StoppedByUserException; import cz.cuni.lf1.lge.ThunderSTORM.UI.StoppedDueToErrorException; import cz.cuni.lf1.lge.ThunderSTORM.detectors.IDetector; import cz.cuni.lf1.lge.ThunderSTORM.detectors.ui.IDetectorUI; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.PSFModel; import cz.cuni.lf1.lge.ThunderSTORM.estimators.ui.IEstimatorUI; import cz.cuni.lf1.lge.ThunderSTORM.filters.ui.IFilterUI; import cz.cuni.lf1.lge.ThunderSTORM.rendering.IncrementalRenderingMethod; import cz.cuni.lf1.lge.ThunderSTORM.rendering.RenderingQueue; import cz.cuni.lf1.lge.ThunderSTORM.rendering.ui.IRendererUI; import cz.cuni.lf1.lge.ThunderSTORM.results.IJResultsTable; import cz.cuni.lf1.lge.ThunderSTORM.results.MeasurementProtocol; import cz.cuni.lf1.lge.ThunderSTORM.thresholding.Thresholder; import cz.cuni.lf1.lge.ThunderSTORM.util.Point; import cz.cuni.lf1.lge.ThunderSTORM.util.VectorMath; import ij.IJ; import ij.ImagePlus; import ij.gui.Roi; import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.plugin.frame.Recorder; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import java.awt.Color; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.SwingUtilities; /** * ThunderSTORM Analysis plugin. * * Open the options dialog, process an image stack to recieve a list of * localized molecules which will get displayed in the {@code ResultsTable} and * previed in a new {@code ImageStack} with detections marked as crosses in * {@code Overlay} of each slice of the stack. */ public final class AnalysisPlugIn implements ExtendedPlugInFilter { private int stackSize; private final AtomicInteger nProcessed = new AtomicInteger(0); private final int pluginFlags = DOES_8G | DOES_16 | DOES_32 | NO_CHANGES | NO_UNDO | DOES_STACKS | PARALLELIZE_STACKS | FINAL_PROCESSING | SUPPORTS_MASKING; private List<IFilterUI> allFilters; private List<IDetectorUI> allDetectors; private List<IEstimatorUI> allEstimators; private List<IRendererUI> allRenderers; private int selectedFilter; private int selectedEstimator; private int selectedDetector; private int selectedRenderer; private ImagePlus processedImage; private RenderingQueue renderingQueue; private Roi roi; private AnalysisOptionsDialog dialog; /** * Returns flags specifying capabilities of the plugin. * * This method is called before an actual analysis and returns flags * supported by the plugin. The method is also called after the processing * is finished to fill the {@code ResultsTable} and to visualize the * detections directly in image stack (a new copy of image stack is * created). * * <strong>The {@code ResultsTable} is always guaranteed to contain columns * <i>frame, x, y</i>!</strong> The other parameters are optional and can * change for different PSFs. * * @param command command * @param imp ImagePlus instance holding the active image (not required) * @return flags specifying capabilities of the plugin */ @Override public int setup(String command, ImagePlus imp) { GUI.setLookAndFeel(); // if(command.equals("final")) { IJ.showStatus("ThunderSTORM is generating the results..."); // // Show results (table and overlay) showResults(); // // Finished IJ.showProgress(1.0); IJ.showStatus("ThunderSTORM finished."); return DONE; } else if("showResultsTable".equals(command)) { IJResultsTable.getResultsTable().show(); return DONE; } else { processedImage = imp; return pluginFlags; // Grayscale only, no changes to the image and therefore no undo } } /** * Show the options dialog for a particular command and block the current * processing thread until user confirms his settings or cancels the * operation. * * @param command command (not required) * @param imp ImagePlus instance holding the active image (not required) * @param pfr instance that initiated this plugin (not required) * @return */ @Override public int showDialog(final ImagePlus imp, final String command, PlugInFilterRunner pfr) { MeasurementProtocol measurementProtocol; try { // load modules allFilters = ModuleLoader.getUIModules(IFilterUI.class); allDetectors = ModuleLoader.getUIModules(IDetectorUI.class); allEstimators = ModuleLoader.getUIModules(IEstimatorUI.class); allRenderers = ModuleLoader.getUIModules(IRendererUI.class); ImagePlus renderedImage; if(MacroParser.isRanFromMacro()) { //parse the macro options MacroParser parser = new MacroParser(false, allFilters, allEstimators, allDetectors, allRenderers); selectedFilter = parser.getFilterIndex(); selectedDetector = parser.getDetectorIndex(); selectedEstimator = parser.getEstimatorIndex(); roi = imp.getRoi() != null ? imp.getRoi() : new Roi(0, 0, imp.getWidth(), imp.getHeight()); IRendererUI rendererPanel = parser.getRendererUI(); rendererPanel.setSize(roi.getBounds().width, roi.getBounds().height); IncrementalRenderingMethod method = rendererPanel.getImplementation(); renderedImage = (method != null) ? method.getRenderedImage() : null; renderingQueue = new RenderingQueue(method, new RenderingQueue.DefaultRepaintTask(renderedImage), rendererPanel.getRepaintFrequency()); measurementProtocol = new MeasurementProtocol(imp, allFilters.get(selectedFilter), allDetectors.get(selectedDetector), allEstimators.get(selectedEstimator)); } else { // Create and show the dialog try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { dialog = new AnalysisOptionsDialog(imp, command, allFilters, allDetectors, allEstimators, allRenderers); dialog.setVisible(true); } }); } catch(InvocationTargetException e) { throw e.getCause(); } if(dialog.wasCanceled()) { // This is a blocking call!! return DONE; // cancel } selectedFilter = dialog.getFilterIndex(); selectedDetector = dialog.getDetectorIndex(); selectedEstimator = dialog.getEstimatorIndex(); selectedRenderer = dialog.getRendererIndex(); roi = imp.getRoi() != null ? imp.getRoi() : new Roi(0, 0, imp.getWidth(), imp.getHeight()); IRendererUI renderer = allRenderers.get(selectedRenderer); renderer.setSize(roi.getBounds().width, roi.getBounds().height); IncrementalRenderingMethod method = renderer.getImplementation(); renderedImage = (method != null) ? method.getRenderedImage() : null; renderingQueue = new RenderingQueue(method, new RenderingQueue.DefaultRepaintTask(renderedImage), renderer.getRepaintFrequency()); //if recording window is open, record parameters of all modules if(Recorder.record) { MacroParser.recordFilterUI(dialog.getFilter()); MacroParser.recordDetectorUI(dialog.getDetector()); MacroParser.recordEstimatorUI(dialog.getEstimator()); MacroParser.recordRendererUI(dialog.getRenderer()); } measurementProtocol = new MeasurementProtocol(imp, dialog.getFilter(), dialog.getDetector(), dialog.getEstimator()); } } catch(Throwable ex) { IJ.handleException(ex); return DONE; } // try { Thresholder.loadFilters(allFilters); Thresholder.setActiveFilter(selectedFilter); // !! must be called before any threshold is evaluated !! Thresholder.parseThreshold(allDetectors.get(selectedDetector).getThreadLocalImplementation().getThresholdFormula()); } catch(Exception ex) { IJ.error("Error parsing threshold formula! " + ex.toString()); return DONE; } // IJResultsTable rt = IJResultsTable.getResultsTable(); rt.reset(); rt.setOriginalState(); rt.setMeasurementProtocol(measurementProtocol); rt.forceHide(); // return pluginFlags; // ok } /** * Gives the plugin information about the number of passes through the image * stack we want to process. * * Allocation of resources to store the results is done here. * * @param nPasses number of passes through the image stack we want to * process */ @Override public void setNPasses(int nPasses) { stackSize = nPasses; nProcessed.set(0); } /** * Run the plugin. * * This method is ran in parallel, thus counting the results must be done * atomicaly. * * @param ip input image */ @Override public void run(ImageProcessor ip) { assert (selectedFilter >= 0 && selectedFilter < allFilters.size()) : "Index out of bounds: selectedFilter!"; assert (selectedDetector >= 0 && selectedDetector < allDetectors.size()) : "Index out of bounds: selectedDetector!"; assert (selectedEstimator >= 0 && selectedEstimator < allEstimators.size()) : "Index out of bounds: selectedEstimator!"; assert (selectedRenderer >= 0 && selectedRenderer < allRenderers.size()) : "Index out of bounds: selectedRenderer!"; assert (renderingQueue != null) : "Renderer was not selected!"; // ip.setRoi(roi.getBounds()); FloatProcessor fp = subtract((FloatProcessor) ip.crop().convertToFloat(), (float) CameraSetupPlugIn.getOffset()); float minVal = VectorMath.min((float[]) fp.getPixels()); if(minVal < 0) { IJ.log("\\Update:Camera base level is set higher than values in the image!"); fp = add(-minVal, fp); } fp.setMask(roi.getMask()); try { Thresholder.setCurrentImage(fp); FloatProcessor filtered = allFilters.get(selectedFilter).getThreadLocalImplementation().filterImage(fp); IDetector detector = allDetectors.get(selectedDetector).getThreadLocalImplementation(); List<Point> detections = detector.detectMoleculeCandidates(filtered); List<Molecule> fits = allEstimators.get(selectedEstimator).getThreadLocalImplementation().estimateParameters(fp, Point.applyRoiMask(roi, detections)); storeFits(fits, ip.getSliceNumber()); nProcessed.incrementAndGet(); if(fits.size() > 0) { renderingQueue.renderLater(fits); } IJ.showProgress((double) nProcessed.intValue() / (double) stackSize); IJ.showStatus("ThunderSTORM processing frame " + nProcessed + " of " + stackSize + "..."); GUI.checkIJEscapePressed(); } catch (StoppedByUserException ie){ IJResultsTable rt = IJResultsTable.getResultsTable(); synchronized(rt) { if(rt.isForceHidden()) { showResults(); } } } catch (StoppedDueToErrorException ex) { IJ.error(ex.getMessage()); } } synchronized public void storeFits(Iterable<Molecule> fits, int frame) { IJResultsTable rt = IJResultsTable.getResultsTable(); for(Molecule psf : fits) { psf.insertParamAt(0, MoleculeDescriptor.LABEL_FRAME, MoleculeDescriptor.Units.UNITLESS, (double)frame); rt.addRow(psf); } } public static void setDefaultColumnsWidth(IJResultsTable rt) { rt.setColumnPreferredWidth(MoleculeDescriptor.LABEL_ID, 40); rt.setColumnPreferredWidth(MoleculeDescriptor.LABEL_FRAME, 40); rt.setColumnPreferredWidth(PSFModel.Params.LABEL_X, 60); rt.setColumnPreferredWidth(PSFModel.Params.LABEL_Y, 60); rt.setColumnPreferredWidth(PSFModel.Params.LABEL_SIGMA, 40); rt.setColumnPreferredWidth(PSFModel.Params.LABEL_SIGMA1, 40); rt.setColumnPreferredWidth(PSFModel.Params.LABEL_SIGMA2, 40); } synchronized public void showResults() { // // Show table with results IJResultsTable rt = IJResultsTable.getResultsTable(); rt.sortTableByFrame(); rt.insertIdColumn(); rt.copyOriginalToActual(); rt.setActualState(); rt.convertAllColumnsToAnalogUnits(); rt.setPreviewRenderer(renderingQueue); setDefaultColumnsWidth(rt); if(processedImage != null) { rt.setAnalyzedImage(processedImage); } rt.forceShow(); // // Show detections in the image if(processedImage != null) { processedImage.setOverlay(null); RenderingOverlay.showPointsInImage(rt, processedImage, roi.getBounds(), Color.red, RenderingOverlay.MARKER_CROSS); renderingQueue.repaintLater(); } } }