Skip to content

Commit

Permalink
Merge pull request #68 from bigdataviewer/bdvsynchronizer
Browse files Browse the repository at this point in the history
adds bdv windows synchronization actions + command + bdvhandle widget…
  • Loading branch information
Christian "Tischi" Tischer authored Jan 16, 2020
2 parents 5f1441c + 72739e1 commit c4f9b34
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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
*
* 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, [email protected]
*/

public class ViewerTransformSyncStarter implements Runnable {

/**
* 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<BdvHandle, TransformListener<AffineTransform3D>> bdvHandleToTransformListener = new HashMap<>();

public ViewerTransformSyncStarter(BdvHandle[] bdvHandles) {
this.bdvHandles = bdvHandles;
}

public void setBdvHandleInitialReference(BdvHandle bdvHandle) {
bdvHandleInitialReference = bdvHandle;
}

@Override
public void run() {

// Getting transform for initial sync
AffineTransform3D at3Dorigin = getViewTransformForInitialSynchronization();

// Building circularly linked listeners with stop condition when all transforms are equal,
// cf documentation

for (int i = 0; i< bdvHandles.length; i++) {

// The idea is that bdvHandles[i], when it has a view transform,
// triggers an identical ViewTransform to the next bdvHandle in the array
// (called nextBdvHandle). nextBdvHandle is bdvHandles[i+1] in most cases,
// unless it's the end of the array,
// where in this case nextBdvHandle is bdvHandles[0]
BdvHandle currentBdvHandle = bdvHandles[i];
BdvHandle nextBdvHandle;

// Identifying nextBdvHandle
if (i == bdvHandles.length-1) {
nextBdvHandle = bdvHandles[0];
} else {
nextBdvHandle = bdvHandles[i+1];
}

// Building the TransformListener of currentBdvHandle
TransformListener<AffineTransform3D> listener =
(at3D) -> {
// Is the transform necessary ? That's the stop condition
AffineTransform3D ati = new AffineTransform3D();
nextBdvHandle.getViewerPanel().getState().getViewerTransform(ati);
if (!Arrays.equals(at3D.getRowPackedCopy(), ati.getRowPackedCopy())) {
// Yes -> triggers a transform change to the nextBdvHandle
nextBdvHandle.getViewerPanel().setCurrentViewerTransform(at3D.copy());
nextBdvHandle.getViewerPanel().requestRepaint();
}
};

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

// 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<BdvHandle, TransformListener<AffineTransform3D>> getSynchronizers() {
return bdvHandleToTransformListener;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package sc.fiji.bdvpg.bdv.navigate;

import bdv.util.BdvHandle;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.ui.TransformListener;
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, [email protected]
**/

public class ViewerTransformSyncStopper implements Runnable {

Map<BdvHandle, TransformListener<AffineTransform3D>> bdvHandleToTransformListener;

public ViewerTransformSyncStopper(Map<BdvHandle, TransformListener<AffineTransform3D>> bdvHandleToTransformListener) {
this.bdvHandleToTransformListener = bdvHandleToTransformListener;
}

@Override
public void run() {
bdvHandleToTransformListener.forEach((bdvHandle, listener) -> {
bdvHandle.getViewerPanel().removeTransformListener(listener);
});
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package sc.fiji.bdvpg.scijava.command.bdv;

import bdv.util.BdvHandle;
import org.scijava.command.Command;
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.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 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 Views")
public class ViewSynchronizerCommand implements Command {

@Parameter(label = "Select Windows to synchronize")
BdvHandle[] bdvhs;

ViewerTransformSyncStarter sync;

public void run() {
// Starting synchronnization of selected bdvhandles
sync = new ViewerTransformSyncStarter(bdvhs);
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
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
new ViewerTransformSyncStopper(sync.getSynchronizers()).run();
e.getWindow().dispose();
}
});
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";
}

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

}
Original file line number Diff line number Diff line change
@@ -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<I extends String, O extends BdvHandle> extends AbstractConverter<I, O> {
@Parameter
ObjectService os;

@Override
public <T> T convert(Object src, Class<T> dest) {
Optional<BdvHandle> 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<O> getOutputType() {
return (Class<O>) BdvHandle.class;
}

@Override
public Class<I> getInputType() {
return (Class<I>) String.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,24 +89,29 @@ public class BdvSourceAndConverterDisplayService extends AbstractService impleme
**/
Map<SourceAndConverter, List<BdvHandleRef>> 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<BdvHandle> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package sc.fiji.bdvpg.scijava.widget;

import bdv.util.BdvHandle;
import org.scijava.widget.InputWidget;

public interface BdvHandleListWidget<U> extends InputWidget<BdvHandle[], U> {
}
Original file line number Diff line number Diff line change
@@ -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<U> extends InputWidget<BdvHandle, U> {
}
Loading

0 comments on commit c4f9b34

Please sign in to comment.