Skip to content

Commit f4a60f2

Browse files
committed
Add annotated object image popover, add ui settings, update settings visuals, fix object cell toggleicon click sometimes not firing.
1 parent 44219d6 commit f4a60f2

18 files changed

+453
-167
lines changed

src/main/java/com/github/mfl28/boundingboxeditor/controller/Controller.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ public void onRegisterSettingsAction() {
185185
.setDisplayedSettingsFromPredictorClientConfig(model.getBoundingBoxPredictorClientConfig());
186186
settingsDialog.getInferenceSettings()
187187
.setDisplayedSettingsFromPredictorConfig(model.getBoundingBoxPredictorConfig());
188+
settingsDialog.getUiSettings()
189+
.setDisplayedSettingsFromUISettingsConfig(view.getUiSettingsConfig());
188190

189191
settingsDialog.showAndWait();
190192
}
@@ -205,6 +207,8 @@ public void onRegisterSettingsApplyAction(ActionEvent event, ButtonType buttonTy
205207
.applyDisplayedSettingsToPredictorClientConfig(model.getBoundingBoxPredictorClientConfig());
206208
view.getSettingsDialog().getInferenceSettings()
207209
.applyDisplayedSettingsToPredictorConfig(model.getBoundingBoxPredictorConfig());
210+
view.getSettingsDialog().getUiSettings()
211+
.applyDisplayedSettingsToUISettingsConfig(view.getUiSettingsConfig());
208212

209213
if(buttonType.equals(ButtonType.APPLY)) {
210214
event.consume();

src/main/java/com/github/mfl28/boundingboxeditor/ui/BoundingBoxTreeItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public boolean equals(Object obj) {
6767
private void setUpInternalListeners() {
6868
((Shape) toggleIcon).fillProperty().bind(((BoundingBoxView) getValue()).strokeProperty());
6969

70-
((Shape) toggleIcon).setOnMousePressed(event -> {
70+
((Shape) toggleIcon).setOnMouseClicked(event -> {
7171
setIconToggledOn(!isIconToggledOn());
7272
event.consume();
7373
});

src/main/java/com/github/mfl28/boundingboxeditor/ui/BoundingBoxView.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import javafx.geometry.BoundingBox;
3232
import javafx.geometry.Bounds;
3333
import javafx.geometry.Point2D;
34+
import javafx.geometry.Rectangle2D;
3435
import javafx.scene.Cursor;
3536
import javafx.scene.control.Toggle;
3637
import javafx.scene.control.ToggleGroup;
@@ -195,6 +196,13 @@ public void autoScaleWithBoundsAndInitialize(ReadOnlyObjectProperty<Bounds> auto
195196
addAutoScaleListener();
196197
}
197198

199+
@Override
200+
public Rectangle2D getRelativeOutlineRectangle() {
201+
final Bounds relativeBounds = getRelativeBoundsInImageView();
202+
return new Rectangle2D(relativeBounds.getMinX(), relativeBounds.getMinY(), relativeBounds.getWidth(),
203+
relativeBounds.getHeight());
204+
}
205+
198206
@Override
199207
public BoundingShapeTreeItem toTreeItem() {
200208
return new BoundingBoxTreeItem(this);

src/main/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonTreeItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public boolean equals(Object obj) {
6767
private void setUpInternalListeners() {
6868
((Shape) toggleIcon).fillProperty().bind(((BoundingPolygonView) getValue()).strokeProperty());
6969

70-
((Shape) toggleIcon).setOnMousePressed(event -> {
70+
((Shape) toggleIcon).setOnMouseClicked(event -> {
7171
setIconToggledOn(!isIconToggledOn());
7272
event.consume();
7373
});

src/main/java/com/github/mfl28/boundingboxeditor/ui/BoundingPolygonView.java

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import javafx.css.PseudoClass;
3232
import javafx.geometry.Bounds;
3333
import javafx.geometry.Point2D;
34+
import javafx.geometry.Rectangle2D;
3435
import javafx.scene.control.Toggle;
3536
import javafx.scene.control.ToggleGroup;
3637
import javafx.scene.input.MouseButton;
@@ -61,10 +62,12 @@ public class BoundingPolygonView extends Polygon implements
6162

6263
private final BooleanProperty editing = createEditingProperty();
6364
private final BooleanProperty constructing = new SimpleBooleanProperty(false);
64-
private final DoubleProperty width = new SimpleDoubleProperty();
65-
private final DoubleProperty height = new SimpleDoubleProperty();
6665
private final ObservableList<VertexHandle> vertexHandles = FXCollections.observableArrayList();
6766
private final ObservableSet<Integer> editingIndices = FXCollections.observableSet(new LinkedHashSet<>());
67+
private final DoubleProperty xMin = new SimpleDoubleProperty();
68+
private final DoubleProperty yMin = new SimpleDoubleProperty();
69+
private final DoubleProperty xMax = new SimpleDoubleProperty();
70+
private final DoubleProperty yMax = new SimpleDoubleProperty();
6871
private List<Double> pointsInImage = Collections.emptyList();
6972

7073
/**
@@ -227,6 +230,18 @@ public void autoScaleWithBoundsAndInitialize(ReadOnlyObjectProperty<Bounds> auto
227230
addAutoScaleListener();
228231
}
229232

233+
@Override
234+
public Rectangle2D getRelativeOutlineRectangle() {
235+
final Bounds imageViewBounds = boundingShapeViewData.autoScaleBounds().getValue();
236+
237+
double relativeXMin = (xMin.get() - imageViewBounds.getMinX()) / imageViewBounds.getWidth();
238+
double relativeYMin = (yMin.get() - imageViewBounds.getMinY()) / imageViewBounds.getHeight();
239+
double relativeWidth = (xMax.get() - xMin.get()) / imageViewBounds.getWidth();
240+
double relativeHeight = (yMax.get() - yMin.get()) / imageViewBounds.getHeight();
241+
242+
return new Rectangle2D(relativeXMin, relativeYMin, relativeWidth, relativeHeight);
243+
}
244+
230245
@Override
231246
public BoundingShapeTreeItem toTreeItem() {
232247
return new BoundingPolygonTreeItem(this);
@@ -265,6 +280,17 @@ List<Double> getRelativePointsInImageView() {
265280
return points;
266281
}
267282

283+
List<Double> getMinMaxScaledPoints(double width, double height) {
284+
final List<Double> points = new ArrayList<>(getPoints().size());
285+
286+
for(VertexHandle vertexHandle : vertexHandles) {
287+
points.add((vertexHandle.getCenterX() - xMin.get()) / (xMax.get() - xMin.get()) * width);
288+
points.add((vertexHandle.getCenterY() - yMin.get()) / (yMax.get() - yMin.get()) * height);
289+
}
290+
291+
return points;
292+
}
293+
268294
ObservableList<VertexHandle> getVertexHandles() {
269295
return vertexHandles;
270296
}
@@ -312,7 +338,7 @@ private void setUpInternalListeners() {
312338
}
313339

314340
boundingShapeViewData.getNodeGroup().getChildren().addAll(c.getAddedSubList());
315-
updateWidthAndHeight();
341+
updateOutlineBox();
316342
}
317343

318344
if(c.wasRemoved()) {
@@ -326,7 +352,7 @@ private void setUpInternalListeners() {
326352
}
327353

328354
boundingShapeViewData.getNodeGroup().getChildren().removeAll(c.getRemoved());
329-
updateWidthAndHeight();
355+
updateOutlineBox();
330356
}
331357

332358
if(isConstructing() && !vertexHandles.isEmpty()) {
@@ -396,27 +422,29 @@ private void setUpInternalListeners() {
396422
boundingShapeViewData.getNodeGroup().viewOrderProperty().bind(
397423
Bindings.when(boundingShapeViewData.selectedProperty())
398424
.then(0)
399-
.otherwise(Bindings.min(width, height))
425+
.otherwise(Bindings.min(xMax.subtract(xMin), yMax.subtract(yMin)))
400426
);
401427
}
402428

403-
private void updateWidthAndHeight() {
404-
double xMin = Double.MAX_VALUE;
405-
double yMin = Double.MAX_VALUE;
406-
double xMax = 0;
407-
double yMax = 0;
429+
private void updateOutlineBox() {
430+
double newXMin = Double.MAX_VALUE;
431+
double newYMin = Double.MAX_VALUE;
432+
double newXMax = 0;
433+
double newYMax = 0;
408434

409435
List<Double> points = getPoints();
410436

411437
for(int i = 0; i < points.size(); i += 2) {
412-
xMin = Math.min(points.get(i), xMin);
413-
yMin = Math.min(points.get(i + 1), yMin);
414-
xMax = Math.max(points.get(i), xMax);
415-
yMax = Math.max(points.get(i + 1), yMax);
438+
newXMin = Math.min(points.get(i), newXMin);
439+
newYMin = Math.min(points.get(i + 1), newYMin);
440+
newXMax = Math.max(points.get(i), newXMax);
441+
newYMax = Math.max(points.get(i + 1), newYMax);
416442
}
417443

418-
width.set(Math.abs(xMax - xMin));
419-
height.set(Math.abs(yMax - yMin));
444+
xMin.set(newXMin);
445+
xMax.set(newXMax);
446+
yMin.set(newYMin);
447+
yMax.set(newYMax);
420448
}
421449

422450
private void initializeFromBoundsInImage(double imageWidth, double imageHeight) {
@@ -443,6 +471,8 @@ private void addAutoScaleListener() {
443471
vertexHandle.setCenterY(
444472
newValue.getMinY() + (vertexHandle.getCenterY() - oldValue.getMinY()) * yScaleFactor);
445473
}
474+
475+
updateOutlineBox();
446476
});
447477
}
448478

@@ -633,11 +663,17 @@ private void addMoveFunctionality() {
633663
}
634664
mouseEvent.consume();
635665
});
666+
667+
setOnMouseReleased(mouseEvent -> {
668+
if(mouseEvent.getButton().equals(MouseButton.PRIMARY)) {
669+
updateOutlineBox();
670+
}
671+
mouseEvent.consume();
672+
});
636673
}
637674

638675
private void setUpInternalListeners() {
639-
fillProperty().bind(Bindings
640-
.when(selected)
676+
fillProperty().bind(Bindings.when(selected)
641677
.then(Bindings.createObjectBinding(() -> Color
642678
.web(BoundingPolygonView.this.strokeProperty().get().toString(), 1.0)
643679
.brighter(),
@@ -663,12 +699,11 @@ private void setUpInternalListeners() {
663699
);
664700

665701
strokeProperty().bind(Bindings.when(editing)
666-
.then(Bindings.createObjectBinding(() ->
667-
((Color) getFill())
668-
.getBrightness() >
669-
BRIGHTNESS_BLACK_SWITCH_THRESHOLD
670-
? Color.BLACK :
671-
Color.WHITE,
702+
.then(Bindings.createObjectBinding(() -> ((Color) getFill())
703+
.getBrightness() >
704+
BRIGHTNESS_BLACK_SWITCH_THRESHOLD
705+
? Color.BLACK :
706+
Color.WHITE,
672707
fillProperty()))
673708
.otherwise(Bindings.createObjectBinding(() -> ((Color) getFill()),
674709
fillProperty())));

src/main/java/com/github/mfl28/boundingboxeditor/ui/BoundingShapeViewable.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import javafx.beans.property.ReadOnlyObjectProperty;
2222
import javafx.geometry.Bounds;
23+
import javafx.geometry.Rectangle2D;
2324

2425
/**
2526
* Interface to access common data of bounding shapes.
@@ -30,5 +31,7 @@ public interface BoundingShapeViewable {
3031
void autoScaleWithBoundsAndInitialize(ReadOnlyObjectProperty<Bounds> autoScaleBounds, double imageWith,
3132
double imageHeight);
3233

34+
Rectangle2D getRelativeOutlineRectangle();
35+
3336
BoundingShapeTreeItem toTreeItem();
3437
}

src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public class MainView extends BorderPane implements View {
8080
private final WorkspaceSplitPaneView workspaceSplitPane = new WorkspaceSplitPaneView();
8181
private final StatusBarView statusBar = new StatusBarView();
8282
private final SettingsDialogView settingsDialog = new SettingsDialogView();
83+
private final UISettingsConfig uiSettingsConfig = new UISettingsConfig();
8384
private ProgressDialog imageMetaDataLoadingProgressDialog;
8485
private ProgressDialog annotationImportProgressDialog;
8586
private ProgressDialog annotationExportProgressDialog;
@@ -550,6 +551,10 @@ public EditorsSplitPaneView getEditorsSplitPane() {
550551
return workspaceSplitPane.getEditorsSplitPane();
551552
}
552553

554+
public UISettingsConfig getUiSettingsConfig() {
555+
return uiSettingsConfig;
556+
}
557+
553558
public void setUpProgressDialogs() {
554559
for(ProgressDialog progressDialog : List.of(imageMetaDataLoadingProgressDialog,
555560
annotationImportProgressDialog,
@@ -614,6 +619,8 @@ private void setUpInternalListeners() {
614619
getEditorImagePane().setMaximizeImageView(newValue);
615620
getEditorImagePane().resetImageViewSize();
616621
});
622+
623+
workspaceSplitPane.showObjectPopoverProperty().bind(uiSettingsConfig.showObjectPopoverProperty());
617624
}
618625

619626
private static void displayInfoAlert(String title, String header, String content, Node additionalInfoNode) {

src/main/java/com/github/mfl28/boundingboxeditor/ui/ObjectCategoryTreeItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ void decrementNrToggledOnChildren() {
150150
private void setUpInternalListeners() {
151151
toggleIcon.fillProperty().bind(((ObjectCategory) getValue()).colorProperty());
152152

153-
toggleIcon.setOnMousePressed(event -> {
153+
toggleIcon.setOnMouseClicked(event -> {
154154
setIconToggledOn(!isIconToggledOn());
155155
event.consume();
156156
});

src/main/java/com/github/mfl28/boundingboxeditor/ui/ObjectTreeElementCell.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929
import javafx.event.EventHandler;
3030
import javafx.geometry.Pos;
3131
import javafx.scene.control.*;
32+
import javafx.scene.image.ImageView;
3233
import javafx.scene.input.ContextMenuEvent;
3334
import javafx.scene.layout.HBox;
3435
import javafx.scene.layout.Region;
3536
import javafx.scene.shape.Shape;
3637
import javafx.scene.text.Text;
38+
import org.controlsfx.control.PopOver;
3739

3840
import java.util.Objects;
3941

@@ -78,6 +80,8 @@ class ObjectTreeElementCell extends TreeCell<Object> {
7880
private final ObjectTreeElementContextMenu contextMenu = new ObjectTreeElementContextMenu();
7981
private final EventHandler<ContextMenuEvent> showContextMenuEventHandler = createShowContextMenuEventHandler();
8082
private final ChangeListener<Boolean> boundingShapeVisibilityListener = createBoundingShapeVisibilityListener();
83+
private final PopOver popOver = new PopOver();
84+
private final ImageView popOverImageView = new ImageView();
8185

8286
/**
8387
* Creates a new tree-cell object responsible for the visual representation of a {@link ObjectCategoryTreeItem}
@@ -87,6 +91,12 @@ class ObjectTreeElementCell extends TreeCell<Object> {
8791
nameText.getStyleClass().add(NAME_TEXT_STYLE);
8892
additionalInfoText.setId(INFO_TEXT_ID);
8993

94+
popOver.setAutoHide(true);
95+
popOver.setHideOnEscape(true);
96+
popOver.setArrowLocation(PopOver.ArrowLocation.LEFT_CENTER);
97+
popOver.setContentNode(popOverImageView);
98+
popOverImageView.setPreserveRatio(true);
99+
90100
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
91101
setUpInternalListeners();
92102
}
@@ -118,6 +128,9 @@ protected void updateItem(Object newCellObject, boolean empty) {
118128
contextMenu.hide();
119129
setContextMenu(null);
120130
setDraggedOver(false);
131+
popOverImageView.setImage(null);
132+
popOverImageView.setViewport(null);
133+
popOverImageView.setClip(null);
121134
} else {
122135
setGraphic(createContentBox());
123136

@@ -170,6 +183,14 @@ MenuItem getChangeObjectCategoryMenuItem() {
170183
return changeObjectCategoryMenuItem;
171184
}
172185

186+
PopOver getPopOver() {
187+
return popOver;
188+
}
189+
190+
ImageView getPopOverImageView() {
191+
return popOverImageView;
192+
}
193+
173194
private void setUpInternalListeners() {
174195
setOnMouseEntered(event -> {
175196
if(!isEmpty()) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.github.mfl28.boundingboxeditor.ui;
2+
3+
import javafx.beans.property.BooleanProperty;
4+
import javafx.beans.property.SimpleBooleanProperty;
5+
6+
public class UISettingsConfig {
7+
private final BooleanProperty showObjectPopover = new SimpleBooleanProperty(true);
8+
9+
public boolean isShowObjectPopover() {
10+
return showObjectPopover.get();
11+
}
12+
13+
public void setShowObjectPopover(boolean showObjectPopover) {
14+
this.showObjectPopover.set(showObjectPopover);
15+
}
16+
17+
public BooleanProperty showObjectPopoverProperty() {
18+
return showObjectPopover;
19+
}
20+
}

0 commit comments

Comments
 (0)