Skip to content

Commit 4edbb0f

Browse files
committed
Adds SwitchableSource and SwitchableSourceControlsView
1 parent 9bffdfc commit 4edbb0f

File tree

10 files changed

+291
-18
lines changed

10 files changed

+291
-18
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package edu.wpi.grip.core;
2+
3+
4+
import java.io.IOException;
5+
6+
/**
7+
* A {@link Source} that can switch its value.
8+
*/
9+
public abstract class SwitchableSource extends Source {
10+
11+
/**
12+
* Loads the next value that this source has into the socket.
13+
* @throws IOException If the source fails to load the next value into the socket.
14+
*/
15+
public abstract void nextValue() throws IOException;
16+
17+
/**
18+
* Loads the previous value that this source has into the socket.
19+
* @throws IOException If the source fails to load the next value into the socket.
20+
*/
21+
public abstract void previousValue() throws IOException;
22+
23+
}

core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import edu.wpi.grip.core.SocketHints;
99
import edu.wpi.grip.core.Source;
1010
import edu.wpi.grip.core.events.SourceStartedEvent;
11+
import edu.wpi.grip.core.util.OpenCVUtility;
1112
import org.bytedeco.javacpp.opencv_core.Mat;
1213
import org.bytedeco.javacpp.opencv_imgcodecs;
1314

@@ -93,21 +94,10 @@ private void loadImage(String path) throws IOException {
9394
this.loadImage(path, opencv_imgcodecs.IMREAD_COLOR);
9495
}
9596

96-
/**
97-
* Loads the image and posts an update to the {@link EventBus}
98-
*
99-
* @param path The location on the file system where the image exists.
100-
* @param flags Flags to pass to imread {@link opencv_imgcodecs#imread(String, int)}
101-
*/
97+
10298
private void loadImage(String path, final int flags) throws IOException {
103-
Mat mat = opencv_imgcodecs.imread(path, flags);
104-
if (!mat.empty()) {
105-
mat.copyTo(this.outputSocket.getValue().get());
106-
this.outputSocket.setValue(this.outputSocket.getValue().get());
107-
} else {
108-
// TODO Output Error to GUI about invalid url
109-
throw new IOException("Error loading image " + path);
110-
}
99+
OpenCVUtility.loadImage(path, flags, this.outputSocket.getValue().get());
100+
this.outputSocket.setValue(this.outputSocket.getValue().get());
111101
this.eventBus.post(new SourceStartedEvent(this));
112102
}
113103
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package edu.wpi.grip.core.sources;
2+
3+
4+
import com.google.common.eventbus.EventBus;
5+
import edu.wpi.grip.core.OutputSocket;
6+
import edu.wpi.grip.core.SocketHint;
7+
import edu.wpi.grip.core.SocketHints;
8+
import edu.wpi.grip.core.SwitchableSource;
9+
import edu.wpi.grip.core.events.SourceStartedEvent;
10+
import edu.wpi.grip.core.util.OpenCVUtility;
11+
import org.bytedeco.javacpp.opencv_core;
12+
13+
import java.io.File;
14+
import java.io.IOException;
15+
import java.net.URLDecoder;
16+
import java.nio.file.Paths;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import java.util.Properties;
20+
import java.util.stream.Collectors;
21+
22+
import static com.google.common.base.Preconditions.checkElementIndex;
23+
import static com.google.common.base.Preconditions.checkNotNull;
24+
25+
/**
26+
* A Source that supports multiple images. They can be toggled using {@link MultiImageFileSource#nextValue()} and
27+
* {@link MultiImageFileSource#previousValue()}
28+
*/
29+
public class MultiImageFileSource extends SwitchableSource {
30+
private static final String INDEX_PROPERTY = "index";
31+
private static final String SIZE_PROPERTY = "size";
32+
33+
private final SocketHint<opencv_core.Mat> imageOutputHint = SocketHints.Inputs.createMatSocketHint("Image", true);
34+
private OutputSocket<opencv_core.Mat> outputSocket;
35+
36+
private EventBus eventBus;
37+
private List<String> paths;
38+
private int index = 0;
39+
40+
/**
41+
* @param eventBus The event bus.
42+
* @param files A list of files to be loaded.
43+
* @param index The index to use as the first file that is in the socket.
44+
* @throws IOException If the source fails to load any of the images
45+
*/
46+
public MultiImageFileSource(final EventBus eventBus, final List<File> files, int index) throws IOException {
47+
this.initialize(eventBus, files.stream()
48+
.map(file -> URLDecoder.decode(Paths.get(file.toURI()).toString()))
49+
.collect(Collectors.toList()), index);
50+
}
51+
52+
public MultiImageFileSource(final EventBus eventBus, final List<File> files) throws IOException {
53+
this(eventBus, files, 0);
54+
}
55+
56+
/**
57+
* Used only for serialization
58+
*/
59+
public MultiImageFileSource(){ /* no op */ }
60+
61+
62+
@SuppressWarnings("unchecked")
63+
private void initialize(final EventBus eventBus, final List<String> paths, int index) throws IOException {
64+
this.eventBus = checkNotNull(eventBus, "Event bus can not be null");
65+
checkElementIndex(index, paths.size(), "File List Index");
66+
this.paths = paths;
67+
this.outputSocket = new OutputSocket(eventBus, imageOutputHint);
68+
for (String path : paths) {
69+
// Ensure that all of the images can be loaded
70+
loadImage(path);
71+
}
72+
loadImage(paths.get(index));
73+
}
74+
75+
@Override
76+
public String getName() {
77+
return "Multi-Image";
78+
}
79+
80+
@Override
81+
protected OutputSocket[] createOutputSockets() {
82+
return new OutputSocket[]{
83+
outputSocket
84+
};
85+
}
86+
87+
@Override
88+
public Properties getProperties() {
89+
final Properties properties = new Properties();
90+
properties.setProperty(SIZE_PROPERTY, Integer.toString(paths.size()));
91+
properties.setProperty(INDEX_PROPERTY, Integer.toString(index));
92+
for (int i = 0; i < paths.size(); i++) {
93+
properties.setProperty(getPathProperty(i), paths.get(i));
94+
}
95+
return properties;
96+
}
97+
98+
@Override
99+
public void createFromProperties(EventBus eventBus, Properties properties) throws IOException {
100+
final int index = Integer.valueOf(properties.getProperty(INDEX_PROPERTY));
101+
final int size = Integer.valueOf(properties.getProperty(SIZE_PROPERTY));
102+
final List<String> paths = new ArrayList<>(size);
103+
for (int i = 0; i < size; i++) {
104+
paths.add(properties.getProperty(getPathProperty(i)));
105+
}
106+
this.initialize(eventBus, paths, index);
107+
}
108+
109+
110+
private void loadImage(String path) throws IOException {
111+
OpenCVUtility.loadImage(path, this.outputSocket.getValue().get());
112+
this.outputSocket.setValue(this.outputSocket.getValue().get());
113+
this.eventBus.post(new SourceStartedEvent(this));
114+
}
115+
116+
117+
private int getListIndex(){
118+
final int listSize = paths.size();
119+
int indexToGet = index % listSize;
120+
//this might happen to be negative
121+
return indexToGet < 0 ? indexToGet + listSize : indexToGet;
122+
}
123+
124+
@Override
125+
public synchronized void nextValue() throws IOException {
126+
index ++;
127+
loadImage(paths.get(getListIndex()));
128+
}
129+
130+
@Override
131+
public synchronized void previousValue() throws IOException {
132+
index--;
133+
loadImage(paths.get(getListIndex()));
134+
}
135+
136+
private static String getPathProperty(int index) {
137+
return "path[" + index + "]";
138+
}
139+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package edu.wpi.grip.core.util;
2+
3+
4+
import com.google.common.eventbus.EventBus;
5+
import org.bytedeco.javacpp.opencv_core.Mat;
6+
import org.bytedeco.javacpp.opencv_imgcodecs;
7+
8+
import java.io.IOException;
9+
10+
import static com.google.common.base.Preconditions.checkNotNull;
11+
12+
public class OpenCVUtility {
13+
private OpenCVUtility() { /* no op */ }
14+
15+
public static void loadImage(String path, Mat dst) throws IOException {
16+
loadImage(path, opencv_imgcodecs.IMREAD_COLOR, dst);
17+
}
18+
19+
/**
20+
* Loads the image and posts an update to the {@link EventBus}
21+
*
22+
* @param path The location on the file system where the image exists.
23+
* @param flags Flags to pass to imread {@link opencv_imgcodecs#imread(String, int)}
24+
*/
25+
public static void loadImage(String path, final int flags, Mat dst) throws IOException {
26+
checkNotNull(path, "The path can not be null");
27+
checkNotNull(dst, "The destination Mat can not be null");
28+
final Mat img = opencv_imgcodecs.imread(path, flags);
29+
if (!img.empty()) {
30+
img.copyTo(dst);
31+
} else {
32+
throw new IOException("Error loading image " + path);
33+
}
34+
}
35+
36+
}

ui/src/main/java/edu/wpi/grip/ui/pipeline/AddSourceView.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import edu.wpi.grip.core.events.SourceAddedEvent;
66
import edu.wpi.grip.core.sources.CameraSource;
77
import edu.wpi.grip.core.sources.ImageFileSource;
8+
import edu.wpi.grip.core.sources.MultiImageFileSource;
89
import edu.wpi.grip.ui.util.DPIUtility;
910
import javafx.application.Platform;
1011
import javafx.event.EventHandler;
@@ -56,13 +57,19 @@ public AddSourceView(EventBus eventBus) {
5657
if (imageFiles == null) return;
5758

5859
// Add a new source for each image .
59-
imageFiles.forEach(file -> {
60+
if(imageFiles.size() == 1) {
6061
try {
61-
eventBus.post(new SourceAddedEvent(new ImageFileSource(eventBus, file)));
62+
eventBus.post(new SourceAddedEvent(new ImageFileSource(eventBus, imageFiles.get(0))));
6263
} catch (IOException e) {
63-
eventBus.post(new UnexpectedThrowableEvent(e, "Tried to create an invalid source"));
64+
eventBus.post(new UnexpectedThrowableEvent(e, "The image selected was invalid"));
6465
}
65-
});
66+
} else {
67+
try {
68+
eventBus.post(new SourceAddedEvent(new MultiImageFileSource(eventBus, imageFiles)));
69+
} catch (IOException e) {
70+
eventBus.post(new UnexpectedThrowableEvent(e, "One of the images selected was invalid"));
71+
}
72+
}
6673
});
6774

6875
addButton("Add\nWebcam", getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"), mouseEvent -> {

ui/src/main/java/edu/wpi/grip/ui/pipeline/source/SourceControlsView.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import javafx.scene.layout.HBox;
44

5+
/**
6+
* A JavaFX control that shows controls for a given source.
7+
* @param <T> The type of Source this control is for.
8+
*/
59
public abstract class SourceControlsView<T> extends HBox {
610

711
}

ui/src/main/java/edu/wpi/grip/ui/pipeline/source/SourceControlsViewFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import com.google.common.eventbus.EventBus;
44
import edu.wpi.grip.core.Source;
55
import edu.wpi.grip.core.StopStartSource;
6+
import edu.wpi.grip.core.SwitchableSource;
67

78
public class SourceControlsViewFactory {
89
private SourceControlsViewFactory() { /* no op */ }
910

1011
public static <T> SourceControlsView<T> createSourceControlsView(EventBus eventBus, Source source) {
1112
if (source instanceof StopStartSource) {
1213
return (SourceControlsView<T>) new StopStartSourceControlsView(eventBus, (StopStartSource) source);
14+
} else if (source instanceof SwitchableSource) {
15+
return (SourceControlsView<T>) new SwitchableSourceControlsView(eventBus, (SwitchableSource) source);
1316
} else {
1417
return new SourceControlsView() {};
1518
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package edu.wpi.grip.ui.pipeline.source;
2+
3+
4+
import com.google.common.eventbus.EventBus;
5+
import edu.wpi.grip.core.SwitchableSource;
6+
import edu.wpi.grip.core.util.ExceptionWitness;
7+
import edu.wpi.grip.ui.util.DPIUtility;
8+
import javafx.scene.control.ToggleButton;
9+
import javafx.scene.control.Tooltip;
10+
import javafx.scene.image.Image;
11+
import javafx.scene.image.ImageView;
12+
import org.controlsfx.control.SegmentedButton;
13+
14+
import java.io.IOException;
15+
16+
import static com.google.common.base.Preconditions.checkNotNull;
17+
18+
/**
19+
* Provides controls for a {@link SwitchableSource}
20+
*/
21+
public class SwitchableSourceControlsView extends SourceControlsView<SwitchableSource> {
22+
23+
private static final Image nextImage = new Image(StopStartSourceControlsView.class.getResourceAsStream("/edu/wpi/grip/ui/icons/next.png"));
24+
private static final Image previousImage = new Image(StopStartSourceControlsView.class.getResourceAsStream("/edu/wpi/grip/ui/icons/previous.png"));
25+
26+
private final ExceptionWitness witness;
27+
28+
public SwitchableSourceControlsView(final EventBus eventBus, final SwitchableSource source) {
29+
checkNotNull(source, "Source can not be null");
30+
this.witness = new ExceptionWitness(eventBus, source);
31+
32+
final ToggleButton nextButton = new ToggleButton(null, createButtonGraphic(nextImage));
33+
nextButton.selectedProperty().addListener(event -> {
34+
try {
35+
source.nextValue();
36+
witness.clearException();
37+
} catch (IOException e) {
38+
e.printStackTrace();
39+
witness.flagException(e);
40+
}
41+
nextButton.setSelected(false);
42+
});
43+
final String nextMessage = "Next";
44+
nextButton.setTooltip(new Tooltip(nextMessage));
45+
nextButton.setAccessibleText(nextMessage);
46+
47+
final ToggleButton previousButton = new ToggleButton(null, createButtonGraphic(previousImage));
48+
previousButton.selectedProperty().addListener(event -> {
49+
try {
50+
source.previousValue();
51+
witness.clearException();
52+
} catch (IOException e) {
53+
e.printStackTrace();
54+
witness.flagException(e);
55+
}
56+
previousButton.setSelected(false);
57+
});
58+
final String previousMessage = "Previous";
59+
previousButton.setTooltip(new Tooltip(previousMessage));
60+
previousButton.setAccessibleText(previousMessage);
61+
62+
this.getChildren().addAll(new SegmentedButton(previousButton, nextButton));
63+
}
64+
65+
private ImageView createButtonGraphic(Image image) {
66+
final ImageView icon = new ImageView(image);
67+
icon.setFitHeight(DPIUtility.MINI_ICON_SIZE);
68+
icon.setFitWidth(DPIUtility.MINI_ICON_SIZE);
69+
return icon;
70+
}
71+
}
362 Bytes
Loading
802 Bytes
Loading

0 commit comments

Comments
 (0)