Files
x11ma/plugins/fiji/sparseviewer/ui/MainFrame.java
T
2026-06-24 13:28:45 +02:00

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;
}
}
}