1330 lines
43 KiB
Java
1330 lines
43 KiB
Java
package sparseviewer.ui;
|
|
|
|
import sparseviewer.io.TxtTableReader;
|
|
import sparseviewer.io.MdaReader;
|
|
import sparseviewer.filter.DirectionFilter;
|
|
import sparseviewer.filter.DirectionMode;
|
|
import sparseviewer.filter.FilteredData;
|
|
import sparseviewer.interp.InterpolationResult;
|
|
import sparseviewer.interp.NearestInterpolator;
|
|
import sparseviewer.model.PlotMode;
|
|
import sparseviewer.model.ScanDataset;
|
|
import sparseviewer.model.ChannelUtils;
|
|
import sparseviewer.prefs.PluginPrefs;
|
|
import sparseviewer.interp.IdwInterpolator;
|
|
import sparseviewer.otf.DelayCorrection;
|
|
|
|
|
|
import ij.IJ;
|
|
import ij.ImagePlus;
|
|
import ij.process.FloatProcessor;
|
|
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.DefaultComboBoxModel;
|
|
import javax.swing.DefaultListModel;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JComboBox;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JFileChooser;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JList;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.TransferHandler;
|
|
import javax.swing.DropMode;
|
|
import javax.swing.Timer;
|
|
import javax.swing.event.DocumentEvent;
|
|
import javax.swing.event.DocumentListener;
|
|
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Dimension;
|
|
import java.awt.datatransfer.DataFlavor;
|
|
|
|
import java.io.File;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class MainFrame extends JFrame {
|
|
|
|
private DefaultListModel<File> fileListModel;
|
|
private JList<File> fileList;
|
|
|
|
private JComboBox<String> scanModeCombo;
|
|
private JComboBox<String> xCombo;
|
|
private JComboBox<String> yCombo;
|
|
private JComboBox<String> zCombo;
|
|
private JComboBox<String> styleCombo;
|
|
|
|
private JComboBox<String> display2DCombo;
|
|
private JComboBox<String> interpolationMethodCombo;
|
|
private JComboBox<String> inputFormatCombo;
|
|
private JTextField gridWidthField;
|
|
private JTextField gridHeightField;
|
|
private JTextField idwPowerField;
|
|
private JCheckBox autoRecomputeCheckBox;
|
|
private JButton recomputeButton;
|
|
private JButton sendToImageJButton;
|
|
private InterpolationResult lastInterpolationResult;
|
|
private boolean interpolationDirty = false;
|
|
private JButton addButton;
|
|
private JButton clearButton;
|
|
private JButton loadButton;
|
|
|
|
private PlotPanel plotPanel;
|
|
private JLabel datasetInfoLabel;
|
|
private boolean updatingControls = false;
|
|
private Timer interpolationParameterTimer;
|
|
private Timer directionParameterTimer;
|
|
private JComboBox<String> directionModeCombo;
|
|
private JComboBox<String> fastAxisCombo;
|
|
private JTextField directionEpsilonField;
|
|
private JCheckBox delayCorrectionCheckBox;
|
|
private JTextField delayMsField;
|
|
private JTextField sampleIntervalMsField;
|
|
|
|
|
|
private FilteredData lastFilteredData;
|
|
|
|
private ScanDataset dataset;
|
|
|
|
public MainFrame() {
|
|
super("Sparse Scan Viewer");
|
|
|
|
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
|
setPreferredSize(new Dimension(1100, 750));
|
|
|
|
buildUi();
|
|
pack();
|
|
setLocationRelativeTo(null);
|
|
}
|
|
private void buildUi() {
|
|
|
|
updatingControls = true;
|
|
|
|
try {
|
|
setLayout(new BorderLayout());
|
|
|
|
JPanel left = new JPanel();
|
|
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
|
|
left.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
|
left.setPreferredSize(new Dimension(330, 700));
|
|
|
|
fileListModel = new DefaultListModel<File>();
|
|
fileList = new JList<File>(fileListModel);
|
|
fileList.setVisibleRowCount(8);
|
|
fileList.setDragEnabled(false);
|
|
fileList.setDropMode(DropMode.ON);
|
|
fileList.setTransferHandler(new FileDropHandler());
|
|
|
|
JScrollPane fileScroll = new JScrollPane(fileList);
|
|
fileScroll.setBorder(BorderFactory.createTitledBorder("TXT files / drag & drop"));
|
|
|
|
addButton = new JButton("Add files...");
|
|
clearButton = new JButton("Clear");
|
|
loadButton = new JButton("Load");
|
|
|
|
inputFormatCombo = new JComboBox<String>(new String[]{
|
|
"TXT",
|
|
"MDA"
|
|
});
|
|
|
|
addButton.addActionListener(e -> chooseFiles());
|
|
clearButton.addActionListener(e -> clearFiles());
|
|
loadButton.addActionListener(e -> loadFiles());
|
|
|
|
JPanel fileButtons = new JPanel();
|
|
fileButtons.add(addButton);
|
|
fileButtons.add(clearButton);
|
|
fileButtons.add(loadButton);
|
|
|
|
scanModeCombo = new JComboBox<String>(new String[]{
|
|
"1D: Y(X)",
|
|
"2D: Z(X,Y)"
|
|
});
|
|
|
|
xCombo = new JComboBox<String>();
|
|
yCombo = new JComboBox<String>();
|
|
zCombo = new JComboBox<String>();
|
|
|
|
styleCombo = new JComboBox<String>(new String[]{
|
|
"Line + points",
|
|
"Line",
|
|
"Points",
|
|
"2D scatter"
|
|
});
|
|
|
|
display2DCombo = new JComboBox<String>(new String[]{
|
|
"2D scatter",
|
|
"Interpolated image"
|
|
});
|
|
|
|
interpolationMethodCombo = new JComboBox<String>(new String[]{
|
|
"Nearest",
|
|
"IDW"
|
|
});
|
|
|
|
directionModeCombo = new JComboBox<String>(new String[]{
|
|
"All directions",
|
|
"Positive fast-axis direction",
|
|
"Negative fast-axis direction",
|
|
});
|
|
fastAxisCombo = new JComboBox<String>();
|
|
directionEpsilonField = new JTextField("0.0");
|
|
|
|
delayCorrectionCheckBox = new JCheckBox("Enable delay correction", false);
|
|
sampleIntervalMsField = new JTextField("100.0");
|
|
delayMsField = new JTextField("0.0");
|
|
|
|
directionParameterTimer = new Timer(400, e -> updatePlotFromControls());
|
|
directionParameterTimer.setRepeats(false);
|
|
|
|
directionModeCombo.addActionListener(e -> updatePlotFromControls());
|
|
fastAxisCombo.addActionListener(e -> updatePlotFromControls());
|
|
installDirectionTextListener(directionEpsilonField);
|
|
|
|
delayCorrectionCheckBox.addActionListener(e -> updatePlotFromControls());
|
|
installDirectionTextListener(delayMsField);
|
|
installDirectionTextListener(sampleIntervalMsField);
|
|
|
|
gridWidthField = new JTextField("300");
|
|
gridHeightField = new JTextField("300");
|
|
idwPowerField = new JTextField("2.0");
|
|
|
|
autoRecomputeCheckBox = new JCheckBox("Auto recompute", true);
|
|
recomputeButton = new JButton("Update interpolated image");
|
|
sendToImageJButton = new JButton("Send to ImageJ");
|
|
|
|
interpolationParameterTimer = new Timer(600, new ActionListener() {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
handleInterpolationParameterChanged();
|
|
}
|
|
});
|
|
interpolationParameterTimer.setRepeats(false);
|
|
|
|
scanModeCombo.addActionListener(e -> updatePlotMode());
|
|
xCombo.addActionListener(e -> updatePlotFromControls());
|
|
yCombo.addActionListener(e -> updatePlotFromControls());
|
|
zCombo.addActionListener(e -> updatePlotFromControls());
|
|
styleCombo.addActionListener(e -> updatePlotFromControls());
|
|
|
|
display2DCombo.addActionListener(e -> updatePlotFromControls());
|
|
interpolationMethodCombo.addActionListener(e -> handleInterpolationParameterChanged());
|
|
installInterpolationTextListener(gridWidthField);
|
|
installInterpolationTextListener(gridHeightField);
|
|
installInterpolationTextListener(idwPowerField);
|
|
|
|
directionModeCombo.addActionListener(e -> updatePlotFromControls());
|
|
fastAxisCombo.addActionListener(e -> updatePlotFromControls());
|
|
|
|
autoRecomputeCheckBox.addActionListener(e -> {
|
|
PluginPrefs.setInt("autoRecompute", autoRecomputeCheckBox.isSelected() ? 1 : 0);
|
|
PluginPrefs.save();
|
|
|
|
if (autoRecomputeCheckBox.isSelected() && interpolationDirty) {
|
|
recomputeInterpolation();
|
|
}
|
|
|
|
updateButtonStates();
|
|
});
|
|
autoRecomputeCheckBox.addActionListener(e -> recomputeInterpolation());
|
|
sendToImageJButton.addActionListener(e -> sendInterpolationToImageJ());
|
|
|
|
recomputeButton.addActionListener(e -> {
|
|
forceInterpolatedDisplayIfNeeded();
|
|
recomputeInterpolation();
|
|
updateButtonStates();
|
|
});
|
|
|
|
|
|
left.add(fileScroll);
|
|
left.add(fileButtons);
|
|
|
|
datasetInfoLabel = new JLabel("No dataset loaded.");
|
|
left.add(datasetInfoLabel);
|
|
|
|
left.add(new JLabel("Input format"));
|
|
left.add(inputFormatCombo);
|
|
|
|
left.add(new JLabel("Scan mode"));
|
|
left.add(scanModeCombo);
|
|
|
|
left.add(new JLabel("X channel"));
|
|
left.add(xCombo);
|
|
|
|
left.add(new JLabel("Y channel"));
|
|
left.add(yCombo);
|
|
|
|
left.add(new JLabel("Z / intensity channel"));
|
|
left.add(zCombo);
|
|
|
|
left.add(new JLabel("Display style"));
|
|
left.add(styleCombo);
|
|
|
|
left.add(new JLabel("2D display"));
|
|
left.add(display2DCombo);
|
|
|
|
left.add(new JLabel("Interpolation method"));
|
|
left.add(interpolationMethodCombo);
|
|
|
|
left.add(new JLabel("Grid width"));
|
|
left.add(gridWidthField);
|
|
|
|
left.add(new JLabel("Grid height"));
|
|
left.add(gridHeightField);
|
|
|
|
left.add(new JLabel("IDW power"));
|
|
left.add(idwPowerField);
|
|
|
|
left.add(autoRecomputeCheckBox);
|
|
left.add(recomputeButton);
|
|
left.add(sendToImageJButton);
|
|
|
|
left.add(new JLabel("OTF / direction filter"));left.add(new JLabel(""));
|
|
left.add(directionModeCombo);
|
|
|
|
left.add(new JLabel("Fast axis channel"));
|
|
left.add(fastAxisCombo);
|
|
|
|
left.add(new JLabel("Direction epsilon"));
|
|
left.add(directionEpsilonField);
|
|
|
|
left.add(new JLabel("Delay correction"));
|
|
left.add(delayCorrectionCheckBox);
|
|
|
|
left.add(new JLabel("Sample interval [ms]"));
|
|
left.add(sampleIntervalMsField);
|
|
|
|
left.add(new JLabel("Delay [ms]"));
|
|
left.add(delayMsField);
|
|
|
|
|
|
plotPanel = new PlotPanel();
|
|
|
|
add(left, BorderLayout.WEST);
|
|
add(plotPanel, BorderLayout.CENTER);
|
|
|
|
} finally {
|
|
updatingControls = false;
|
|
}
|
|
}
|
|
private void chooseFiles() {
|
|
JFileChooser chooser = new JFileChooser();
|
|
chooser.setMultiSelectionEnabled(true);
|
|
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
|
|
|
int result = chooser.showOpenDialog(this);
|
|
|
|
if (result != JFileChooser.APPROVE_OPTION) {
|
|
return;
|
|
}
|
|
|
|
File[] files = chooser.getSelectedFiles();
|
|
|
|
for (int i = 0; i < files.length; i++) {
|
|
addFile(files[i]);
|
|
}
|
|
}
|
|
private void addFile(File file) {
|
|
if (file == null) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < fileListModel.size(); i++) {
|
|
if (fileListModel.get(i).equals(file)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
fileListModel.addElement(file);
|
|
|
|
if (inputFormatCombo != null) {
|
|
String name = file.getName().toLowerCase();
|
|
|
|
if (name.endsWith(".mda")) {
|
|
inputFormatCombo.setSelectedItem("MDA");
|
|
} else if (name.endsWith(".txt")) {
|
|
inputFormatCombo.setSelectedItem("TXT");
|
|
}
|
|
}
|
|
|
|
if (!file.exists()) {
|
|
return;
|
|
}
|
|
|
|
if (inputFormatCombo != null) {
|
|
String name = file.getName().toLowerCase();
|
|
|
|
if (name.endsWith(".mda")) {
|
|
inputFormatCombo.setSelectedItem("MDA");
|
|
} else if (name.endsWith(".txt")) {
|
|
inputFormatCombo.setSelectedItem("TXT");
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < fileListModel.size(); i++) {
|
|
if (fileListModel.get(i).equals(file)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
fileListModel.addElement(file);
|
|
}
|
|
private void clearFiles() {
|
|
fileListModel.clear();
|
|
dataset = null;
|
|
lastInterpolationResult = null;
|
|
interpolationDirty = false;
|
|
|
|
clearCombos();
|
|
|
|
if (plotPanel != null) {
|
|
plotPanel.clear();
|
|
}
|
|
|
|
if (datasetInfoLabel != null) {
|
|
datasetInfoLabel.setText("No dataset loaded.");
|
|
}
|
|
}
|
|
private void loadFiles() {
|
|
try {
|
|
List<File> files = new ArrayList<File>();
|
|
|
|
for (int i = 0; i < fileListModel.size(); i++) {
|
|
files.add(fileListModel.get(i));
|
|
}
|
|
|
|
String inputFormat = determineInputFormat(files);
|
|
|
|
IJ.log("Sparse Scan Viewer: input format = " + inputFormat);
|
|
|
|
if ("MDA".equals(inputFormat)) {
|
|
dataset = MdaReader.readFiles(files);
|
|
} else {
|
|
dataset = TxtTableReader.readFiles(files);
|
|
}
|
|
|
|
lastInterpolationResult = null;
|
|
interpolationDirty = false;
|
|
|
|
populateChannels(dataset);
|
|
restoreSelections();
|
|
|
|
if (datasetInfoLabel != null) {populateChannels(dataset);
|
|
datasetInfoLabel.setText(
|
|
dataset.getSourceFiles().size() + " file(s), "
|
|
+ dataset.getRowCount() + " rows, "
|
|
+ dataset.getChannelCount() + " channels"
|
|
);
|
|
}
|
|
|
|
updatePlotFromControls();
|
|
|
|
} catch (Exception ex) {
|
|
|
|
String message = ex.getMessage();
|
|
|
|
if (message == null) {
|
|
message = ex.getClass().getName();
|
|
}
|
|
|
|
IJ.error("Could not load files:\n"
|
|
+ ex.getClass().getName()
|
|
+ "\n"
|
|
+ String.valueOf(ex.getMessage()));
|
|
ex.printStackTrace();
|
|
|
|
}
|
|
}
|
|
private String determineInputFormat(List<File> files) {
|
|
if (files != null && !files.isEmpty()) {
|
|
boolean allMda = true;
|
|
boolean allTxt = true;
|
|
|
|
for (int i = 0; i < files.size(); i++) {
|
|
File f = files.get(i);
|
|
|
|
if (f == null) {
|
|
allMda = false;
|
|
allTxt = false;
|
|
continue;
|
|
}
|
|
|
|
String name = f.getName().toLowerCase();
|
|
|
|
if (!name.endsWith(".mda")) {
|
|
allMda = false;
|
|
}
|
|
|
|
if (!name.endsWith(".txt")) {
|
|
allTxt = false;
|
|
}
|
|
}
|
|
|
|
if (allMda) {
|
|
if (inputFormatCombo != null) {
|
|
inputFormatCombo.setSelectedItem("MDA");
|
|
}
|
|
|
|
return "MDA";
|
|
}
|
|
|
|
if (allTxt) {
|
|
if (inputFormatCombo != null) {
|
|
inputFormatCombo.setSelectedItem("TXT");
|
|
}
|
|
|
|
return "TXT";
|
|
}
|
|
}
|
|
|
|
String inputFormat = "TXT";
|
|
|
|
if (inputFormatCombo != null && inputFormatCombo.getSelectedItem() != null) {
|
|
inputFormat = inputFormatCombo.getSelectedItem().toString();
|
|
}
|
|
|
|
return inputFormat;
|
|
}
|
|
private int clamp(int v, int min, int max) {
|
|
if (max < min) {
|
|
return min;
|
|
}
|
|
|
|
if (v < min) {
|
|
return min;
|
|
}
|
|
|
|
if (v > max) {
|
|
return max;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
private void clearCombos() {
|
|
xCombo.setModel(new DefaultComboBoxModel<String>());
|
|
yCombo.setModel(new DefaultComboBoxModel<String>());
|
|
zCombo.setModel(new DefaultComboBoxModel<String>());
|
|
|
|
if (fastAxisCombo != null) {
|
|
fastAxisCombo.setModel(new DefaultComboBoxModel<String>());
|
|
}
|
|
|
|
}
|
|
private void delayedDirectionParameterChanged() {
|
|
if (updatingControls) {
|
|
return;
|
|
}
|
|
|
|
if (directionParameterTimer != null) {
|
|
directionParameterTimer.restart();
|
|
}
|
|
}
|
|
private void delayedInterpolationParameterChanged() {
|
|
if (updatingControls) {
|
|
return;
|
|
}
|
|
|
|
if (interpolationParameterTimer != null) {
|
|
interpolationParameterTimer.restart();
|
|
}
|
|
}
|
|
|
|
private void forceInterpolatedDisplayIfNeeded() {
|
|
if (display2DCombo != null && display2DCombo.getSelectedIndex() != 1)
|
|
{
|
|
display2DCombo.setSelectedIndex(1);
|
|
}
|
|
}
|
|
private FilteredData getFilteredDataForCurrentSelection() {
|
|
if (dataset == null) {
|
|
return null;
|
|
}
|
|
|
|
int xIdx = xCombo.getSelectedIndex();
|
|
int yIdx = yCombo.getSelectedIndex();
|
|
int zIdx = zCombo.getSelectedIndex();
|
|
|
|
if (xIdx < 0 || yIdx < 0 || zIdx < 0) {
|
|
return null;
|
|
}
|
|
|
|
double[] xRaw = dataset.getColumn(xIdx);
|
|
double[] yRaw = dataset.getColumn(yIdx);
|
|
double[] zRaw = dataset.getColumn(zIdx);
|
|
|
|
double[] xUsed = xRaw;
|
|
double[] yUsed = yRaw;
|
|
|
|
double[] fastAxis = null;
|
|
|
|
if (fastAxisCombo != null && fastAxisCombo.getSelectedIndex() >= 0) {
|
|
fastAxis = dataset.getColumn(fastAxisCombo.getSelectedIndex());
|
|
}
|
|
|
|
boolean delayEnabled = delayCorrectionCheckBox != null
|
|
&& delayCorrectionCheckBox.isSelected();
|
|
|
|
if (delayEnabled) {
|
|
double sampleIntervalMs = parseDouble(
|
|
sampleIntervalMsField != null ? sampleIntervalMsField.getText() : "",
|
|
100.0
|
|
);
|
|
|
|
double delayMs = parseDouble(
|
|
delayMsField != null ? delayMsField.getText() : "",
|
|
0.0
|
|
);
|
|
|
|
if (sampleIntervalMs <= 0.0) {
|
|
IJ.showStatus("Invalid sample interval. Delay correction skipped.");
|
|
} else {
|
|
double shiftSamples = delayMs / sampleIntervalMs;
|
|
|
|
DelayCorrection.CorrectedXY corrected =
|
|
DelayCorrection.correctXY(xRaw, yRaw, shiftSamples);
|
|
|
|
xUsed = corrected.getX();
|
|
yUsed = corrected.getY();
|
|
|
|
if (fastAxis != null) {
|
|
fastAxis = DelayCorrection.shiftArray(fastAxis, shiftSamples);
|
|
}
|
|
|
|
IJ.showStatus("Delay correction: "
|
|
+ delayMs + " ms / "
|
|
+ sampleIntervalMs + " ms = "
|
|
+ String.format("%.4f", shiftSamples)
|
|
+ " samples");
|
|
}
|
|
}
|
|
|
|
DirectionMode mode = getSelectedDirectionMode();
|
|
|
|
double epsilon = 0.0;
|
|
|
|
if (directionEpsilonField != null) {
|
|
epsilon = parseDouble(directionEpsilonField.getText(), 0.0);
|
|
}
|
|
|
|
lastFilteredData = DirectionFilter.apply(
|
|
xUsed,
|
|
yUsed,
|
|
zRaw,
|
|
fastAxis,
|
|
mode,
|
|
epsilon
|
|
);
|
|
|
|
IJ.showStatus("Filtered points: "
|
|
+ lastFilteredData.size()
|
|
+ " / "
|
|
+ dataset.getRowCount());
|
|
|
|
return lastFilteredData;
|
|
}
|
|
private DirectionMode getSelectedDirectionMode() {
|
|
if (directionModeCombo == null) {
|
|
return DirectionMode.ALL;
|
|
}
|
|
|
|
int idx = directionModeCombo.getSelectedIndex();
|
|
|
|
if (idx == 1) {
|
|
return DirectionMode.POSITIVE;
|
|
}
|
|
|
|
if (idx == 2) {
|
|
return DirectionMode.NEGATIVE;
|
|
}
|
|
|
|
return DirectionMode.ALL;
|
|
}
|
|
private void handleInterpolationParameterChanged() {
|
|
PluginPrefs.setString("idwPower", idwPowerField.getText());
|
|
|
|
// Parameter speichern
|
|
PluginPrefs.setString("gridWidth", gridWidthField.getText());
|
|
|
|
if (interpolationMethodCombo != null) {
|
|
PluginPrefs.setInt("interpMethod", interpolationMethodCombo.getSelectedIndex());
|
|
}
|
|
|
|
if (autoRecomputeCheckBox != null) {
|
|
PluginPrefs.setInt("autoRecompute", autoRecomputeCheckBox.isSelected() ? 1 : 0);
|
|
}
|
|
|
|
PluginPrefs.save();
|
|
|
|
boolean in2DMode = scanModeCombo != null && scanModeCombo.getSelectedIndex() == 1;
|
|
boolean interpolatedDisplay = display2DCombo != null && display2DCombo.getSelectedIndex() == 1;
|
|
|
|
if (!in2DMode || !interpolatedDisplay) {
|
|
return;
|
|
}
|
|
|
|
boolean auto = autoRecomputeCheckBox == null || autoRecomputeCheckBox.isSelected();
|
|
|
|
if (auto) {
|
|
recomputeInterpolation();
|
|
} else {
|
|
interpolationDirty = true;
|
|
IJ.showStatus("Interpolation parameters changed. Press Update interpolated image.");
|
|
}
|
|
|
|
if (updatingControls) {
|
|
return;
|
|
}
|
|
|
|
if (dataset == null) {
|
|
return;
|
|
}
|
|
updateButtonStates();
|
|
}
|
|
private void installInterpolationTextListener(JTextField field) {
|
|
|
|
field.addActionListener(e -> handleInterpolationParameterChanged());
|
|
|
|
field.getDocument().addDocumentListener(new DocumentListener() {
|
|
|
|
@Override
|
|
public void insertUpdate(DocumentEvent e) {
|
|
delayedInterpolationParameterChanged();
|
|
}
|
|
|
|
@Override
|
|
public void removeUpdate(DocumentEvent e) {
|
|
delayedInterpolationParameterChanged();
|
|
}
|
|
|
|
@Override
|
|
public void changedUpdate(DocumentEvent e) {
|
|
delayedInterpolationParameterChanged();
|
|
}
|
|
});
|
|
}
|
|
private void installDirectionTextListener(JTextField field) {
|
|
|
|
field.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
|
|
@Override
|
|
public void insertUpdate(javax.swing.event.DocumentEvent e) {
|
|
delayedDirectionParameterChanged();
|
|
}
|
|
@Override
|
|
public void removeUpdate(javax.swing.event.DocumentEvent e) {
|
|
delayedDirectionParameterChanged();
|
|
}
|
|
|
|
@Override
|
|
public void changedUpdate(javax.swing.event.DocumentEvent e) {
|
|
delayedDirectionParameterChanged();
|
|
}
|
|
});
|
|
|
|
}
|
|
private void populateChannels(ScanDataset ds) {
|
|
updatingControls = true;
|
|
|
|
try {
|
|
String[] names = ds.getChannelNames();
|
|
|
|
xCombo.setModel(new DefaultComboBoxModel<String>(names));
|
|
yCombo.setModel(new DefaultComboBoxModel<String>(names));
|
|
zCombo.setModel(new DefaultComboBoxModel<String>(names));
|
|
|
|
if (fastAxisCombo != null) {
|
|
fastAxisCombo.setModel(new DefaultComboBoxModel<String>(names));
|
|
}
|
|
|
|
if (fastAxisCombo != null && names.length > 0) {fastAxisCombo.setSelectedIndex(0);
|
|
}
|
|
|
|
|
|
if (names.length > 0) {
|
|
xCombo.setSelectedIndex(0);
|
|
}
|
|
|
|
if (names.length > 1) {
|
|
yCombo.setSelectedIndex(1);
|
|
}
|
|
|
|
if (names.length > 2) {
|
|
zCombo.setSelectedIndex(2);
|
|
}
|
|
} finally {
|
|
updatingControls = false;
|
|
}
|
|
}
|
|
private void recomputeInterpolation() {
|
|
if (updatingControls) {
|
|
return;
|
|
}
|
|
|
|
if (dataset == null) {
|
|
IJ.error("No dataset loaded.");
|
|
return;
|
|
}
|
|
|
|
if (scanModeCombo.getSelectedIndex() != 1) {
|
|
IJ.error("Interpolation is only available in 2D mode.");
|
|
return;
|
|
}
|
|
|
|
if (display2DCombo.getSelectedIndex() != 1) {
|
|
IJ.error("Set 2D display to 'Interpolated image' first.");
|
|
return;
|
|
}
|
|
|
|
int xIdx = xCombo.getSelectedIndex();
|
|
int yIdx = yCombo.getSelectedIndex();
|
|
int zIdx = zCombo.getSelectedIndex();
|
|
|
|
FilteredData fd = getFilteredDataForCurrentSelection();
|
|
|
|
if (fd == null || fd.size() == 0) {
|
|
IJ.error("No points available after direction filtering.");
|
|
return;
|
|
}
|
|
|
|
if (xIdx < 0 || yIdx < 0 || zIdx < 0) {
|
|
IJ.error("Please select X, Y and Z channels.");
|
|
return;
|
|
}
|
|
|
|
int gridWidth = parseInt(gridWidthField.getText(), 300);
|
|
int gridHeight = parseInt(gridHeightField.getText(), 300);
|
|
double power = parseDouble(idwPowerField.getText(), 2.0);
|
|
|
|
if (gridWidth < 2) {
|
|
gridWidth = 2;
|
|
gridWidthField.setText(String.valueOf(gridWidth));
|
|
}
|
|
|
|
if (gridHeight < 2) {
|
|
gridHeight = 2;
|
|
gridHeightField.setText(String.valueOf(gridHeight));
|
|
}
|
|
|
|
int methodIndex = interpolationMethodCombo.getSelectedIndex();
|
|
|
|
PluginPrefs.setString("gridWidth", gridWidthField.getText());
|
|
PluginPrefs.setString("gridHeight", gridHeightField.getText());
|
|
PluginPrefs.setString("idwPower", idwPowerField.getText());
|
|
PluginPrefs.setInt("interpMethod", methodIndex);
|
|
PluginPrefs.save();
|
|
|
|
IJ.showStatus("Interpolating...");
|
|
IJ.showProgress(0.1);
|
|
|
|
try {
|
|
if (methodIndex == 0) {
|
|
lastInterpolationResult = NearestInterpolator.interpolate(
|
|
fd.getX(),
|
|
fd.getY(),
|
|
fd.getZ(),
|
|
gridWidth,
|
|
gridHeight
|
|
);
|
|
} else {
|
|
lastInterpolationResult = IdwInterpolator.interpolate(
|
|
fd.getX(),
|
|
fd.getY(),
|
|
fd.getZ(),
|
|
gridWidth,
|
|
gridHeight,
|
|
power
|
|
);
|
|
}
|
|
|
|
interpolationDirty = false;
|
|
|
|
plotPanel.set2DImageData(
|
|
fd.getX(),
|
|
fd.getY(),
|
|
fd.getZ(),
|
|
dataset.getChannelName(xIdx),
|
|
dataset.getChannelName(yIdx),
|
|
dataset.getChannelName(zIdx),
|
|
lastInterpolationResult
|
|
);
|
|
|
|
updateButtonStates();
|
|
|
|
IJ.showProgress(1.0);
|
|
IJ.showStatus("Interpolation done.");
|
|
|
|
} catch (Exception ex) {
|
|
IJ.showProgress(1.0);
|
|
IJ.showStatus("");
|
|
|
|
IJ.error("Interpolation failed:\n"
|
|
+ ex.getClass().getName()
|
|
+ "\n"
|
|
+ String.valueOf(ex.getMessage()));
|
|
|
|
ex.printStackTrace();
|
|
}
|
|
}
|
|
private void restoreSelections() {
|
|
if (dataset == null) {
|
|
return;
|
|
}
|
|
|
|
updatingControls = true;
|
|
|
|
try {
|
|
String[] names = dataset.getChannelNames();
|
|
|
|
int storedXIndex = PluginPrefs.getInt("xChannel", 0);
|
|
int storedYIndex = PluginPrefs.getInt("yChannel", Math.min(1, dataset.getChannelCount() - 1));
|
|
int storedZIndex = PluginPrefs.getInt("zChannel", Math.min(2, dataset.getChannelCount() - 1));
|
|
|
|
String storedXName = PluginPrefs.getString("xChannelName", "");
|
|
String storedYName = PluginPrefs.getString("yChannelName", "");
|
|
String storedZName = PluginPrefs.getString("zChannelName", "");
|
|
|
|
int x = ChannelUtils.resolveChannelIndex(
|
|
names,
|
|
storedXName,
|
|
storedXIndex,
|
|
0
|
|
);
|
|
|
|
int y = ChannelUtils.resolveChannelIndex(
|
|
names,
|
|
storedYName,
|
|
storedYIndex,
|
|
Math.min(1, dataset.getChannelCount() - 1)
|
|
);
|
|
|
|
int z = ChannelUtils.resolveChannelIndex(
|
|
names,
|
|
storedZName,
|
|
storedZIndex,
|
|
Math.min(2, dataset.getChannelCount() - 1)
|
|
);
|
|
|
|
int mode = PluginPrefs.getInt("scanMode", 0);
|
|
int style = PluginPrefs.getInt("style", 0);
|
|
|
|
int directionMode = PluginPrefs.getInt("directionMode", 0);
|
|
int storedFastAxisIndex = PluginPrefs.getInt("fastAxisChannel", 0);
|
|
String storedFastAxisName = PluginPrefs.getString("fastAxisChannelName", "");
|
|
|
|
int fastAxis = ChannelUtils.resolveChannelIndex(
|
|
names,
|
|
storedFastAxisName,
|
|
storedFastAxisIndex,
|
|
0
|
|
);
|
|
|
|
if (delayCorrectionCheckBox != null) {
|
|
boolean enabled = PluginPrefs.getInt("delayCorrectionEnabled", 0) != 0;
|
|
delayCorrectionCheckBox.setSelected(enabled);
|
|
}
|
|
|
|
if (sampleIntervalMsField != null) {
|
|
sampleIntervalMsField.setText(
|
|
PluginPrefs.getString("sampleIntervalMs", "100.0")
|
|
);
|
|
}
|
|
|
|
if (delayMsField != null) {
|
|
delayMsField.setText(
|
|
PluginPrefs.getString("delayMs", "0.0")
|
|
);
|
|
}
|
|
|
|
if (directionModeCombo != null) {
|
|
directionModeCombo.setSelectedIndex(
|
|
clamp(directionMode, 0, directionModeCombo.getItemCount() - 1)
|
|
);
|
|
}
|
|
|
|
if (fastAxisCombo != null && fastAxis >= 0) {
|
|
fastAxisCombo.setSelectedIndex(fastAxis);
|
|
}
|
|
|
|
if (directionEpsilonField != null) {
|
|
directionEpsilonField.setText(
|
|
PluginPrefs.getString("directionEpsilon", "0.0")
|
|
);
|
|
}
|
|
|
|
|
|
scanModeCombo.setSelectedIndex(
|
|
clamp(mode, 0, scanModeCombo.getItemCount() - 1)
|
|
);
|
|
|
|
styleCombo.setSelectedIndex(
|
|
clamp(style, 0, styleCombo.getItemCount() - 1)
|
|
);
|
|
|
|
if (x >= 0) {
|
|
xCombo.setSelectedIndex(x);
|
|
}
|
|
|
|
if (y >= 0) {
|
|
yCombo.setSelectedIndex(y);
|
|
}
|
|
|
|
if (z >= 0) {
|
|
zCombo.setSelectedIndex(z);
|
|
}
|
|
|
|
int display2D = PluginPrefs.getInt("display2D", 0);
|
|
int interpMethod = PluginPrefs.getInt("interpMethod", 1);
|
|
|
|
display2DCombo.setSelectedIndex(
|
|
clamp(display2D, 0, display2DCombo.getItemCount() - 1)
|
|
);
|
|
|
|
interpolationMethodCombo.setSelectedIndex(
|
|
clamp(interpMethod, 0, interpolationMethodCombo.getItemCount() - 1)
|
|
);
|
|
|
|
gridWidthField.setText(PluginPrefs.getString("gridWidth", "300"));
|
|
gridHeightField.setText(PluginPrefs.getString("gridHeight", "300"));
|
|
idwPowerField.setText(PluginPrefs.getString("idwPower", "2.0"));
|
|
|
|
boolean autoRecompute = PluginPrefs.getInt("autoRecompute", 1) != 0;
|
|
|
|
if (autoRecomputeCheckBox != null) {
|
|
autoRecomputeCheckBox.setSelected(autoRecompute);
|
|
}
|
|
|
|
|
|
} finally {
|
|
updatingControls = false;
|
|
}
|
|
}
|
|
private void sendInterpolationToImageJ() {
|
|
if (lastInterpolationResult == null) {
|
|
IJ.error("No interpolated image available.");
|
|
return;
|
|
}
|
|
|
|
int width = lastInterpolationResult.getGrid().getWidth();
|
|
int height = lastInterpolationResult.getGrid().getHeight();
|
|
|
|
float[] src = lastInterpolationResult.getPixels();
|
|
float[] copy = new float[src.length];
|
|
|
|
System.arraycopy(src, 0, copy, 0, src.length);
|
|
|
|
FloatProcessor fp = new FloatProcessor(width, height, copy);
|
|
|
|
ImagePlus imp = new ImagePlus("Sparse Scan Interpolated", fp);
|
|
|
|
// Grid metadata
|
|
imp.setProperty(
|
|
"SparseScan.XMin",
|
|
String.valueOf(lastInterpolationResult.getGrid().getXMin())
|
|
);
|
|
|
|
imp.setProperty(
|
|
"SparseScan.XMax",
|
|
String.valueOf(lastInterpolationResult.getGrid().getXMax())
|
|
);
|
|
|
|
imp.setProperty(
|
|
"SparseScan.YMin",
|
|
String.valueOf(lastInterpolationResult.getGrid().getYMin())
|
|
);
|
|
|
|
imp.setProperty(
|
|
"SparseScan.YMax",
|
|
String.valueOf(lastInterpolationResult.getGrid().getYMax())
|
|
);
|
|
|
|
// Direction filter metadata
|
|
if (directionModeCombo != null && directionModeCombo.getSelectedItem() != null) {
|
|
imp.setProperty(
|
|
"SparseScan.DirectionMode",
|
|
directionModeCombo.getSelectedItem().toString()
|
|
);
|
|
}
|
|
|
|
if (fastAxisCombo != null
|
|
&& fastAxisCombo.getSelectedIndex() >= 0
|
|
&& dataset != null) {
|
|
imp.setProperty(
|
|
"SparseScan.FastAxis",
|
|
dataset.getChannelName(fastAxisCombo.getSelectedIndex())
|
|
);
|
|
}
|
|
|
|
if (directionEpsilonField != null) {
|
|
imp.setProperty(
|
|
"SparseScan.DirectionEpsilon",
|
|
directionEpsilonField.getText()
|
|
);
|
|
}
|
|
|
|
if (delayCorrectionCheckBox != null) {
|
|
imp.setProperty(
|
|
"SparseScan.DelayCorrectionEnabled",
|
|
delayCorrectionCheckBox.isSelected()
|
|
);
|
|
}
|
|
if (sampleIntervalMsField != null) {
|
|
imp.setProperty(
|
|
"SparseScan.SampleIntervalMs",
|
|
sampleIntervalMsField.getText()
|
|
);
|
|
}
|
|
if (delayMsField != null) {
|
|
imp.setProperty(
|
|
"SparseScan.DelayMs",
|
|
delayMsField.getText()
|
|
);
|
|
}
|
|
|
|
imp.show();
|
|
}
|
|
private void updatePlotMode() {
|
|
updatePlotFromControls();
|
|
}
|
|
private void updatePlotFromControls() {
|
|
|
|
if (updatingControls) {
|
|
return;
|
|
}
|
|
|
|
if (dataset == null) {
|
|
return;
|
|
}
|
|
|
|
if (display2DCombo == null
|
|
|| interpolationMethodCombo == null
|
|
|| gridWidthField == null
|
|
|| gridHeightField == null
|
|
|| idwPowerField == null) {
|
|
return;
|
|
}
|
|
|
|
if (xCombo.getSelectedIndex() < 0 || yCombo.getSelectedIndex() < 0) {
|
|
return;
|
|
}
|
|
|
|
int xIdx = xCombo.getSelectedIndex();
|
|
int yIdx = yCombo.getSelectedIndex();
|
|
int zIdx = zCombo.getSelectedIndex();
|
|
|
|
int scanMode = scanModeCombo.getSelectedIndex();
|
|
int style = styleCombo.getSelectedIndex();
|
|
|
|
int display2D = display2DCombo.getSelectedIndex();
|
|
int interpMethod = interpolationMethodCombo.getSelectedIndex();
|
|
|
|
PluginPrefs.setInt("xChannel", xIdx);
|
|
PluginPrefs.setInt("yChannel", yIdx);
|
|
PluginPrefs.setInt("zChannel", zIdx);
|
|
PluginPrefs.setInt("scanMode", scanMode);
|
|
PluginPrefs.setInt("style", style);
|
|
|
|
PluginPrefs.setInt("display2D", display2D);
|
|
PluginPrefs.setInt("interpMethod", interpMethod);
|
|
PluginPrefs.setString("gridWidth", gridWidthField.getText());
|
|
PluginPrefs.setString("gridHeight", gridHeightField.getText());
|
|
PluginPrefs.setString("idwPower", idwPowerField.getText());
|
|
|
|
PluginPrefs.setInt(
|
|
"autoRecompute",
|
|
autoRecomputeCheckBox != null && autoRecomputeCheckBox.isSelected() ? 1 : 0
|
|
);
|
|
|
|
|
|
PluginPrefs.setString("xChannelName", dataset.getChannelName(xIdx));
|
|
PluginPrefs.setString("yChannelName", dataset.getChannelName(yIdx));
|
|
|
|
if (zIdx >= 0) {
|
|
PluginPrefs.setString("zChannelName", dataset.getChannelName(zIdx));
|
|
}
|
|
|
|
PluginPrefs.save();
|
|
PluginPrefs.setInt("display2D", display2DCombo.getSelectedIndex());
|
|
PluginPrefs.setInt("display2D", interpolationMethodCombo.getSelectedIndex());
|
|
PluginPrefs.setString("gridWidth", gridWidthField.getText());
|
|
PluginPrefs.setString("gridHeight", gridHeightField.getText());
|
|
PluginPrefs.setString("idwPower", idwPowerField.getText());
|
|
|
|
if (directionModeCombo != null) {
|
|
PluginPrefs.setInt("directionMode", directionModeCombo.getSelectedIndex());
|
|
}
|
|
|
|
if (fastAxisCombo != null && fastAxisCombo.getSelectedIndex() >= 0) {
|
|
PluginPrefs.setInt("fastAxisChannel", fastAxisCombo.getSelectedIndex());
|
|
PluginPrefs.setString(
|
|
"fastAxisChannelName",
|
|
dataset.getChannelName(fastAxisCombo.getSelectedIndex())
|
|
);
|
|
}
|
|
|
|
if (directionEpsilonField != null) {
|
|
PluginPrefs.setString("directionEpsilon", directionEpsilonField.getText());
|
|
}
|
|
|
|
if (delayCorrectionCheckBox != null) {
|
|
PluginPrefs.setInt(
|
|
"delayCorrectionEnabled",
|
|
delayCorrectionCheckBox.isSelected() ? 1 : 0
|
|
);
|
|
}
|
|
|
|
if (sampleIntervalMsField != null) {
|
|
PluginPrefs.setString("sampleIntervalMs", sampleIntervalMsField.getText());
|
|
}
|
|
|
|
if (delayMsField != null) {
|
|
PluginPrefs.setString("delayMs", delayMsField.getText());
|
|
}
|
|
|
|
if (scanMode == 0) {
|
|
PlotMode mode;
|
|
|
|
if (style == 1) {
|
|
mode = PlotMode.PLOT_1D_LINE;
|
|
} else if (style == 2) {
|
|
mode = PlotMode.PLOT_1D_POINTS;
|
|
} else {
|
|
mode = PlotMode.PLOT_1D_LINE_POINTS;
|
|
}
|
|
|
|
plotPanel.set1DData(
|
|
dataset.getColumn(xIdx),
|
|
dataset.getColumn(yIdx),
|
|
dataset.getChannelName(xIdx),
|
|
dataset.getChannelName(yIdx),
|
|
mode
|
|
);
|
|
} else {
|
|
if (zIdx < 0) {
|
|
return;
|
|
}
|
|
|
|
if (display2D == 0) {
|
|
lastInterpolationResult = null;
|
|
interpolationDirty = false;
|
|
|
|
FilteredData fd = getFilteredDataForCurrentSelection();
|
|
|
|
if (fd == null || fd.size() == 0) {
|
|
IJ.showStatus("No points after direction filtering.");
|
|
return;
|
|
}
|
|
|
|
plotPanel.set2DData(
|
|
fd.getX(),
|
|
fd.getY(),
|
|
fd.getZ(),
|
|
dataset.getChannelName(xIdx),
|
|
dataset.getChannelName(yIdx),
|
|
dataset.getChannelName(zIdx)
|
|
);
|
|
|
|
} else {
|
|
boolean autoRecompute = autoRecomputeCheckBox == null
|
|
|| autoRecomputeCheckBox.isSelected();
|
|
|
|
if (autoRecompute) {
|
|
recomputeInterpolation();
|
|
} else {
|
|
interpolationDirty = true;
|
|
|
|
if (lastInterpolationResult == null) {
|
|
IJ.showStatus("No interpolated image yet. Press Update interpolated image.");
|
|
} else {
|
|
IJ.showStatus("Interpolation settings changed. Press Update interpolated image.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
updateButtonStates();
|
|
}
|
|
private void updateButtonStates() {
|
|
boolean canInterpolate = dataset != null
|
|
&& scanModeCombo != null
|
|
&& scanModeCombo.getSelectedIndex() == 1
|
|
&& display2DCombo != null
|
|
&& display2DCombo.getSelectedIndex() == 1;
|
|
|
|
if (recomputeButton != null) {
|
|
recomputeButton.setEnabled(canInterpolate);
|
|
}
|
|
|
|
if (sendToImageJButton != null) {
|
|
sendToImageJButton.setEnabled(lastInterpolationResult != null);
|
|
}
|
|
}
|
|
|
|
// Helpers
|
|
private class FileDropHandler extends TransferHandler {
|
|
|
|
@Override
|
|
public boolean canImport(TransferSupport support) {
|
|
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
|
|
}
|
|
|
|
@Override
|
|
public boolean importData(TransferSupport support) {
|
|
if (!canImport(support)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
Object data = support.getTransferable()
|
|
.getTransferData(DataFlavor.javaFileListFlavor);
|
|
|
|
if (!(data instanceof List)) {
|
|
return false;
|
|
}
|
|
|
|
List<?> dropped = (List<?>) data;
|
|
|
|
for (int i = 0; i < dropped.size(); i++) {
|
|
Object obj = dropped.get(i);
|
|
|
|
if (obj instanceof File) {
|
|
addFile((File) obj);
|
|
}
|
|
}
|
|
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
loadFiles();
|
|
}
|
|
});
|
|
|
|
return true;
|
|
|
|
} catch (Exception e) {
|
|
IJ.error("Drag & drop failed:\n" + e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
private int parseInt(String text, int defaultValue) {
|
|
try {
|
|
return Integer.parseInt(text.trim());
|
|
} catch (Exception e) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
private double parseDouble(String text, double defaultValue) {
|
|
try {
|
|
return Double.parseDouble(text.trim());
|
|
} catch (Exception e) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
} |