Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle z & t in 'synchronize viewers' #1302

Merged
merged 1 commit into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This is a work-in-progress for the next QuPath release.
* ProjectCommands.promptToImportImages always returns an empty list (https://github.com/qupath/qupath/issues/1251)
* PathIO doesn't restore backup if writing ImageData fails (https://github.com/qupath/qupath/issues/1252)
* Scripts open with the caret at the bottom of the text rather than the top (https://github.com/qupath/qupath/issues/1258)
* 'Synchronize viewers' ignores z and t positions (https://github.com/qupath/qupath/issues/1220)

### Dependency updates
* Bio-Formats 7.0.0
Expand Down
117 changes: 81 additions & 36 deletions qupath-gui-fx/src/main/java/qupath/lib/gui/viewer/ViewerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -71,6 +74,7 @@
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import jfxtras.scene.menu.CirclePopupMenu;
import qupath.lib.common.GeneralTools;
import qupath.lib.gui.QuPathGUI;
import qupath.lib.gui.ToolManager;
import qupath.lib.gui.actions.ActionTools;
Expand Down Expand Up @@ -128,10 +132,8 @@ public class ViewerManager implements QuPathViewerListener {
private final Color colorBorder = Color.rgb(180, 0, 0, 0.5);

private BooleanProperty synchronizeViewers = new SimpleBooleanProperty(true);
private double lastX = Double.NaN;
private double lastY = Double.NaN;
private double lastDownsample = Double.NaN;
private double lastRotation = Double.NaN;

private Map<QuPathViewer, ViewerPosition> lastViewerPosition = new WeakHashMap<>();

private ViewerManager(final QuPathGUI qupath) {
this.qupath = qupath;
Expand Down Expand Up @@ -237,17 +239,12 @@ public void setActiveViewer(final QuPathViewer viewer) {
}
}
this.activeViewerProperty.set(viewer);
lastX = Double.NaN;
lastY = Double.NaN;
lastDownsample = Double.NaN;
lastRotation = Double.NaN;
getLastViewerPosition(viewer).reset();

if (viewer != null) {
viewer.setBorderColor(colorBorder);
if (viewer.getServer() != null) {
lastX = viewer.getCenterPixelX();
lastY = viewer.getCenterPixelY();
lastDownsample = viewer.getDownsampleFactor();
lastRotation = viewer.getRotation();
getLastViewerPosition(viewer).update(viewer);
}
}
logger.debug("Active viewer set to {}", viewer);
Expand Down Expand Up @@ -331,13 +328,6 @@ protected QuPathViewerPlus createViewer() {
return viewerNew;
}


SplitPane getAncestorSplitPane(Node node) {
while (node != null && !(node instanceof SplitPane))
node = node.getParent();
return (SplitPane)node;
}

/**
* Try to remove the row containing the specified viewer, notifying the user if this isn't possible.
* @param viewer
Expand Down Expand Up @@ -449,14 +439,15 @@ public void imageDataChanged(QuPathViewer viewer, ImageData<BufferedImage> image
if (viewer != null && viewer == getActiveViewer()) {
if (viewer.getServer() != null) {
// Setting these to NaN prevents unexpected jumping when a new image is opened
lastX = Double.NaN;
lastY = Double.NaN;
lastDownsample = Double.NaN;
lastRotation = Double.NaN;
getLastViewerPosition(viewer).reset();
}
imageDataProperty.set(viewer.getImageData());
}
}

private ViewerPosition getLastViewerPosition(QuPathViewer viewer) {
return lastViewerPosition.computeIfAbsent(viewer, v -> new ViewerPosition());
}


@Override
Expand All @@ -468,29 +459,37 @@ public void visibleRegionChanged(QuPathViewer viewer, Shape shape) {
}

QuPathViewer activeViewer = getActiveViewer();


double x = activeViewer.getCenterPixelX();
double y = activeViewer.getCenterPixelY();
double rotation = activeViewer.getRotation();
double dx = Double.NaN, dy = Double.NaN, dr = Double.NaN;
int dt = 0, dz = 0;

double downsample = viewer.getDownsampleFactor();
double relativeDownsample = viewer.getDownsampleFactor() / lastDownsample;
var position = getLastViewerPosition(activeViewer);
double relativeDownsample = viewer.getDownsampleFactor() / position.downsample;

// Shift as required, assuming we aren't aligning cores
// if (!aligningCores) {
// synchronizeViewers = true;
if (synchronizeViewers.get()) {
if (!Double.isNaN(lastX + lastY)) {
dx = x - lastX;
dy = y - lastY;
dr = rotation - lastRotation;
if (!Double.isNaN( position.x + position.y)) {
dx = x - position.x;
dy = y - position.y;
dr = rotation - position.rotation;
dt = activeViewer.getTPosition() - position.t;
dz = activeViewer.getZPosition() - position.z;
}

for (QuPathViewer v : viewers) {
if (v == viewer)
continue;

if (!Double.isNaN(relativeDownsample))
v.setDownsampleFactor(v.getDownsampleFactor() * relativeDownsample, -1, -1, false);

if (!Double.isNaN(dr) && dr != 0)
v.setRotation(v.getRotation() + dr);

Expand All @@ -509,14 +508,20 @@ public void visibleRegionChanged(QuPathViewer viewer, Shape shape) {
double dy3 = sin * dx2 + cos * dy2;

v.setCenterPixelLocation(v.getCenterPixelX() + dx3, v.getCenterPixelY() + dy3);

// Handle z and t
if (dz != 0) {
v.setZPosition(GeneralTools.clipValue(v.getZPosition() + dz, 0, v.getServer().nZSlices()-1));
}
if (dt != 0) {
v.setTPosition(GeneralTools.clipValue(v.getTPosition() + dt, 0, v.getServer().nTimepoints()-1));
}

}
}
}

lastX = x;
lastY = y;
lastDownsample = downsample;
lastRotation = rotation;
position.update(activeViewer);
}


Expand Down Expand Up @@ -551,10 +556,7 @@ public void selectedObjectChanged(QuPathViewer viewer, PathObject pathObjectSele
return;

// Thwart the upcoming region shift
lastX = Double.NaN;
lastY = Double.NaN;
lastDownsample = Double.NaN;
lastRotation = Double.NaN;
getLastViewerPosition(getActiveViewer()).reset();

// aligningCores = true;
String coreName = ((TMACoreObject)pathObjectSelected).getName();
Expand Down Expand Up @@ -1248,4 +1250,47 @@ private void updateSetAnnotationPathClassMenu(final ObservableList<MenuItem> men
menuSetClassItems.setAll(itemList);
}


private static class ViewerPosition {

private double x;
private double y;
private double downsample;
private double rotation;
private int z;
private int t;

private ViewerPosition() {
reset();
}

private ViewerPosition(QuPathViewer viewer) {
update(viewer);
}

private void update(QuPathViewer viewer) {
if (viewer == null || viewer.getImageData() == null) {
reset();
} else {
x = viewer.getCenterPixelX();
y = viewer.getCenterPixelY();
downsample = viewer.getDownsampleFactor();
rotation = viewer.getRotation();
z = viewer.getZPosition();
t = viewer.getTPosition();
}
}

private void reset() {
x = Double.NaN;
y = Double.NaN;
downsample = Double.NaN;
rotation = Double.NaN;
z = -1;
t = -1;
}

}


}
Loading