package cz.cuni.lf1.lge.ThunderSTORM;
import cz.cuni.lf1.lge.ThunderSTORM.UI.*;
import cz.cuni.lf1.lge.ThunderSTORM.detectors.IDetector;
import cz.cuni.lf1.lge.ThunderSTORM.detectors.ui.IDetectorUI;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.IBiplaneEstimator;
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.IBiplaneEstimatorUI;
import cz.cuni.lf1.lge.ThunderSTORM.filters.IFilter;
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.*;
import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.Utils;
import ij.IJ;
import ij.ImagePlus;
import ij.gui.Roi;
import ij.plugin.PlugIn;
import ij.plugin.frame.Recorder;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import javax.swing.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static cz.cuni.lf1.lge.ThunderSTORM.util.ImageMath.add;
import static cz.cuni.lf1.lge.ThunderSTORM.util.ImageMath.subtract;
/**
* ThunderSTORM biplane analysis plugin.
*
* Open the options dialog, process an two image stacks simultaneously 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 BiplaneAnalysisPlugIn implements PlugIn {
private IFilterUI selectedFilterUI;
private IBiplaneEstimatorUI selectedEstimatorUI;
private IDetectorUI selectedDetectorUI;
private ImagePlus imp1, imp2;
private Roi roi1, roi2;
private RenderingQueue renderingQueue;
@Override
public void run(String arg) {
GUI.setLookAndFeel();
//
if (Utils.getOpenImageTitles(true).length < 3) { // 3 = 2 images + 1 empty string
IJ.error("Two images must be opened for biplane calibration to work!");
return;
}
try {
// load modules
List<IFilterUI> allFilters = ModuleLoader.getUIModules(IFilterUI.class);
List<IDetectorUI> allDetectors = ModuleLoader.getUIModules(IDetectorUI.class);
List<IBiplaneEstimatorUI> allEstimators = ModuleLoader.getUIModules(IBiplaneEstimatorUI.class);
List<IRendererUI> allRenderers = ModuleLoader.getUIModules(IRendererUI.class);
// Create and show the dialog
BiplaneAnalysisOptionsDialog dialog = new BiplaneAnalysisOptionsDialog(allFilters, allDetectors, allEstimators, allRenderers);
if(dialog.showAndGetResult() != JOptionPane.OK_OPTION) {
return;
}
selectedFilterUI = dialog.getActiveFilterUI();
selectedDetectorUI = dialog.getActiveDetectorUI();
selectedEstimatorUI = dialog.getActiveEstimatorUI();
IRendererUI selectedRendererUI = dialog.getActiveRendererUI();
if (((imp1 = dialog.getFirstPlaneStack()) == null) || ((imp2 = dialog.getSecondPlaneStack()) == null)) {
IJ.error("Couldn't open both images!");
return;
}
if (imp1.getImageStackSize() != imp2.getImageStackSize()) {
IJ.error("Both stacks must have the same number of frames!");
return;
}
roi1 = imp1.getRoi() != null ? imp1.getRoi() : new Roi(0, 0, imp1.getWidth(), imp1.getHeight());
roi2 = imp2.getRoi() != null ? imp2.getRoi() : new Roi(0, 0, imp2.getWidth(), imp2.getHeight());
if (roi1.getFloatWidth() != roi2.getFloatWidth() || roi1.getFloatHeight() != roi2.getFloatHeight()) {
IJ.error("Both used images (or ROIs) must be of the same size!");
return;
}
selectedRendererUI.setSize(roi1.getBounds().width, roi1.getBounds().height);
IncrementalRenderingMethod method = selectedRendererUI.getImplementation();
ImagePlus renderedImage = (method != null) ? method.getRenderedImage() : null;
renderingQueue = new RenderingQueue(method, new RenderingQueue.DefaultRepaintTask(renderedImage), selectedRendererUI.getRepaintFrequency());
// if recording window is open, record parameters of all modules
if(Recorder.record) {
MacroParser.recordFilterUI(dialog.getActiveFilterUI());
MacroParser.recordDetectorUI(dialog.getActiveDetectorUI());
MacroParser.recordBiplaneEstimatorUI(dialog.getActiveEstimatorUI());
MacroParser.recordRendererUI(dialog.getActiveRendererUI());
}
try {
Thresholder.loadFilters(allFilters);
Thresholder.setActiveFilter(dialog.getActiveFilterUIIndex()); // !! must be called before any threshold is evaluated !!
Thresholder.parseThreshold(selectedDetectorUI.getThreadLocalImplementation().getThresholdFormula());
} catch(Exception ex) {
IJ.error("Error parsing threshold formula! " + ex.toString());
return;
}
// force clear and hide results table
IJResultsTable rt = IJResultsTable.getResultsTable();
rt.forceHide();
rt.reset();
rt.setOriginalState();
rt.setMeasurementProtocol(new MeasurementProtocol(imp1, imp2, selectedFilterUI, selectedDetectorUI, selectedEstimatorUI));
// analyze
final AtomicInteger framesProcessed = new AtomicInteger(0);
final int stackSize = imp1.getImageStackSize();
Loop.withIndex(1, stackSize + 1, new Loop.BodyWithIndex() {
@Override
public void run(int frame) {
try {
analyzeFrame(frame, getRoiProcessor(imp1, roi1, frame), getRoiProcessor(imp2, roi2, frame));
framesProcessed.incrementAndGet();
IJ.showProgress((double) framesProcessed.intValue() / (double) stackSize);
IJ.showStatus("ThunderSTORM processing frame " + framesProcessed + " of " + stackSize + "...");
GUI.checkIJEscapePressed();
} catch (StoppedByUserException ie){
IJResultsTable rt = IJResultsTable.getResultsTable();
synchronized(rt) {
if(rt.isForceHidden()) {
showResults();
}
}
} catch (StoppedDueToErrorException ex) {
IJ.error(ex.getMessage());
}
}
});
// finalize and display the results
IJ.showStatus("ThunderSTORM is generating the results...");
showResults();
} catch (ClassCastException ex) {
// this is usually caused by incompatibility of calibration file with an estimator
IJ.error("Method can't be initiated: " + ex.getMessage());
} catch(Throwable ex) {
IJ.handleException(ex);
}
IJ.showProgress(1.0);
IJ.showStatus("ThunderSTORM finished.");
}
private FloatProcessor getRoiProcessor(ImagePlus imp, Roi roi, int index) {
ImageProcessor ip = imp.getStack().getProcessor(index);
ip.setRoi(roi.getBounds());
FloatProcessor fp = subtract(ip.crop().convertToFloatProcessor(), (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());
return fp;
}
private void analyzeFrame(int frame, FloatProcessor fp1, FloatProcessor fp2) {
// init
IFilter filter = selectedFilterUI.getThreadLocalImplementation();
IDetector detector = selectedDetectorUI.getThreadLocalImplementation();
IBiplaneEstimator estimator = selectedEstimatorUI.getThreadLocalImplementation();
// detection in first plane
Thresholder.setCurrentImage(fp1);
List<Point> det1 = detector.detectMoleculeCandidates(filter.filterImage(fp1));
// detection in second plane
Thresholder.setCurrentImage(fp2);
List<Point> det2 = detector.detectMoleculeCandidates(filter.filterImage(fp2));
// run fitting on the pairs
List<Molecule> fits = estimator.estimateParameters(fp1, fp2, Point.applyRoiMask(roi1, det1), Point.applyRoiMask(roi2, det2));
storeFits(fits, frame);
if(fits.size() > 0) {
renderingQueue.renderLater(fits);
}
}
synchronized private 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);
}
}
synchronized private 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);
// TODO: uncomment!!!
/*
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();
}
*/
}
private 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);
}
}