Skip to content

Commit 1a7e66c

Browse files
authored
Merge pull request #228 from saalfeldlab/interpolation-painting
Interpolation painting
2 parents 1741b57 + 135ecd4 commit 1a7e66c

31 files changed

+2467
-859
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ Usage: Paintera [-h] [--default-to-temp-directory] [--print-error-codes]
162162
| `F` + left click | 2D Flood-fill in current viewer plane with id that was last toggled active (if any) |
163163
| `Shift` + `F` + left click | Flood-fill with id that was last toggled active (if any) |
164164
| `N` | Select new, previously unused id |
165+
| `S` | Enter shape interpolation mode |
166+
| `1` / `2` | Edit first/second section when previewing interpolated shape |
167+
| `Enter` | Commit interpolated shape into canvas |
168+
| `Esc` | Abort shape interpolation mode |
165169
| `Ctrl` + `C` | Show dialog to commit canvas and/or assignments |
166170
| `C` | Increment ARGB stream seed by one |
167171
| `Shift` + `C` | Decrement ARGB stream seed by one |
@@ -174,6 +178,14 @@ Usage: Paintera [-h] [--default-to-temp-directory] [--print-error-codes]
174178
| `Ctrl` + `Shift` + `N` | Create new label dataset |
175179
| `Ctrl` + `T` | Threshold raw source (only available if current source is raw source) |
176180
181+
### Shape interpolation mode
182+
183+
The mode is activated by pressing the `S` key when the current source is a label source. Then, you can select the objects in the sections by left/right clicking (scrolling automatically fixes the selection in the current section).
184+
185+
When you're done with selecting the objects in the second section and initiate scrolling, the preview of the interpolated shape will be displayed. If something is not right, you can edit the selection in the first or second section by pressing `1` or `2`, which will update the preview. When the desired result is reached, hit `Enter` to commit the results into the canvas and return back to normal mode.
186+
187+
While in the shape interpolation mode, at any point in time you can hit `Esc` to discard the current state and exit the mode.
188+
177189
## Data
178190
179191
In [#61](https://github.com/saalfeldlab/paintera/issues/61) we introduced a specification for the data format that Paintera can load through the opener dialog (`Ctrl O`).

src/main/java/bdv/fx/viewer/ViewerPanelFX.java

+20-18
Original file line numberDiff line numberDiff line change
@@ -36,43 +36,26 @@
3636
import bdv.viewer.Source;
3737
import bdv.viewer.SourceAndConverter;
3838
import bdv.viewer.ViewerOptions;
39-
import gnu.trove.list.array.TIntArrayList;
40-
import javafx.beans.property.ObjectProperty;
4139
import javafx.beans.property.ReadOnlyBooleanProperty;
4240
import javafx.beans.property.ReadOnlyDoubleProperty;
43-
import javafx.beans.property.ReadOnlyObjectProperty;
44-
import javafx.beans.property.SimpleObjectProperty;
4541
import javafx.collections.FXCollections;
4642
import javafx.collections.ListChangeListener;
4743
import javafx.collections.ObservableList;
4844
import javafx.scene.Node;
49-
import javafx.scene.canvas.Canvas;
5045
import javafx.scene.layout.StackPane;
5146
import net.imglib2.Interval;
52-
import net.imglib2.Point;
5347
import net.imglib2.Positionable;
5448
import net.imglib2.RealInterval;
5549
import net.imglib2.RealLocalizable;
5650
import net.imglib2.RealPoint;
5751
import net.imglib2.RealPositionable;
58-
import net.imglib2.algorithm.fill.FloodFill;
59-
import net.imglib2.algorithm.neighborhood.DiamondShape;
60-
import net.imglib2.img.array.ArrayImg;
61-
import net.imglib2.img.array.ArrayImgs;
62-
import net.imglib2.img.basictypeaccess.array.IntArray;
63-
import net.imglib2.img.cell.CellGrid;
6452
import net.imglib2.realtransform.AffineTransform3D;
65-
import net.imglib2.type.numeric.integer.IntType;
6653
import net.imglib2.ui.TransformListener;
67-
import net.imglib2.util.Intervals;
68-
import net.imglib2.view.Views;
69-
7054
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
7155
import org.slf4j.Logger;
7256
import org.slf4j.LoggerFactory;
7357
import java.lang.invoke.MethodHandles;
7458
import java.util.ArrayList;
75-
import java.util.Arrays;
7659
import java.util.Collection;
7760
import java.util.List;
7861
import java.util.concurrent.CopyOnWriteArrayList;
@@ -287,6 +270,25 @@ public void displayToGlobalCoordinates(final double x, final double y, final Rea
287270
viewerTransform.applyInverse(gPos, lPos);
288271
}
289272

273+
/**
274+
* Set {@code pos} to the display coordinates (x,y,0)<sup>T</sup> transformed into the source coordinate system.
275+
*
276+
* @param pos
277+
* is set to the source coordinates at display (x,y,0)<sup>T</sup>.
278+
*/
279+
public <P extends RealLocalizable & RealPositionable> void displayToSourceCoordinates(
280+
final double x,
281+
final double y,
282+
final AffineTransform3D sourceTransform,
283+
final P pos)
284+
{
285+
pos.setPosition(x, 0);
286+
pos.setPosition(y, 1);
287+
pos.setPosition(0, 2);
288+
displayToGlobalCoordinates(pos);
289+
sourceTransform.applyInverse(pos, pos);
290+
}
291+
290292
/**
291293
* Set {@code gPos} to the current mouse coordinates transformed into the global coordinate system.
292294
*
@@ -499,7 +501,7 @@ public ReadOnlyDoubleProperty mouseYProperty()
499501
* 1. {@code 0 < sceenScales[i] <= 1} for all {@code i}
500502
* 2. {@code screenScales[i] < screenScales[i - 1]} for all {@code i > 0}
501503
*/
502-
public void setScreenScales(double[] screenScales)
504+
public void setScreenScales(final double[] screenScales)
503505
{
504506
LOG.debug("Setting screen scales to {}", screenScales);
505507
this.renderUnit.setScreenScales(screenScales.clone());

src/main/java/org/janelia/saalfeldlab/fx/ortho/OrthogonalViews.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
import java.util.function.Consumer;
66
import java.util.function.Function;
77

8+
import org.janelia.saalfeldlab.paintera.control.navigation.AffineTransformWithListeners;
9+
import org.janelia.saalfeldlab.paintera.control.navigation.TransformConcatenator;
10+
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
11+
import org.janelia.saalfeldlab.paintera.state.GlobalTransformManager;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
815
import bdv.cache.CacheControl;
916
import bdv.fx.viewer.ViewerPanelFX;
10-
import bdv.util.volatiles.SharedQueue;
1117
import bdv.viewer.Interpolation;
1218
import bdv.viewer.Source;
1319
import bdv.viewer.SourceAndConverter;
@@ -16,14 +22,8 @@
1622
import javafx.event.EventHandler;
1723
import javafx.event.EventType;
1824
import javafx.scene.Node;
19-
import javafx.scene.layout.Pane;
25+
import javafx.scene.layout.GridPane;
2026
import net.imglib2.realtransform.AffineTransform3D;
21-
import org.janelia.saalfeldlab.paintera.control.navigation.AffineTransformWithListeners;
22-
import org.janelia.saalfeldlab.paintera.control.navigation.TransformConcatenator;
23-
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
24-
import org.janelia.saalfeldlab.paintera.state.GlobalTransformManager;
25-
import org.slf4j.Logger;
26-
import org.slf4j.LoggerFactory;
2727

2828
/**
2929
* Wrap a {@link ResizableGridPane2x2} with {@link ViewerPanelFX viewer panels} at top left, top right, and bottom left. Bottom right
@@ -157,7 +157,7 @@ public ResizableGridPane2x2<ViewerPanelFX, ViewerPanelFX, ViewerPanelFX, BR> gri
157157
*
158158
* @return {@link ResizableGridPane2x2#pane()} for the underlying {@link ResizableGridPane2x2}
159159
*/
160-
public Pane pane()
160+
public GridPane pane()
161161
{
162162
return grid().pane();
163163
}

src/main/java/org/janelia/saalfeldlab/fx/ortho/ResizableGridPane2x2.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import javafx.beans.property.SimpleObjectProperty;
55
import javafx.scene.Node;
66
import javafx.scene.layout.GridPane;
7-
import javafx.scene.layout.Pane;
87

98
/**
109
* A wrapper around {@link GridPane} that holds for children organized in a 2x2 grid. The underlying
@@ -62,7 +61,7 @@ public ResizableGridPane2x2(
6261
*
6362
* @return underlying {@link GridPane}
6463
*/
65-
public Pane pane()
64+
public GridPane pane()
6665
{
6766
return this.grid;
6867
}
@@ -149,11 +148,18 @@ public void manage(final GridConstraintsManager manager)
149148
manager.manageGrid(this.grid);
150149
}
151150

151+
public Node getNodeAt(final int col, final int row)
152+
{
153+
for (final Node child : grid.getChildren())
154+
if (GridPane.getColumnIndex(child) == col && GridPane.getRowIndex(child) == row)
155+
return child;
156+
return null;
157+
}
158+
152159
private static void replace(final GridPane grid, final Node oldValue, final Node newValue, final int col, final int
153160
row)
154161
{
155162
grid.getChildren().remove(oldValue);
156163
grid.add(newValue, col, row);
157164
}
158-
159165
}

src/main/java/org/janelia/saalfeldlab/paintera/BorderPaneWithStatusBars.java

+63-32
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,14 @@
44
import java.util.Collections;
55
import java.util.HashMap;
66
import java.util.Map;
7-
import java.util.Optional;
87
import java.util.concurrent.Executors;
98
import java.util.concurrent.ScheduledExecutorService;
109
import java.util.concurrent.TimeUnit;
11-
import java.util.function.*;
10+
import java.util.function.BiConsumer;
11+
import java.util.function.LongSupplier;
12+
import java.util.function.LongUnaryOperator;
13+
import java.util.function.Supplier;
1214

13-
import bdv.fx.viewer.ViewerPanelFX;
14-
import bdv.viewer.Source;
15-
import javafx.animation.KeyFrame;
16-
import javafx.animation.Timeline;
17-
import javafx.beans.binding.Bindings;
18-
import javafx.beans.property.*;
19-
import javafx.beans.value.ObservableObjectValue;
20-
import javafx.event.ActionEvent;
21-
import javafx.event.EventHandler;
22-
import javafx.scene.Group;
23-
import javafx.scene.control.*;
24-
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
25-
import javafx.scene.input.MouseEvent;
26-
import javafx.scene.layout.BorderPane;
27-
import javafx.scene.layout.HBox;
28-
import javafx.scene.layout.Priority;
29-
import javafx.scene.layout.Region;
30-
import javafx.scene.layout.VBox;
31-
import javafx.scene.paint.Color;
32-
import javafx.scene.text.Font;
33-
import javafx.util.Duration;
34-
import net.imglib2.RealPoint;
3515
import org.janelia.saalfeldlab.fx.TitledPanes;
3616
import org.janelia.saalfeldlab.fx.ortho.OrthogonalViews;
3717
import org.janelia.saalfeldlab.fx.ortho.OrthogonalViews.ViewerAndTransforms;
@@ -57,6 +37,40 @@
5737
import org.slf4j.Logger;
5838
import org.slf4j.LoggerFactory;
5939

40+
import bdv.fx.viewer.ViewerPanelFX;
41+
import bdv.viewer.Source;
42+
import javafx.animation.KeyFrame;
43+
import javafx.animation.Timeline;
44+
import javafx.beans.binding.Bindings;
45+
import javafx.beans.property.BooleanProperty;
46+
import javafx.beans.property.LongProperty;
47+
import javafx.beans.property.ObjectProperty;
48+
import javafx.beans.property.ReadOnlyBooleanProperty;
49+
import javafx.beans.property.SimpleBooleanProperty;
50+
import javafx.beans.value.ObservableObjectValue;
51+
import javafx.event.ActionEvent;
52+
import javafx.event.EventHandler;
53+
import javafx.scene.Group;
54+
import javafx.scene.control.Alert;
55+
import javafx.scene.control.Button;
56+
import javafx.scene.control.ButtonType;
57+
import javafx.scene.control.CheckBox;
58+
import javafx.scene.control.Label;
59+
import javafx.scene.control.ScrollPane;
60+
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
61+
import javafx.scene.control.TitledPane;
62+
import javafx.scene.control.Tooltip;
63+
import javafx.scene.input.MouseEvent;
64+
import javafx.scene.layout.BorderPane;
65+
import javafx.scene.layout.HBox;
66+
import javafx.scene.layout.Priority;
67+
import javafx.scene.layout.Region;
68+
import javafx.scene.layout.VBox;
69+
import javafx.scene.paint.Color;
70+
import javafx.scene.text.Font;
71+
import javafx.util.Duration;
72+
import net.imglib2.RealPoint;
73+
6074
public class BorderPaneWithStatusBars
6175
{
6276

@@ -160,16 +174,29 @@ public BorderPaneWithStatusBars(
160174
center.orthogonalViews(),
161175
center.viewer3D().meshesGroup(),
162176
center.sourceInfo()
163-
);
164-
165-
center.sourceInfo().currentNameProperty().addListener((obs, oldv, newv) -> {
166-
currentSourceStatus.textProperty().unbind();
167-
Optional.ofNullable(newv).ifPresent(currentSourceStatus.textProperty()::bind);
168-
});
177+
);
169178

170179
final SingleChildStackPane sourceDisplayStatus = new SingleChildStackPane();
171180
center.sourceInfo().currentState().addListener((obs, oldv, newv) -> sourceDisplayStatus.setChild(newv.getDisplayStatus()));
172181

182+
// show source name by default, or override it with source status text if any
183+
center.sourceInfo().currentState().addListener((obs, oldv, newv) -> {
184+
sourceDisplayStatus.setChild(newv.getDisplayStatus());
185+
currentSourceStatus.textProperty().unbind();
186+
currentSourceStatus.textProperty().bind(Bindings.createStringBinding(
187+
() -> {
188+
if (newv.statusTextProperty() != null && newv.statusTextProperty().get() != null)
189+
return newv.statusTextProperty().get();
190+
else if (newv.nameProperty().get() != null)
191+
return newv.nameProperty().get();
192+
else
193+
return null;
194+
},
195+
newv.nameProperty(),
196+
newv.statusTextProperty()
197+
));
198+
});
199+
173200
// for positioning the 'show status bar' checkbox on the right
174201
final Region valueStatusSpacing = new Region();
175202
HBox.setHgrow(valueStatusSpacing, Priority.ALWAYS);
@@ -272,21 +299,25 @@ public BorderPaneWithStatusBars(
272299
this.sideBar.setHbarPolicy(ScrollBarPolicy.NEVER);
273300
this.sideBar.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
274301
this.sideBar.setVisible(true);
275-
this.sideBar.prefWidthProperty().set(250);
302+
this.sideBar.prefWidthProperty().set(280);
276303
sourceTabs.widthProperty().bind(sideBar.prefWidthProperty());
277304
settingsContents.prefWidthProperty().bind(sideBar.prefWidthProperty());
278305

279306
resizeSideBar = new ResizeOnLeftSide(sideBar, sideBar.prefWidthProperty(), dist -> Math.abs(dist) < 5);
280307
}
281308

309+
public ScrollPane getSideBar()
310+
{
311+
return sideBar;
312+
}
313+
282314
public void toggleSideBar()
283315
{
284316
if (pane.getRight() == null)
285317
{
286318
pane.setRight(sideBar);
287319
resizeSideBar.install();
288320
}
289-
290321
else
291322
{
292323
resizeSideBar.remove();

0 commit comments

Comments
 (0)