From 0b26a52d5362dad02c41514858bdee2e06c79226 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Wed, 15 Jan 2020 15:50:37 +0100 Subject: [PATCH 1/2] adds bdv windows synchronization actions + command + bdvhandle widget for bdvhandle and bdvhandle list --- .../navigate/ViewerTransformSyncStarter.java | 90 ++++++++++++++++++ .../navigate/ViewerTransformSyncStopper.java | 27 ++++++ .../command/bdv/ViewSynchronizerCommand.java | 68 ++++++++++++++ .../scijava/converters/StringToBdvHandle.java | 38 ++++++++ .../BdvSourceAndConverterDisplayService.java | 29 +++--- .../scijava/widget/BdvHandleListWidget.java | 7 ++ .../bdvpg/scijava/widget/BdvHandleWidget.java | 8 ++ .../widget/SwingBdvHandleListWidget.java | 81 +++++++++++++++++ .../scijava/widget/SwingBdvHandleWidget.java | 91 +++++++++++++++++++ .../IBdvSourceAndConverterDisplayService.java | 5 + .../ViewTransformSynchronizationDemo.java | 84 +++++++++++++++++ 11 files changed, 516 insertions(+), 12 deletions(-) create mode 100644 src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java create mode 100644 src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/converters/StringToBdvHandle.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleListWidget.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleWidget.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleListWidget.java create mode 100644 src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleWidget.java create mode 100644 src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java diff --git a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java new file mode 100644 index 00000000..70cc4ca2 --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java @@ -0,0 +1,90 @@ +package sc.fiji.bdvpg.bdv.navigate; + +import bdv.util.BdvHandle; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.ui.TransformListener; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Action which synchronizes the display location of n BdvHandle + * + * Works in combination with the action ViewerTransformSyncStopper + * + * See also ViewTransformSynchronizationDemo + * + * author Nicolas Chiaruttini, BIOP EPFL, nicolas.chiaruttini@epfl.ch + * + */ + +public class ViewerTransformSyncStarter implements Runnable { + + BdvHandle[] bdvhs; + + BdvHandle originatingBdvHandle = null; + + Map> bdvToTransformListener = new HashMap<>(); + + public ViewerTransformSyncStarter(BdvHandle[] bdvhs) { + this.bdvhs = bdvhs; + } + + public void setOriginatingBdvHandle(BdvHandle bdvh) { + originatingBdvHandle = bdvh; + } + + @Override + public void run() { + + // Building circularly linked listeners with stop condition when all transforms are equal + + AffineTransform3D at3Dorigin = null; + + for (int i=0;i listener = + (at3D) -> { + //System.out.println("listener index "+ifinal+" called"); + AffineTransform3D ati = new AffineTransform3D(); + bdvhip1.getViewerPanel().getState().getViewerTransform(ati); + if (!Arrays.equals(at3D.getRowPackedCopy(), ati.getRowPackedCopy())) { + bdvhip1.getViewerPanel().setCurrentViewerTransform(at3D.copy()); + bdvhip1.getViewerPanel().requestRepaint(); + } + }; + bdvhs[i].getViewerPanel().addTransformListener(listener); + bdvToTransformListener.put(bdvhs[i], listener); + } + + if ((originatingBdvHandle!=null)&&(at3Dorigin!=null)) { + //System.out.println("Fixing origin to proper location"); + for (BdvHandle bdvh:bdvhs) { + bdvh.getViewerPanel().setCurrentViewerTransform(at3Dorigin.copy()); + bdvh.getViewerPanel().requestRepaint(); + } + } + } + + public Map> getSynchronizers() { + return bdvToTransformListener; + } +} diff --git a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java new file mode 100644 index 00000000..447ea711 --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java @@ -0,0 +1,27 @@ +package sc.fiji.bdvpg.bdv.navigate; + +import bdv.util.BdvHandle; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.ui.TransformListener; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class ViewerTransformSyncStopper implements Runnable { + + Map> bdvToTransformListener; + + public ViewerTransformSyncStopper(Map> bdvToTransformListener) { + this.bdvToTransformListener = bdvToTransformListener; + } + + @Override + public void run() { + bdvToTransformListener.forEach((bdvh, listener) -> { + bdvh.getViewerPanel().removeTransformListener(listener); + }); + } + + +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java b/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java new file mode 100644 index 00000000..77cd688d --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java @@ -0,0 +1,68 @@ +package sc.fiji.bdvpg.scijava.command.bdv; + +import bdv.util.BdvHandle; +import org.scijava.command.Command; +import org.scijava.command.InteractiveCommand; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.bdv.navigate.ViewerTransformSyncStarter; +import sc.fiji.bdvpg.bdv.navigate.ViewerTransformSyncStopper; +import sc.fiji.bdvpg.scijava.BdvHandleHelper; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.services.BdvSourceAndConverterService; +import sc.fiji.bdvpg.services.BdvService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * I wanted to do this as an Interactice Command but there's no callback I found + * when an interactive command is closed -> we cannot stop the synchronization + * appropriately. Hence the dirty JFrame the user has to close... + * + * author Nicolas Chiaruttini + */ + +@Plugin(type = Command.class, menuPath = ScijavaBdvDefaults.RootMenu+"Bdv>Synchronize") +public class ViewSynchronizerCommand implements Command { + + @Parameter(label = "Select Windows to synchronize") + BdvHandle[] bdvhs; + + ViewerTransformSyncStarter sync; + + public void run() { + sync = new ViewerTransformSyncStarter(bdvhs); + sync.setOriginatingBdvHandle(BdvService.getSourceAndConverterDisplayService().getActiveBdv()); + sync.run(); + + JFrame frameStopSync = new JFrame(); + frameStopSync.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + super.windowClosing(e); + new ViewerTransformSyncStopper(sync.getSynchronizers()).run(); + e.getWindow().dispose(); + } + }); + frameStopSync.setTitle("Close window to stop synchronization"); + + String text = ""; + for (BdvHandle bdvh:bdvhs) { + text+= BdvHandleHelper.getWindowTitle(bdvh)+"\n"; + } + + JPanel pane = new JPanel(); + JTextArea textArea = new JTextArea(text); + textArea.setEditable(false); + pane.add(textArea); + frameStopSync.add(pane); + frameStopSync.setPreferredSize(new Dimension(600,100)); + + frameStopSync.pack(); + frameStopSync.setVisible(true); + } + +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/converters/StringToBdvHandle.java b/src/main/java/sc/fiji/bdvpg/scijava/converters/StringToBdvHandle.java new file mode 100644 index 00000000..6742c2fa --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/converters/StringToBdvHandle.java @@ -0,0 +1,38 @@ +package sc.fiji.bdvpg.scijava.converters; + +import bdv.util.BdvHandle; +import org.scijava.convert.AbstractConverter; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.BdvHandleHelper; + +import java.util.Optional; + +@Plugin(type = org.scijava.convert.Converter.class) +public class StringToBdvHandle extends AbstractConverter { + @Parameter + ObjectService os; + + @Override + public T convert(Object src, Class dest) { + Optional ans = os.getObjects(BdvHandle.class).stream().filter(bdvh -> + (bdvh.toString().equals(src))||(BdvHandleHelper.getWindowTitle(bdvh).equals(src)) + ).findFirst(); + if (ans.isPresent()) { + return (T) ans.get(); + } else { + return null; + } + } + + @Override + public Class getOutputType() { + return (Class) BdvHandle.class; + } + + @Override + public Class getInputType() { + return (Class) String.class; + } +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/services/BdvSourceAndConverterDisplayService.java b/src/main/java/sc/fiji/bdvpg/scijava/services/BdvSourceAndConverterDisplayService.java index fffb2428..3ba218fe 100644 --- a/src/main/java/sc/fiji/bdvpg/scijava/services/BdvSourceAndConverterDisplayService.java +++ b/src/main/java/sc/fiji/bdvpg/scijava/services/BdvSourceAndConverterDisplayService.java @@ -89,24 +89,29 @@ public class BdvSourceAndConverterDisplayService extends AbstractService impleme **/ Map> sacToBdvHandleRefs; + public BdvHandle getNewBdv() { + try + { + return (BdvHandle) + cs.run(BdvWindowCreatorCommand.class, + true, + "is2D", false, + "windowTitle", "Bdv").get().getOutput("bdvh"); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + return null; + } + /** * Returns the last active Bdv or create a new one */ public BdvHandle getActiveBdv() { List bdvhs = os.getObjects(BdvHandle.class); if ((bdvhs == null)||(bdvhs.size()==0)) { - try - { - return (BdvHandle) - cs.run(BdvWindowCreatorCommand.class, - true, - "is2D", false, - "windowTitle", "Bdv").get().getOutput("bdvh"); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } + return getNewBdv(); } if (bdvhs.size()==1) { diff --git a/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleListWidget.java b/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleListWidget.java new file mode 100644 index 00000000..c6d97709 --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleListWidget.java @@ -0,0 +1,7 @@ +package sc.fiji.bdvpg.scijava.widget; + +import bdv.util.BdvHandle; +import org.scijava.widget.InputWidget; + +public interface BdvHandleListWidget extends InputWidget { +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleWidget.java b/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleWidget.java new file mode 100644 index 00000000..1b28e21d --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/widget/BdvHandleWidget.java @@ -0,0 +1,8 @@ +package sc.fiji.bdvpg.scijava.widget; + +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; +import org.scijava.widget.InputWidget; + +public interface BdvHandleWidget extends InputWidget { +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleListWidget.java b/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleListWidget.java new file mode 100644 index 00000000..61138f0a --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleListWidget.java @@ -0,0 +1,81 @@ +package sc.fiji.bdvpg.scijava.widget; + +import bdv.util.BdvHandle; +import org.scijava.Priority; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.ui.swing.widget.SwingInputWidget; +import org.scijava.widget.InputWidget; +import org.scijava.widget.WidgetModel; +import sc.fiji.bdvpg.scijava.BdvHandleHelper; +import sc.fiji.bdvpg.scijava.services.BdvSourceAndConverterService; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Swing implementation of {@link BdvHandleListWidget}. + * + * @author Nicolas Chiaruttini + */ + +@Plugin(type = InputWidget.class, priority = Priority.EXTREMELY_HIGH) +public class SwingBdvHandleListWidget extends SwingInputWidget implements + BdvHandleListWidget { + + @Override + protected void doRefresh() { + } + + @Override + public boolean supports(final WidgetModel model) { + return super.supports(model) && model.isType(BdvHandle[].class); + } + + @Override + public BdvHandle[] getValue() { + return getSelectedBdvHandles(); + } + + JList list; + + public BdvHandle[] getSelectedBdvHandles() { + List selected = list.getSelectedValuesList(); + return selected.stream().map((e) -> e.bdvh) + .collect(Collectors.toList()).toArray(new BdvHandle[selected.size()]); + } + + @Parameter + ObjectService os; + + @Override + public void set(final WidgetModel model) { + super.set(model); + List bdvhs = os.getObjects(BdvHandle.class).stream().map(bdvh -> new RenamableBdvHandle(bdvh)).collect(Collectors.toList()); + RenamableBdvHandle[] data = bdvhs.toArray(new RenamableBdvHandle[bdvhs.size()]); + list = new JList(data); //data has type Object[] + list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(250, 80)); + list.addListSelectionListener((e)-> model.setValue(getValue())); + getComponent().add(listScroller); + } + + public class RenamableBdvHandle { + + public BdvHandle bdvh; + + public RenamableBdvHandle(BdvHandle bdvh) { + this.bdvh = bdvh; + } + + public String toString() { + return BdvHandleHelper.getWindowTitle(bdvh); + } + + } + +} diff --git a/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleWidget.java b/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleWidget.java new file mode 100644 index 00000000..6c488836 --- /dev/null +++ b/src/main/java/sc/fiji/bdvpg/scijava/widget/SwingBdvHandleWidget.java @@ -0,0 +1,91 @@ +package sc.fiji.bdvpg.scijava.widget; + +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; +import org.scijava.Priority; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.ui.swing.widget.SwingInputWidget; +import org.scijava.widget.InputWidget; +import org.scijava.widget.WidgetModel; +import sc.fiji.bdvpg.scijava.BdvHandleHelper; +import sc.fiji.bdvpg.scijava.services.BdvSourceAndConverterService; +import sc.fiji.bdvpg.scijava.services.ui.BdvSourceServiceUI; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Swing implementation of {@link BdvHandleWidget}. + * + * @author Nicolas Chiaruttini + */ + +@Plugin(type = InputWidget.class, priority = Priority.EXTREMELY_HIGH) +public class SwingBdvHandleWidget extends SwingInputWidget implements + BdvHandleWidget { + + @Override + protected void doRefresh() { + } + + @Override + public boolean supports(final WidgetModel model) { + return super.supports(model) && model.isType(BdvHandle.class); + } + + @Override + public BdvHandle getValue() { + return getSelectedBdvHandle(); + } + + @Parameter + BdvSourceAndConverterService bss; + + JList list; + + public BdvHandle getSelectedBdvHandle() { + return ((RenamableBdvHandle) list.getSelectedValue()).bdvh; + } + + @Parameter + ObjectService os; + + + @Override + public void set(final WidgetModel model) { + super.set(model); + List bdvhs = os.getObjects(BdvHandle.class).stream().map(bdvh -> new RenamableBdvHandle(bdvh)).collect(Collectors.toList()); + RenamableBdvHandle[] data = bdvhs.toArray(new RenamableBdvHandle[bdvhs.size()]); + list = new JList(data); //data has type Object[] + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(250, 80)); + list.addListSelectionListener((e)-> model.setValue(getValue())); + getComponent().add(listScroller); + } + + public class RenamableBdvHandle { + + public BdvHandle bdvh; + + public RenamableBdvHandle(BdvHandle bdvh) { + this.bdvh = bdvh; + } + + public String toString() { + return BdvHandleHelper.getWindowTitle(bdvh); + } + + } + +} diff --git a/src/main/java/sc/fiji/bdvpg/services/IBdvSourceAndConverterDisplayService.java b/src/main/java/sc/fiji/bdvpg/services/IBdvSourceAndConverterDisplayService.java index 6ad8b9eb..e28e33c9 100644 --- a/src/main/java/sc/fiji/bdvpg/services/IBdvSourceAndConverterDisplayService.java +++ b/src/main/java/sc/fiji/bdvpg/services/IBdvSourceAndConverterDisplayService.java @@ -50,6 +50,11 @@ public interface IBdvSourceAndConverterDisplayService { */ BdvHandle getActiveBdv(); + /** + * Returns a new Bdv window + */ + BdvHandle getNewBdv(); + /** * Returns SourceAndConverter object * @param sac diff --git a/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java b/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java new file mode 100644 index 00000000..3e161304 --- /dev/null +++ b/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java @@ -0,0 +1,84 @@ +package sc.fiji.bdvpg.bdv.navigate; + +import bdv.util.BdvHandle; +import bdv.util.RandomAccessibleIntervalSource; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import ij.IJ; +import ij.ImagePlus; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.util.Util; +import net.imglib2.view.Views; +import org.scijava.ui.behaviour.ClickBehaviour; +import sc.fiji.bdvpg.behaviour.ClickBehaviourInstaller; +import sc.fiji.bdvpg.services.BdvService; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterUtils; + +/** + * ViewTransformSynchronizationDemo + *

+ *

+ *

+ * Author: Nicolas Chiaruttini + * 01 2020 + */ +public class ViewTransformSynchronizationDemo { + + + static boolean isSynchronizing; + + public static void main(String[] args) { + + // Initializes static SourceService and Display Service + BdvService.InitScijavaServices(); + + // load and convert an image + ImagePlus imp = IJ.openImage("src/test/resources/blobs.tif"); + RandomAccessibleInterval rai = ImageJFunctions.wrapReal(imp); + // Adds a third dimension because Bdv needs 3D + rai = Views.addDimension( rai, 0, 0 ); + + // Makes Bdv Source + Source source = new RandomAccessibleIntervalSource(rai, Util.getTypeFromInterval(rai), "blobs"); + SourceAndConverter sac = SourceAndConverterUtils.createSourceAndConverter(source); + + // Creates a BdvHandle + BdvHandle bdvHandle1 = BdvService.getSourceAndConverterDisplayService().getNewBdv(); + // Creates a BdvHandle + BdvHandle bdvHandle2 = BdvService.getSourceAndConverterDisplayService().getNewBdv(); + // Creates a BdvHandles + BdvHandle bdvHandle3 = BdvService.getSourceAndConverterDisplayService().getNewBdv(); + + BdvHandle[] bdvhs = new BdvHandle[]{bdvHandle1,bdvHandle2,bdvHandle3}; + + ViewerTransformSyncStarter syncstart = new ViewerTransformSyncStarter(bdvhs); + ViewerTransformSyncStopper syncstop = new ViewerTransformSyncStopper(syncstart.getSynchronizers()); + + syncstart.run(); + isSynchronizing = true; + + for (BdvHandle bdvHandle:bdvhs) { + // Show the sourceandconverter + BdvService.getSourceAndConverterDisplayService().show(bdvHandle, sac); + + // Adjust view on sourceandconverter + new ViewerTransformAdjuster(bdvHandle, sac).run(); + + new ClickBehaviourInstaller(bdvHandle, (x,y) -> { + if (isSynchronizing) { + syncstop.run(); + } else { + syncstart.setOriginatingBdvHandle(bdvHandle); + syncstart.run(); + } + isSynchronizing = !isSynchronizing; + }).install("Toggle Synchronization", "ctrl S"); + } + + + + + } +} From 72739e1cf201b698fe4dbfb38b982d5515b2e1a4 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Thu, 16 Jan 2020 08:47:27 +0100 Subject: [PATCH 2/2] renaming variables, adding documentation for synchronization actions --- .../navigate/ViewerTransformSyncStarter.java | 125 ++++++++++++------ .../navigate/ViewerTransformSyncStopper.java | 22 +-- .../command/bdv/ViewSynchronizerCommand.java | 17 ++- .../ViewTransformSynchronizationDemo.java | 4 +- 4 files changed, 113 insertions(+), 55 deletions(-) diff --git a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java index 70cc4ca2..78b9eb52 100644 --- a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java +++ b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStarter.java @@ -15,76 +15,127 @@ * * See also ViewTransformSynchronizationDemo * - * author Nicolas Chiaruttini, BIOP EPFL, nicolas.chiaruttini@epfl.ch + * Principle : for every changed view transform of a specific BdvHandle, + * the view transform change is triggered to the following BdvHandle in a closed loop manner + * + * To avoid inifinite loop, the stop condition is : if the view transform is unnecessary (between + * the view target is equal to the source), then there's no need to trigger a view transform change + * to the next BdvHandle * + * author Nicolas Chiaruttini, BIOP EPFL, nicolas.chiaruttini@epfl.ch */ public class ViewerTransformSyncStarter implements Runnable { - BdvHandle[] bdvhs; - - BdvHandle originatingBdvHandle = null; - - Map> bdvToTransformListener = new HashMap<>(); - - public ViewerTransformSyncStarter(BdvHandle[] bdvhs) { - this.bdvhs = bdvhs; + /** + * Array of BdvHandles to synchronize + */ + BdvHandle[] bdvHandles; + + /** + * Reference to the BdvHandle which will serve as a reference for the + * first synchronization. Most of the time this has to be the BdvHandle + * currently used by the user. If not set, the first synchronization + * will look like it's a random BdvHandle which is used (one not in focus) + */ + BdvHandle bdvHandleInitialReference = null; + + /** + * Map which links each BdvHandle to the TransformListener which has been added + * for synchronization purpose. This object contains all what's neede to stop + * the synchronization + */ + Map> bdvHandleToTransformListener = new HashMap<>(); + + public ViewerTransformSyncStarter(BdvHandle[] bdvHandles) { + this.bdvHandles = bdvHandles; } - public void setOriginatingBdvHandle(BdvHandle bdvh) { - originatingBdvHandle = bdvh; + public void setBdvHandleInitialReference(BdvHandle bdvHandle) { + bdvHandleInitialReference = bdvHandle; } @Override public void run() { - // Building circularly linked listeners with stop condition when all transforms are equal + // Getting transform for initial sync + AffineTransform3D at3Dorigin = getViewTransformForInitialSynchronization(); - AffineTransform3D at3Dorigin = null; + // Building circularly linked listeners with stop condition when all transforms are equal, + // cf documentation - for (int i=0;i listener = (at3D) -> { - //System.out.println("listener index "+ifinal+" called"); + // Is the transform necessary ? That's the stop condition AffineTransform3D ati = new AffineTransform3D(); - bdvhip1.getViewerPanel().getState().getViewerTransform(ati); + nextBdvHandle.getViewerPanel().getState().getViewerTransform(ati); if (!Arrays.equals(at3D.getRowPackedCopy(), ati.getRowPackedCopy())) { - bdvhip1.getViewerPanel().setCurrentViewerTransform(at3D.copy()); - bdvhip1.getViewerPanel().requestRepaint(); + // Yes -> triggers a transform change to the nextBdvHandle + nextBdvHandle.getViewerPanel().setCurrentViewerTransform(at3D.copy()); + nextBdvHandle.getViewerPanel().requestRepaint(); } }; - bdvhs[i].getViewerPanel().addTransformListener(listener); - bdvToTransformListener.put(bdvhs[i], listener); + + // Adding this transform listener to the currenBdvHandle + currentBdvHandle.getViewerPanel().addTransformListener(listener); + + // Storing the transform listener -> needed to remove them in order to stop synchronization when needed + bdvHandleToTransformListener.put(bdvHandles[i], listener); } - if ((originatingBdvHandle!=null)&&(at3Dorigin!=null)) { - //System.out.println("Fixing origin to proper location"); - for (BdvHandle bdvh:bdvhs) { + // Setting first transform for initial synchronization, + // but only if the two necessary objects are present (the origin BdvHandle and the transform + if ((bdvHandleInitialReference !=null)&&(at3Dorigin!=null)) { + for (BdvHandle bdvh: bdvHandles) { bdvh.getViewerPanel().setCurrentViewerTransform(at3Dorigin.copy()); bdvh.getViewerPanel().requestRepaint(); } } } + /** + * A simple search to identify the view transform of the BdvHandle that will be used + * for the initial synchronization (first reference) + * @return + */ + private AffineTransform3D getViewTransformForInitialSynchronization() { + AffineTransform3D at3Dorigin = null; + for (int i = 0; i< bdvHandles.length; i++) { + BdvHandle bdvHandle = bdvHandles[i]; + // if the BdvHandle is the one that should be used for initial synchronization + if (bdvHandle.equals(bdvHandleInitialReference)) { + // Storing the transform that will be used for first synchronization + at3Dorigin = new AffineTransform3D(); + bdvHandle.getViewerPanel().getState().getViewerTransform(at3Dorigin); + } + } + return at3Dorigin; + } + + /** + * output of this action : this map can be used to stop the synchronization + * see ViewerTransformSyncStopper + * @return + */ public Map> getSynchronizers() { - return bdvToTransformListener; + return bdvHandleToTransformListener; } } diff --git a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java index 447ea711..f0e32474 100644 --- a/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java +++ b/src/main/java/sc/fiji/bdvpg/bdv/navigate/ViewerTransformSyncStopper.java @@ -3,23 +3,29 @@ import bdv.util.BdvHandle; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.ui.TransformListener; - -import java.util.Arrays; -import java.util.HashMap; import java.util.Map; +/** + * Action which stops the synchronization of the display location of n BdvHandle + * Works in combination with the action ViewerTransformSyncStarter + * + * See also ViewTransformSynchronizationDemo + * + * author Nicolas Chiaruttini, BIOP EPFL, nicolas.chiaruttini@epfl.ch + **/ + public class ViewerTransformSyncStopper implements Runnable { - Map> bdvToTransformListener; + Map> bdvHandleToTransformListener; - public ViewerTransformSyncStopper(Map> bdvToTransformListener) { - this.bdvToTransformListener = bdvToTransformListener; + public ViewerTransformSyncStopper(Map> bdvHandleToTransformListener) { + this.bdvHandleToTransformListener = bdvHandleToTransformListener; } @Override public void run() { - bdvToTransformListener.forEach((bdvh, listener) -> { - bdvh.getViewerPanel().removeTransformListener(listener); + bdvHandleToTransformListener.forEach((bdvHandle, listener) -> { + bdvHandle.getViewerPanel().removeTransformListener(listener); }); } diff --git a/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java b/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java index 77cd688d..684feab2 100644 --- a/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java +++ b/src/main/java/sc/fiji/bdvpg/scijava/command/bdv/ViewSynchronizerCommand.java @@ -2,14 +2,12 @@ import bdv.util.BdvHandle; import org.scijava.command.Command; -import org.scijava.command.InteractiveCommand; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.bdv.navigate.ViewerTransformSyncStarter; import sc.fiji.bdvpg.bdv.navigate.ViewerTransformSyncStopper; import sc.fiji.bdvpg.scijava.BdvHandleHelper; import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; -import sc.fiji.bdvpg.scijava.services.BdvSourceAndConverterService; import sc.fiji.bdvpg.services.BdvService; import javax.swing.*; @@ -18,14 +16,16 @@ import java.awt.event.WindowEvent; /** - * I wanted to do this as an Interactice Command but there's no callback I found - * when an interactive command is closed -> we cannot stop the synchronization - * appropriately. Hence the dirty JFrame the user has to close... + * I wanted to do this as an Interactive Command but there's no callback + * when an interactive command is closed (bug https://github.com/scijava/scijava-common/issues/379) + * -> we cannot stop the synchronization appropriately. + * + * Hence the dirty JFrame the user has to close to stop synchronization ... * * author Nicolas Chiaruttini */ -@Plugin(type = Command.class, menuPath = ScijavaBdvDefaults.RootMenu+"Bdv>Synchronize") +@Plugin(type = Command.class, menuPath = ScijavaBdvDefaults.RootMenu+"Bdv>Synchronize Views") public class ViewSynchronizerCommand implements Command { @Parameter(label = "Select Windows to synchronize") @@ -34,10 +34,12 @@ public class ViewSynchronizerCommand implements Command { ViewerTransformSyncStarter sync; public void run() { + // Starting synchronnization of selected bdvhandles sync = new ViewerTransformSyncStarter(bdvhs); - sync.setOriginatingBdvHandle(BdvService.getSourceAndConverterDisplayService().getActiveBdv()); + sync.setBdvHandleInitialReference(BdvService.getSourceAndConverterDisplayService().getActiveBdv()); sync.run(); + // JFrame serving the purpose of stopping synchronization when it is being closed JFrame frameStopSync = new JFrame(); frameStopSync.addWindowListener(new WindowAdapter() { @Override @@ -49,6 +51,7 @@ public void windowClosing(WindowEvent e) { }); frameStopSync.setTitle("Close window to stop synchronization"); + // Building JFrame with a simple panel and textarea String text = ""; for (BdvHandle bdvh:bdvhs) { text+= BdvHandleHelper.getWindowTitle(bdvh)+"\n"; diff --git a/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java b/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java index 3e161304..58aa62cf 100644 --- a/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java +++ b/src/test/src/sc/fiji/bdvpg/bdv/navigate/ViewTransformSynchronizationDemo.java @@ -8,10 +8,8 @@ import ij.ImagePlus; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.display.imagej.ImageJFunctions; -import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.util.Util; import net.imglib2.view.Views; -import org.scijava.ui.behaviour.ClickBehaviour; import sc.fiji.bdvpg.behaviour.ClickBehaviourInstaller; import sc.fiji.bdvpg.services.BdvService; import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterUtils; @@ -70,7 +68,7 @@ public static void main(String[] args) { if (isSynchronizing) { syncstop.run(); } else { - syncstart.setOriginatingBdvHandle(bdvHandle); + syncstart.setBdvHandleInitialReference(bdvHandle); syncstart.run(); } isSynchronizing = !isSynchronizing;