package cz.cuni.lf1.lge.ThunderSTORM.results;
import cz.cuni.lf1.lge.ThunderSTORM.UI.GUI;
import cz.cuni.lf1.lge.ThunderSTORM.UI.Help;
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.util.GridBagHelper;
import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.ParameterKey;
import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.validators.DoubleValidatorFactory;
import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.validators.IntegerValidatorFactory;
import cz.cuni.lf1.lge.ThunderSTORM.util.WorkerThread;
import ij.IJ;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
import static cz.cuni.lf1.lge.ThunderSTORM.util.MathProxy.sqr;
public class ResultsGrouping extends PostProcessingModule {
private JTextField distanceTextField;
private JTextField offFramesTextField;
private JTextField framesPerMoleculeTextField;
private JButton applyButton;
private final ParameterKey.Double distParam;
private final ParameterKey.Integer offFramesParam;
private final ParameterKey.Integer framesPerMolecule;
private final ParameterKey.Double zCoordWeightParam; //hidden param
public ResultsGrouping() {
params.setNoGuiParametersAllowed(true);
distParam = params.createDoubleField("dist", DoubleValidatorFactory.positive(), 20);
offFramesParam = params.createIntField("offFrames", IntegerValidatorFactory.positive(), 1);
framesPerMolecule = params.createIntField("framesPerMolecule", IntegerValidatorFactory.positive(), 0);
zCoordWeightParam = params.createDoubleField("zCoordWeight", DoubleValidatorFactory.positive(), 0.1);
}
@Override
public String getMacroName() {
return "merge";
}
@Override
public String getTabName() {
return "Merging";
}
@Override
protected JPanel createUIPanel() {
JPanel grouping = new JPanel(new GridBagLayout());
InputListener listener = new InputListener();
distanceTextField = new JTextField(5);
distanceTextField.addKeyListener(listener);
distParam.registerComponent(distanceTextField);
JLabel groupThrLabel = new JLabel("Maximum distance [units of x,y]: ");
offFramesTextField = new JTextField(5);
offFramesTextField.addKeyListener(listener);
offFramesParam.registerComponent(offFramesTextField);
JLabel offFramesLabel = new JLabel("Maximum off frames: ");
framesPerMoleculeTextField = new JTextField(5);
framesPerMoleculeTextField.addKeyListener(listener);
framesPerMolecule.registerComponent(framesPerMoleculeTextField);
JLabel framesPerMoleculeLabel = new JLabel("Max. frames per molecule (0 = unlimited): ");
applyButton = new JButton("Merge");
applyButton.addActionListener(listener);
grouping.add(groupThrLabel, new GridBagHelper.Builder().gridxy(0, 0).weightx(0.2).anchor(GridBagConstraints.EAST).build());
grouping.add(distanceTextField, new GridBagHelper.Builder().gridxy(1, 0).weightx(0.3).anchor(GridBagConstraints.WEST).build());
grouping.add(framesPerMoleculeLabel, new GridBagHelper.Builder().gridxy(2, 0).weightx(0.2).anchor(GridBagConstraints.EAST).build());
grouping.add(framesPerMoleculeTextField, new GridBagHelper.Builder().gridxy(3, 0).weightx(0.3).anchor(GridBagConstraints.WEST).build());
grouping.add(Help.createHelpButton(getClass()), new GridBagHelper.Builder().gridxy(4, 0).anchor(GridBagConstraints.EAST).build());
grouping.add(offFramesLabel, new GridBagHelper.Builder().gridxy(0, 1).weightx(0.5).anchor(GridBagConstraints.EAST).build());
grouping.add(offFramesTextField, new GridBagHelper.Builder().gridxy(1, 1).weightx(0.5).anchor(GridBagConstraints.WEST).build());
grouping.add(applyButton, new GridBagHelper.Builder().gridxy(4, 1).build());
params.updateComponents();
return grouping;
}
@Override
protected void runImpl() {
final double dist = distParam.getValue();
final int offFrames = offFramesParam.getValue();
final int framesPerMol = framesPerMolecule.getValue();
final double zWeight = zCoordWeightParam.getValue();
if(!applyButton.isEnabled() || (dist == 0)) {
return;
}
applyButton.setEnabled(false);
saveStateForUndo();
final int merged = model.getRowCount();
new WorkerThread<List<Molecule>>() {
@Override
protected List<Molecule> doJob() {
return getMergedMolecules(model, dist, offFrames, framesPerMol, zWeight);
}
@Override
protected void finishJob(List<Molecule> mergedMolecules) {
model.reset();
for(Molecule mol : mergedMolecules) {
model.addRow(mol);
}
int into = model.getRowCount();
addOperationToHistory(new DefaultOperation());
table.setStatus(Integer.toString(merged) + " molecules were merged into " + Integer.toString(into) + " molecules");
table.showPreview();
}
@Override
public void exCatch(Throwable ex) {
IJ.handleException(ex);
GUI.showBalloonTip(distanceTextField, ex.getCause().toString());
}
@Override
public void exFinally() {
applyButton.setEnabled(true);
}
}.execute();
}
private class InputListener extends KeyAdapter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
run();
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
applyButton.doClick();
}
}
}
public static List<Molecule> getMergedMolecules(GenericTableModel model, double dist, int offFrames, int framesPerMol, double zWeight) {
if(!model.columnExists(PSFModel.Params.LABEL_X) || !model.columnExists(PSFModel.Params.LABEL_Y)) {
throw new RuntimeException(String.format("X and Y columns not found in Results table. Looking for: %s and %s. Found: %s.", PSFModel.Params.LABEL_X, PSFModel.Params.LABEL_Y, model.getColumnNames()));
}
//
FrameSequence frames = new FrameSequence();
for(int i = 0, im = model.getRowCount(); i < im; i++) {
frames.InsertMolecule(model.getRow(i));
}
frames.mergeMolecules(sqr(dist), offFrames, new FrameSequence.LastDetection(), framesPerMol, zWeight, null);
//
// Set new IDs for the new "macro" molecules
for(Molecule mol : frames.getAllMolecules()) {
if(!mol.isSingleMolecule()) {
mol.setParam(MoleculeDescriptor.LABEL_ID, model.getNewId());
}
}
//
return frames.getAllMolecules();
}
}