package cz.cuni.lf1.lge.ThunderSTORM; import cz.cuni.lf1.lge.ThunderSTORM.UI.GUI; import cz.cuni.lf1.lge.ThunderSTORM.UI.Help; import cz.cuni.lf1.lge.ThunderSTORM.UI.MacroParser; import cz.cuni.lf1.lge.ThunderSTORM.colocalization.CBC; 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.MoleculeDescriptor.Units; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.PSFModel; import cz.cuni.lf1.lge.ThunderSTORM.rendering.ASHRendering; import cz.cuni.lf1.lge.ThunderSTORM.rendering.RenderingMethod; import cz.cuni.lf1.lge.ThunderSTORM.results.GenericTable; import cz.cuni.lf1.lge.ThunderSTORM.results.GenericTableModel; import cz.cuni.lf1.lge.ThunderSTORM.results.IJGroundTruthTable; import cz.cuni.lf1.lge.ThunderSTORM.results.IJResultsTable; import cz.cuni.lf1.lge.ThunderSTORM.util.*; import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.DialogStub; import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.ParameterKey; import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.ParameterTracker; import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.validators.IntegerValidatorFactory; import cz.cuni.lf1.lge.ThunderSTORM.util.MacroUI.validators.StringValidatorFactory; import ij.IJ; import ij.ImagePlus; import ij.Macro; import ij.plugin.PlugIn; import javax.swing.*; import java.awt.*; public class CBCPlugIn implements PlugIn { ParameterTracker params = new ParameterTracker(); public CBCPlugIn() { params.setNoGuiParametersAllowed(true); } @Override public void run(String arg) { GUI.setLookAndFeel(); try { if(Macro.getOptions() != null) { params.readMacroOptions(); } else { params.recordMacroOptions(); } CBCDialog dialog = new CBCDialog(IJ.getInstance()); if(MacroParser.isRanFromMacro()) { dialog.getParams().readMacroOptions(); } else { if(dialog.showAndGetResult() != JOptionPane.OK_OPTION) { return; } } runCBC(dialog.getChannel1Table(), dialog.getChannel2Table(), dialog.is3D(), dialog.radiusStep.getValue(), dialog.stepCount.getValue(), dialog.addCBC.getValue(), dialog.addNNDist.getValue(), dialog.addNNCount.getValue()); } catch(Exception ex) { IJ.error(ex.getMessage()); } } public void runCBC(GenericTable channel1Table, GenericTable channel2Table, boolean is3D, double radiusStep, int stepCount, boolean addCBCToTable, boolean addNNDistToTable, boolean addNeighborsInDistToTable) { if (channel1Table == null || channel2Table == null) return; double[][] firstCoords = new double[channel1Table.getRowCount()][]; if (is3D) { for (int i = 0; i < firstCoords.length; i++) { Molecule m = channel1Table.getRow(i); firstCoords[i] = new double[]{m.getX(Units.NANOMETER), m.getY(Units.NANOMETER), m.getZ(Units.NANOMETER)}; } } else { for (int i = 0; i < firstCoords.length; i++) { Molecule m = channel1Table.getRow(i); firstCoords[i] = new double[]{m.getX(Units.NANOMETER), m.getY(Units.NANOMETER)}; } } CBC cbc; if (channel1Table != channel2Table) { double[][] secondCoords = new double[channel2Table.getRowCount()][]; if (is3D) { for (int i = 0; i < secondCoords.length; i++) { Molecule m = channel2Table.getRow(i); secondCoords[i] = new double[]{m.getX(Units.NANOMETER), m.getY(Units.NANOMETER), m.getZ(Units.NANOMETER)}; } } else { for (int i = 0; i < secondCoords.length; i++) { Molecule m = channel2Table.getRow(i); secondCoords[i] = new double[]{m.getX(Units.NANOMETER), m.getY(Units.NANOMETER)}; } } cbc = new CBC(firstCoords, secondCoords, radiusStep, stepCount); } else { cbc = new CBC(firstCoords, firstCoords, radiusStep, stepCount); } IJ.showStatus("Calculating first channel CBC."); double[] firstChannelCBC = cbc.calculateFirstChannelCBC(); if(addCBCToTable) addColumnToModel(channel1Table.getModel(), firstChannelCBC, "cbc", Units.UNITLESS); if(addNNDistToTable) addColumnToModel(channel1Table.getModel(), cbc.firstChannelNearestNeighborDistances, "nn_dist", Units.NANOMETER); if(addNeighborsInDistToTable) { for(int i = 0; i < cbc.squaredRadiusDomain.length; i++) { addColumnToModel(channel1Table.getModel(), cbc.firstChannelNeighborsInDistance[i], "neighbors_in_dist_"+((int)((i+1)*radiusStep)), Units.UNITLESS); } } double[] x1 = channel1Table.getColumnAsDoubles(PSFModel.Params.LABEL_X, MoleculeDescriptor.Units.PIXEL); double[] y1 = channel1Table.getColumnAsDoubles(PSFModel.Params.LABEL_Y, MoleculeDescriptor.Units.PIXEL); double[] x2 = channel2Table.getColumnAsDoubles(PSFModel.Params.LABEL_X, MoleculeDescriptor.Units.PIXEL); double[] y2 = channel2Table.getColumnAsDoubles(PSFModel.Params.LABEL_Y, MoleculeDescriptor.Units.PIXEL); double maxRoiX = MathProxy.max(VectorMath.max(x1), VectorMath.max(x2)); double maxRoiY = MathProxy.max(VectorMath.max(y1), VectorMath.max(y2)); RenderingMethod renderer = new ASHRendering.Builder() .shifts(2) .zShifts(1).colorize(true).zRange(-1, 1, 0.1) .roi(0, maxRoiX, 0, maxRoiY) .resolution(0.2) .build(); ImagePlus imp = renderer.getRenderedImage(x1, y1, firstChannelCBC, null, null); imp.show(); if (channel1Table != channel2Table) { IJ.showStatus("Calculating second channel CBC."); double[] secondChannelCBC = cbc.calculateSecondChannelCBC(); if (addCBCToTable) addColumnToModel(channel2Table.getModel(), secondChannelCBC, "cbc", Units.UNITLESS); if (addNNDistToTable) addColumnToModel(channel2Table.getModel(), cbc.secondChannelNearestNeighborDistances, "nn_dist", Units.NANOMETER); if (addNeighborsInDistToTable) { for (int i = 0; i < cbc.squaredRadiusDomain.length; i++) { addColumnToModel(channel2Table.getModel(), cbc.secondChannelNeighborsInDistance[i], "neighbors_in_dist_" + ((int) ((i + 1) * radiusStep)), Units.UNITLESS); } } RenderingMethod renderer2 = new ASHRendering.Builder() .shifts(2) .zShifts(1).colorize(true).zRange(-1, 1, 0.1) .roi(0, maxRoiX, 0, maxRoiY) .resolution(0.2) .build(); ImagePlus imp2 = renderer2.getRenderedImage(x2, y2, secondChannelCBC, null, null); imp2.show(); } IJ.showStatus("Done."); } private void addColumnToModel(GenericTableModel model, final double[] data, String title, Units units) { int col = model.findColumn(title); if (col == GenericTableModel.COLUMN_NOT_FOUND) { model.addColumn(title, units, new IValue<Double>() { int i = 0; @Override public Double getValue() { return data[i++]; } }); model.fireTableStructureChanged(); } else { model.insertColumn(col, title, units, new IValue<Double>() { int i = 0; @Override public Double getValue() { return data[i++]; } }); } model.fireTableDataChanged(); } class CBCDialog extends DialogStub { ParameterKey.Integer radiusStep; ParameterKey.String dimensions; ParameterKey.Integer stepCount; ParameterKey.String channel1; ParameterKey.String channel2; ParameterKey.Boolean addCBC; ParameterKey.Boolean addNNDist; ParameterKey.Boolean addNNCount; final String[] dim = {"2D", "3D"}; final String[] tables = new String[] {"Results table", "Ground-truth table"}; public CBCDialog(Window owner) { super(new ParameterTracker("thunderstorm.cbc"), owner, "Coordinate-Based Colocalization"); radiusStep = params.createIntField("radiusStep", IntegerValidatorFactory.positiveNonZero(), 50); dimensions = params.createStringField("dimensions", StringValidatorFactory.isMember(dim), dim[0]); stepCount = params.createIntField("stepCount", IntegerValidatorFactory.rangeInclusive(2, 10000), 10); channel1 = params.createStringField("channel1", StringValidatorFactory.isMember(tables), tables[0]); channel2 = params.createStringField("channel2", StringValidatorFactory.isMember(tables), tables[1]); addCBC = params.createBooleanField("addCBC", null, true); addNNDist = params.createBooleanField("addNNDist", null, false); addNNCount = params.createBooleanField("addNNCount", null, false); } ParameterTracker getParams() { return params; } @Override protected void layoutComponents() { JTextField radiusStepTextField = new JTextField(20); JTextField stepCountTextField = new JTextField(20); ButtonGroup dimBtnGroup = new ButtonGroup(); JRadioButton twoDimRadioButton = new JRadioButton(dim[0]); JRadioButton threeDimRadioButton = new JRadioButton(dim[1]); dimBtnGroup.add(twoDimRadioButton); dimBtnGroup.add(threeDimRadioButton); JComboBox<String> channel1ComboBox = new JComboBox<String>(tables); JComboBox<String> channel2ComboBox = new JComboBox<String>(tables); JCheckBox addCBCCheckBox = new JCheckBox("Add CBC into the results table"); JCheckBox addNNDistCheckBox = new JCheckBox("Add distance to the nearest neighbor into the results table"); JCheckBox addNNCountCheckBox = new JCheckBox("Add count of neighbors within the radius into the results table"); radiusStep.registerComponent(radiusStepTextField); dimensions.registerComponent(dimBtnGroup); stepCount.registerComponent(stepCountTextField); channel1.registerComponent(channel1ComboBox); channel2.registerComponent(channel2ComboBox); addCBC.registerComponent(addCBCCheckBox); addNNDist.registerComponent(addNNDistCheckBox); addNNCount.registerComponent(addNNCountCheckBox); JPanel dimPanel = new JPanel(new GridBagLayout()); dimPanel.add(twoDimRadioButton, new GridBagHelper.Builder().gridxy(0, 0).weightx(0.2).anchor(GridBagConstraints.WEST).build()); dimPanel.add(threeDimRadioButton, new GridBagHelper.Builder().gridxy(1, 0).weightx(0.2).anchor(GridBagConstraints.WEST).build()); dimPanel.add(Box.createHorizontalGlue(), new GridBagHelper.Builder().gridxy(2, 0).weightx(0.6).anchor(GridBagConstraints.WEST).build()); add(new JLabel("Radius step [nm]:"), GridBagHelper.leftCol()); add(radiusStepTextField, GridBagHelper.rightCol()); add(new JLabel("Dimensions:"), GridBagHelper.leftCol()); add(dimPanel, GridBagHelper.rightCol()); add(new JLabel("Step count:"), GridBagHelper.leftCol()); add(stepCountTextField, GridBagHelper.rightCol()); add(new JLabel("Channel 1:"), GridBagHelper.leftCol()); add(channel1ComboBox, GridBagHelper.rightCol()); add(new JLabel("Channel 2:"), GridBagHelper.leftCol()); add(channel2ComboBox, GridBagHelper.rightCol()); add(Box.createVerticalStrut(10), GridBagHelper.twoCols()); add(addCBCCheckBox, GridBagHelper.twoCols()); add(addNNDistCheckBox, GridBagHelper.twoCols()); add(addNNCountCheckBox, GridBagHelper.twoCols()); add(Box.createVerticalStrut(10), GridBagHelper.twoCols()); //buttons JPanel buttons = new JPanel(new GridBagLayout()); buttons.add(createDefaultsButton()); buttons.add(Box.createHorizontalGlue(), new GridBagHelper.Builder() .fill(GridBagConstraints.HORIZONTAL).weightx(1).build()); buttons.add(Help.createHelpButton(CBCPlugIn.class)); buttons.add(createOKButton()); buttons.add(createCancelButton()); add(buttons, GridBagHelper.twoCols()); params.loadPrefs(); getRootPane().setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); pack(); setLocationRelativeTo(null); setModal(true); } boolean is3D() { return dimensions.getValue().equals(dim[1]); } public GenericTable getChannel1Table() throws Exception { return getChannelTable(channel1); } public GenericTable getChannel2Table() throws Exception { return getChannelTable(channel2); } protected GenericTable getChannelTable(ParameterKey.String channel) throws Exception { GenericTable table; if (channel.getValue().equals(tables[0])) { // results table table = IJResultsTable.getResultsTable(); if (table == null) { throw new Exception("Requires results table to be opened!"); } if (table.getRowCount() <= 0) { throw new Exception("Results table cannot be empty!"); } } else if (channel.getValue().equals(tables[1])) { // ground-truth table table = IJGroundTruthTable.getGroundTruthTable(); if (table == null) { throw new Exception("Requires ground-truth table to be opened!"); } if (table.getRowCount() <= 0) { throw new Exception("Ground-truth table cannot be empty!"); } } else { throw new Exception("Unknown channel!"); } if (!table.columnExists(PSFModel.Params.LABEL_X) || !table.columnExists(PSFModel.Params.LABEL_Y)) { throw new Exception("Columns `x` and `y` must be present in the table!"); } if (is3D() && !table.columnExists(PSFModel.Params.LABEL_Z)) { throw new Exception("Column `z` must be present in the table! Use 2D filter instead."); } return table; } } }