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 fileListModel; private JList fileList; private JComboBox scanModeCombo; private JComboBox xCombo; private JComboBox yCombo; private JComboBox zCombo; private JComboBox styleCombo; private JComboBox display2DCombo; private JComboBox interpolationMethodCombo; private JComboBox 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 directionModeCombo; private JComboBox 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(); fileList = new JList(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(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(new String[]{ "1D: Y(X)", "2D: Z(X,Y)" }); xCombo = new JComboBox(); yCombo = new JComboBox(); zCombo = new JComboBox(); styleCombo = new JComboBox(new String[]{ "Line + points", "Line", "Points", "2D scatter" }); display2DCombo = new JComboBox(new String[]{ "2D scatter", "Interpolated image" }); interpolationMethodCombo = new JComboBox(new String[]{ "Nearest", "IDW" }); directionModeCombo = new JComboBox(new String[]{ "All directions", "Positive fast-axis direction", "Negative fast-axis direction", }); fastAxisCombo = new JComboBox(); 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 files = new ArrayList(); 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 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()); yCombo.setModel(new DefaultComboBoxModel()); zCombo.setModel(new DefaultComboBoxModel()); if (fastAxisCombo != null) { fastAxisCombo.setModel(new DefaultComboBoxModel()); } } 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(names)); yCombo.setModel(new DefaultComboBoxModel(names)); zCombo.setModel(new DefaultComboBoxModel(names)); if (fastAxisCombo != null) { fastAxisCombo.setModel(new DefaultComboBoxModel(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; } } }