package gdsc.smlm.ij.plugins;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2013 Alex Herbert
* Genome Damage and Stability Centre
* University of Sussex, UK
*
* This program 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.
*---------------------------------------------------------------------------*/
import gdsc.smlm.engine.DataFilter;
import gdsc.smlm.engine.DataFilterType;
import gdsc.smlm.engine.FitEngineConfiguration;
import gdsc.smlm.fitting.FitConfiguration;
import gdsc.smlm.fitting.FitFunction;
import gdsc.smlm.fitting.FitSolver;
import gdsc.smlm.ij.settings.GlobalSettings;
import gdsc.smlm.ij.settings.SettingsManager;
import gdsc.core.ij.Utils;
import gdsc.smlm.results.Calibration;
import ij.IJ;
import ij.gui.GenericDialog;
import ij.gui.YesNoCancelDialog;
import ij.io.OpenDialog;
import ij.plugin.PlugIn;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.SystemColor;
import java.awt.TextField;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.io.File;
import java.util.Vector;
/**
* Adjust the configuration used for fitting.
*/
// TODO - This could be incorporated into the PeakFit plugin as another run mode.
public class Configuration implements PlugIn, MouseListener, TextListener, ItemListener
{
private static final String TITLE = "Fit Configuration";
private boolean configurationChanged = false;
// Used for the mouse listener
private TextField textConfigFile;
// All the fields that will be updated when reloading the configuration file
private TextField textNmPerPixel;
private TextField textGain;
private Checkbox textEMCCD;
private TextField textExposure;
private TextField textInitialPeakStdDev0;
private TextField textInitialPeakStdDev1;
private TextField textInitialAngleD;
private Choice textDataFilterType;
private Choice textDataFilter;
private TextField textSmooth;
private TextField textSearch;
private TextField textBorder;
private TextField textFitting;
private Choice textFitSolver;
private Choice textFitFunction;
private TextField textFailuresLimit;
private Checkbox textIncludeNeighbours;
private TextField textNeighbourHeightThreshold;
private TextField textResidualsThreshold;
private TextField textDuplicateDistance;
private Checkbox textSmartFilter;
private Checkbox textDisableSimpleFilter;
private TextField textCoordinateShiftFactor;
private TextField textSignalStrength;
private TextField textMinPhotons;
private TextField textPrecisionThreshold;
private TextField textMinWidthFactor;
private TextField textWidthFactor;
/*
* (non-Javadoc)
*
* @see ij.plugin.PlugIn#run(java.lang.String)
*/
public void run(String arg)
{
SMLMUsageTracker.recordPlugin(this.getClass(), arg);
showDialog();
}
/**
* Show the current properties
*/
@SuppressWarnings("unchecked")
public void showDialog()
{
configurationChanged = false;
String filename = SettingsManager.getSettingsFilename();
GlobalSettings settings = SettingsManager.loadSettings(filename);
FitEngineConfiguration config = settings.getFitEngineConfiguration();
FitConfiguration fitConfig = config.getFitConfiguration();
Calibration calibration = settings.getCalibration();
GenericDialog gd = new GenericDialog(TITLE);
gd.addHelp(About.HELP_URL);
gd.addMessage("Configuration settings for the single-molecule localisation microscopy plugins");
gd.addStringField("Config_file", filename, 40);
gd.addNumericField("Calibration (nm/px)", calibration.getNmPerPixel(), 2);
gd.addNumericField("Gain", calibration.getGain(), 2);
gd.addCheckbox("EM-CCD", calibration.isEmCCD());
gd.addNumericField("Exposure_time (ms)", calibration.getExposureTime(), 2);
gd.addMessage("--- Gaussian parameters ---");
gd.addNumericField("Initial_StdDev0", fitConfig.getInitialPeakStdDev0(), 3);
gd.addNumericField("Initial_StdDev1", fitConfig.getInitialPeakStdDev1(), 3);
gd.addNumericField("Initial_Angle", fitConfig.getInitialAngle(), 3);
gd.addMessage("--- Maxima identification ---");
String[] filterTypes = SettingsManager.getNames((Object[]) DataFilterType.values());
gd.addChoice("Spot_filter_type", filterTypes, filterTypes[config.getDataFilterType().ordinal()]);
String[] filterNames = SettingsManager.getNames((Object[]) DataFilter.values());
gd.addChoice("Spot_filter", filterNames, filterNames[config.getDataFilter(0).ordinal()]);
gd.addSlider("Smoothing", 0, 2.5, config.getSmooth(0));
gd.addSlider("Search_width", 0.5, 2.5, config.getSearch());
gd.addSlider("Border", 0.5, 2.5, config.getBorder());
gd.addSlider("Fitting_width", 2, 4.5, config.getFitting());
gd.addMessage("--- Gaussian fitting ---");
Component splitLabel = gd.getMessage();
String[] solverNames = SettingsManager.getNames((Object[]) FitSolver.values());
gd.addChoice("Fit_solver", solverNames, solverNames[fitConfig.getFitSolver().ordinal()]);
String[] functionNames = SettingsManager.getNames((Object[]) FitFunction.values());
gd.addChoice("Fit_function", functionNames, functionNames[fitConfig.getFitFunction().ordinal()]);
// Parameters specific to each Fit solver are collected in a second dialog
gd.addNumericField("Fail_limit", config.getFailuresLimit(), 0);
gd.addCheckbox("Include_neighbours", config.isIncludeNeighbours());
gd.addSlider("Neighbour_height", 0.01, 1, config.getNeighbourHeightThreshold());
gd.addSlider("Residuals_threshold", 0.01, 1, config.getResidualsThreshold());
gd.addSlider("Duplicate_distance", 0, 1.5, fitConfig.getDuplicateDistance());
gd.addMessage("--- Peak filtering ---\nDiscard fits that shift; are too low; or expand/contract");
gd.addCheckbox("Smart_filter", fitConfig.isSmartFilter());
gd.addCheckbox("Disable_simple_filter", fitConfig.isDisableSimpleFilter());
gd.addSlider("Shift_factor", 0.01, 2, fitConfig.getCoordinateShiftFactor());
gd.addNumericField("Signal_strength", fitConfig.getSignalStrength(), 2);
gd.addNumericField("Min_photons", fitConfig.getMinPhotons(), 0);
gd.addSlider("Min_width_factor", 0, 0.99, fitConfig.getMinWidthFactor());
gd.addSlider("Width_factor", 1.01, 5, fitConfig.getWidthFactor());
gd.addNumericField("Precision_threshold", fitConfig.getPrecisionThreshold(), 2);
// Add a mouse listener to the config file field
if (Utils.isShowGenericDialog())
{
Vector<TextField> texts = (Vector<TextField>) gd.getStringFields();
Vector<TextField> numerics = (Vector<TextField>) gd.getNumericFields();
Vector<Checkbox> checkboxes = (Vector<Checkbox>) gd.getCheckboxes();
Vector<Choice> choices = (Vector<Choice>) gd.getChoices();
int n = 0;
int t = 0;
int b = 0;
int ch = 0;
textConfigFile = texts.get(t++);
textConfigFile.addMouseListener(this);
textConfigFile.addTextListener(this);
// TODO: add a value changed listener to detect when typing a new file
textNmPerPixel = numerics.get(n++);
textGain = numerics.get(n++);
textEMCCD = checkboxes.get(b++);
textExposure = numerics.get(n++);
textInitialPeakStdDev0 = numerics.get(n++);
textInitialPeakStdDev1 = numerics.get(n++);
textInitialAngleD = numerics.get(n++);
textDataFilterType = choices.get(ch++);
textDataFilter = choices.get(ch++);
textSmooth = numerics.get(n++);
textSearch = numerics.get(n++);
textBorder = numerics.get(n++);
textFitting = numerics.get(n++);
textFitSolver = choices.get(ch++);
textFitFunction = choices.get(ch++);
textFailuresLimit = numerics.get(n++);
textIncludeNeighbours = checkboxes.get(b++);
textNeighbourHeightThreshold = numerics.get(n++);
textResidualsThreshold = numerics.get(n++);
textDuplicateDistance = numerics.get(n++);
textSmartFilter = checkboxes.get(b++);
textDisableSimpleFilter = checkboxes.get(b++);
textCoordinateShiftFactor = numerics.get(n++);
textSignalStrength = numerics.get(n++);
textMinPhotons = numerics.get(n++);
textMinWidthFactor = numerics.get(n++);
textWidthFactor = numerics.get(n++);
textPrecisionThreshold = numerics.get(n++);
updateFilterInput();
textSmartFilter.addItemListener(this);
textDisableSimpleFilter.addItemListener(this);
}
if (gd.getLayout() != null)
{
GridBagLayout grid = (GridBagLayout) gd.getLayout();
int xOffset = 0, yOffset = 0;
int lastY = -1, rowCount = 0;
for (Component comp : gd.getComponents())
{
// Check if this should be the second major column
if (comp == splitLabel)
{
xOffset += 2;
yOffset -= rowCount;
}
// Reposition the field
GridBagConstraints c = grid.getConstraints(comp);
if (lastY != c.gridy)
rowCount++;
lastY = c.gridy;
c.gridx = c.gridx + xOffset;
c.gridy = c.gridy + yOffset;
c.insets.left = c.insets.left + 10 * xOffset;
c.insets.top = 0;
c.insets.bottom = 0;
grid.setConstraints(comp, c);
}
if (IJ.isLinux())
gd.setBackground(new Color(238, 238, 238));
}
gd.showDialog();
if (gd.wasCanceled())
return;
filename = gd.getNextString();
calibration.setNmPerPixel(gd.getNextNumber());
calibration.setGain(gd.getNextNumber());
calibration.setEmCCD(gd.getNextBoolean());
calibration.setExposureTime(gd.getNextNumber());
fitConfig.setInitialPeakStdDev0(gd.getNextNumber());
fitConfig.setInitialPeakStdDev1(gd.getNextNumber());
fitConfig.setInitialAngleD(gd.getNextNumber());
config.setDataFilterType(gd.getNextChoiceIndex());
config.setDataFilter(gd.getNextChoiceIndex(), Math.abs(gd.getNextNumber()), 0);
config.setSearch(gd.getNextNumber());
config.setBorder(gd.getNextNumber());
config.setFitting(gd.getNextNumber());
fitConfig.setFitSolver(gd.getNextChoiceIndex());
fitConfig.setFitFunction(gd.getNextChoiceIndex());
config.setFailuresLimit((int) gd.getNextNumber());
config.setIncludeNeighbours(gd.getNextBoolean());
config.setNeighbourHeightThreshold(gd.getNextNumber());
config.setResidualsThreshold(gd.getNextNumber());
fitConfig.setDuplicateDistance(gd.getNextNumber());
fitConfig.setSmartFilter(gd.getNextBoolean());
fitConfig.setDisableSimpleFilter(gd.getNextBoolean());
fitConfig.setCoordinateShiftFactor(gd.getNextNumber());
fitConfig.setSignalStrength(gd.getNextNumber());
fitConfig.setMinPhotons(gd.getNextNumber());
fitConfig.setMinWidthFactor(gd.getNextNumber());
fitConfig.setWidthFactor(gd.getNextNumber());
fitConfig.setPrecisionThreshold(gd.getNextNumber());
// Check arguments
try
{
Parameters.isAboveZero("nm per pixel", calibration.getNmPerPixel());
Parameters.isAboveZero("Gain", calibration.getGain());
Parameters.isAboveZero("Exposure time", calibration.getExposureTime());
Parameters.isAboveZero("Initial SD0", fitConfig.getInitialPeakStdDev0());
Parameters.isAboveZero("Initial SD1", fitConfig.getInitialPeakStdDev1());
Parameters.isPositive("Initial angle", fitConfig.getInitialAngleD());
Parameters.isAboveZero("Search_width", config.getSearch());
Parameters.isAboveZero("Fitting_width", config.getFitting());
Parameters.isPositive("Failures limit", config.getFailuresLimit());
Parameters.isPositive("Neighbour height threshold", config.getNeighbourHeightThreshold());
Parameters.isPositive("Residuals threshold", config.getResidualsThreshold());
Parameters.isPositive("Duplicate distance", fitConfig.getDuplicateDistance());
Parameters.isPositive("Coordinate Shift factor", fitConfig.getCoordinateShiftFactor());
Parameters.isPositive("Signal strength", fitConfig.getSignalStrength());
Parameters.isPositive("Min photons", fitConfig.getMinPhotons());
Parameters.isPositive("Min width factor", fitConfig.getMinWidthFactor());
Parameters.isPositive("Width factor", fitConfig.getWidthFactor());
Parameters.isPositive("Precision threshold", fitConfig.getPrecisionThreshold());
}
catch (IllegalArgumentException e)
{
IJ.error(TITLE, e.getMessage());
return;
}
if (gd.invalidNumber())
return;
configurationChanged = SettingsManager.saveSettings(settings, filename);
if (configurationChanged)
SettingsManager.saveSettingsFilename(filename);
if (!PeakFit.configureSmartFilter(settings, filename))
return;
if (!PeakFit.configureDataFilter(settings, filename, false))
return;
PeakFit.configureFitSolver(settings, filename, false);
}
public boolean isConfigurationChanged()
{
return configurationChanged;
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() > 1) // Double-click
{
if (e.getSource() == textConfigFile)
{
String[] path = Utils.decodePath(textConfigFile.getText());
OpenDialog chooser = new OpenDialog("Configuration_File", path[0], path[1]);
if (chooser.getFileName() != null)
{
String newFilename = chooser.getDirectory() + chooser.getFileName();
textConfigFile.setText(newFilename);
}
}
}
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent e)
{
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent e)
{
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e)
{
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e)
{
}
/*
* (non-Javadoc)
*
* @see java.awt.event.TextListener#textValueChanged(java.awt.event.TextEvent)
*/
public void textValueChanged(TextEvent e)
{
if (e.getSource() == textConfigFile)
{
refreshSettings(textConfigFile.getText());
}
}
private void refreshSettings(String newFilename)
{
if (newFilename != null && new File(newFilename).exists())
{
YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), TITLE, "Reload settings from file");
d.setVisible(true);
if (d.yesPressed())
{
// Reload the settings and update the GUI
// XXX : This does not deal with loading settings into fields that are not displayed,
// e.g. for configuring the Fit Solvers. This could be done by writing into
// a class scope settings instance (loaded in showDialog()). However the user would not
// see all the changes that have been written, since the later dialogs are shown depending
// on what options are initially configured.
GlobalSettings settings = SettingsManager.unsafeLoadSettings(newFilename, false);
if (settings == null)
return;
FitEngineConfiguration config = settings.getFitEngineConfiguration();
FitConfiguration fitConfig = config.getFitConfiguration();
Calibration calibration = settings.getCalibration();
textNmPerPixel.setText("" + calibration.getNmPerPixel());
textGain.setText("" + calibration.getGain());
textEMCCD.setState(calibration.isEmCCD());
textExposure.setText("" + calibration.getExposureTime());
textInitialPeakStdDev0.setText("" + fitConfig.getInitialPeakStdDev0());
textInitialPeakStdDev1.setText("" + fitConfig.getInitialPeakStdDev1());
textInitialAngleD.setText("" + fitConfig.getInitialAngle());
textDataFilterType.select(config.getDataFilterType().ordinal());
textDataFilter.select(config.getDataFilter(0).ordinal());
textSmooth.setText("" + config.getSmooth(0));
textSearch.setText("" + config.getSearch());
textBorder.setText("" + config.getBorder());
textFitting.setText("" + config.getFitting());
textFitSolver.select(fitConfig.getFitSolver().ordinal());
textFitFunction.select(fitConfig.getFitFunction().ordinal());
textFailuresLimit.setText("" + config.getFailuresLimit());
textIncludeNeighbours.setState(config.isIncludeNeighbours());
textNeighbourHeightThreshold.setText("" + config.getNeighbourHeightThreshold());
textResidualsThreshold.setText("" + config.getResidualsThreshold());
textDuplicateDistance.setText("" + fitConfig.getDuplicateDistance());
textCoordinateShiftFactor.setText("" + fitConfig.getCoordinateShiftFactor());
textSignalStrength.setText("" + fitConfig.getSignalStrength());
textMinPhotons.setText("" + fitConfig.getMinPhotons());
textMinWidthFactor.setText("" + fitConfig.getMinWidthFactor());
textWidthFactor.setText("" + fitConfig.getWidthFactor());
textPrecisionThreshold.setText("" + fitConfig.getPrecisionThreshold());
}
}
}
/*
* (non-Javadoc)
*
* @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
*/
public void itemStateChanged(ItemEvent e)
{
if (e.getSource() instanceof Checkbox)
{
if (e.getSource() == textSmartFilter)
{
textDisableSimpleFilter.setState(textSmartFilter.getState());
}
updateFilterInput();
}
}
private void updateFilterInput()
{
if (textDisableSimpleFilter.getState())
{
disableEditing(textCoordinateShiftFactor);
disableEditing(textSignalStrength);
disableEditing(textMinPhotons);
// These are used to set bounds
//disableEditing(textMinWidthFactor);
//disableEditing(textWidthFactor);
disableEditing(textPrecisionThreshold);
}
else
{
enableEditing(textCoordinateShiftFactor);
enableEditing(textSignalStrength);
enableEditing(textMinPhotons);
//enableEditing(textMinWidthFactor);
//enableEditing(textWidthFactor);
enableEditing(textPrecisionThreshold);
}
}
private void disableEditing(TextField textField)
{
textField.setEditable(false);
textField.setBackground(SystemColor.control);
}
private void enableEditing(TextField textField)
{
textField.setEditable(true);
textField.setBackground(SystemColor.white);
}
}