Skip to content

Commit 0e2660b

Browse files
committed
Sources should not throw exceptions when they fail to load
1 parent 8663b73 commit 0e2660b

File tree

7 files changed

+206
-105
lines changed

7 files changed

+206
-105
lines changed

core/src/main/java/edu/wpi/grip/core/Source.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,17 @@ public final OutputSocket[] getOutputSockets() {
4545
* @see #getProperties()
4646
*/
4747
public abstract void createFromProperties(EventBus eventBus, Properties properties) throws IOException;
48+
49+
50+
public <T extends Source> T start(EventBus eventBus) throws IOException {
51+
final T source = (T) start();
52+
eventBus.register(source);
53+
return source;
54+
};
55+
56+
protected abstract Source start() throws IOException;
57+
58+
public abstract Source stop() throws Exception;
59+
60+
public abstract boolean isRunning();
4861
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package edu.wpi.grip.core.events;
2+
3+
4+
import edu.wpi.grip.core.Source;
5+
6+
/**
7+
* An event that occurs when a source is started.
8+
*/
9+
public class SourceStartedEvent {
10+
private final Source source;
11+
12+
public SourceStartedEvent(Source source){
13+
this.source = source;
14+
}
15+
16+
public Source getSource() {
17+
return this.source;
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package edu.wpi.grip.core.events;
2+
3+
import edu.wpi.grip.core.Source;
4+
5+
/**
6+
* An event that occurs when a source is stopped.
7+
*/
8+
public class SourceStoppedEvent {
9+
private final Source source;
10+
11+
public SourceStoppedEvent(Source source){
12+
this.source = source;
13+
}
14+
15+
public Source getSource() {
16+
return this.source;
17+
}
18+
}

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

Lines changed: 92 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import edu.wpi.grip.core.Source;
1212
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
1313
import edu.wpi.grip.core.events.SourceRemovedEvent;
14+
import edu.wpi.grip.core.events.SourceStartedEvent;
15+
import edu.wpi.grip.core.events.SourceStoppedEvent;
1416
import org.bytedeco.javacpp.opencv_core.Mat;
1517
import org.bytedeco.javacv.*;
1618

@@ -42,7 +44,7 @@ public class CameraSource extends Source {
4244
private OutputSocket<Mat> frameOutputSocket;
4345
private OutputSocket<Number> frameRateOutputSocket;
4446
private Optional<Thread> frameThread;
45-
private Optional<FrameGrabber> grabber;
47+
private FrameGrabber grabber;
4648

4749
/**
4850
* Creates a camera source that can be used as an input to a pipeline
@@ -72,7 +74,6 @@ public CameraSource(EventBus eventBus, String address) throws IOException {
7274
* Used for serialization
7375
*/
7476
public CameraSource() {
75-
this.grabber = Optional.empty();
7677
this.frameThread = Optional.empty();
7778
}
7879

@@ -81,9 +82,8 @@ private void initialize(EventBus eventBus, FrameGrabber frameGrabber, String nam
8182
this.name = name;
8283
this.frameOutputSocket = new OutputSocket<>(eventBus, imageOutputHint);
8384
this.frameRateOutputSocket = new OutputSocket<>(eventBus, frameRateOutputHint);
84-
85-
this.eventBus.register(this);
86-
this.startVideo(frameGrabber);
85+
this.grabber = frameGrabber;
86+
eventBus.register(this);
8787
}
8888

8989
@Override
@@ -127,109 +127,117 @@ public void createFromProperties(EventBus eventBus, Properties properties) throw
127127
}
128128

129129
/**
130-
* Starts the video capture from the
131-
*
132-
* @param grabber A JavaCV {@link FrameGrabber} instance to capture from
130+
* Starts the video capture from the source device
133131
*/
134-
private synchronized void startVideo(FrameGrabber grabber) throws IOException {
132+
public CameraSource start() throws IOException, IllegalStateException {
135133
final OpenCVFrameConverter.ToMat convertToMat = new OpenCVFrameConverter.ToMat();
136-
if (this.frameThread.isPresent()) {
137-
throw new IllegalStateException("The video retrieval thread has already been started.");
138-
}
139-
if (this.grabber.isPresent()) {
140-
throw new IllegalStateException("The Frame Grabber has already been started.");
141-
}
142-
try {
143-
grabber.start();
144-
} catch (FrameGrabber.Exception e) {
145-
throw new IOException("A problem occurred trying to start the frame grabber for " + this.name, e);
146-
}
134+
synchronized (this.frameThread) {
135+
if (this.frameThread.isPresent()) {
136+
throw new IllegalStateException("The video retrieval thread has already been started.");
137+
}
138+
try {
139+
grabber.start();
140+
} catch (FrameGrabber.Exception e) {
141+
throw new IOException("A problem occurred trying to start the frame grabber for " + this.name, e);
142+
}
147143

148-
// Store the grabber only once it has been started in the case that there is an exception.
149-
this.grabber = Optional.of(grabber);
144+
final Thread frameExecutor = new Thread(() -> {
145+
long lastFrame = System.currentTimeMillis();
146+
while (!Thread.interrupted()) {
147+
final Frame videoFrame;
148+
try {
149+
videoFrame = grabber.grab();
150+
} catch (FrameGrabber.Exception e) {
151+
throw new IllegalStateException("Failed to grab image", e);
152+
}
150153

151-
final Thread frameExecutor = new Thread(() -> {
152-
long lastFrame = System.currentTimeMillis();
153-
while (!Thread.interrupted()) {
154-
final Frame videoFrame;
155-
try {
156-
videoFrame = grabber.grab();
157-
} catch (FrameGrabber.Exception e) {
158-
throw new IllegalStateException("Failed to grab image", e);
159-
}
160-
final Mat frameMat = convertToMat.convert(videoFrame);
154+
final Mat frameMat = convertToMat.convert(videoFrame);
161155

162-
if (frameMat == null || frameMat.isNull()) {
163-
throw new IllegalStateException("The camera returned a null frame Mat");
156+
if (frameMat == null || frameMat.isNull()) {
157+
throw new IllegalStateException("The camera returned a null frame Mat");
158+
}
159+
160+
frameMat.copyTo(frameOutputSocket.getValue().get());
161+
frameOutputSocket.setValue(frameOutputSocket.getValue().get());
162+
long thisMoment = System.currentTimeMillis();
163+
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
164+
lastFrame = thisMoment;
164165
}
166+
}, "Camera");
165167

166-
frameMat.copyTo(frameOutputSocket.getValue().get());
167-
frameOutputSocket.setValue(frameOutputSocket.getValue().get());
168-
long thisMoment = System.currentTimeMillis();
169-
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
170-
lastFrame = thisMoment;
171-
}
172-
}, "Camera");
173-
frameExecutor.setUncaughtExceptionHandler(
174-
(thread, exception) -> {
175-
eventBus.post(new UnexpectedThrowableEvent(exception, "Webcam Frame Grabber Thread crashed with uncaught exception"));
176-
try {
177-
stopVideo();
178-
} catch (TimeoutException e) {
179-
eventBus.post(new UnexpectedThrowableEvent(e, "Webcam Frame Grabber could not be stopped!"));
168+
frameExecutor.setUncaughtExceptionHandler(
169+
(thread, exception) -> {
170+
eventBus.post(new UnexpectedThrowableEvent(exception, "Camera Frame Grabber Thread crashed with uncaught exception"));
171+
try {
172+
stop();
173+
} catch (TimeoutException e) {
174+
eventBus.post(new UnexpectedThrowableEvent(e, "Camera Frame Grabber could not be stopped!"));
175+
}
180176
}
181-
}
182-
);
183-
frameExecutor.setDaemon(true);
184-
frameExecutor.start();
185-
frameThread = Optional.of(frameExecutor);
177+
);
178+
frameExecutor.setDaemon(true);
179+
frameExecutor.start();
180+
this.frameThread = Optional.of(frameExecutor);
181+
}
182+
eventBus.post(new SourceStartedEvent(this));
183+
return this;
186184
}
187185

188186
/**
189187
* Stops the video feed from updating the output socket.
190188
*
191-
* @throws TimeoutException If the thread running the Webcam fails to join this one after a timeout.
189+
* @throws TimeoutException If the thread running the Webcam fails to join this one after a timeout.
190+
* @throws IllegalStateException If the camera was already stopped
192191
*/
193-
private void stopVideo() throws TimeoutException {
194-
if (frameThread.isPresent()) {
195-
final Thread ex = frameThread.get();
196-
ex.interrupt();
197-
try {
198-
ex.join(TimeUnit.SECONDS.toMillis(2));
199-
if (ex.isAlive()) {
200-
throw new TimeoutException("Unable to terminate video feed from Web Camera");
201-
}
202-
} catch (InterruptedException e) {
203-
Thread.currentThread().interrupt();
204-
//TODO: Move this into a logging framework
205-
System.out.println("Caught Exception:");
206-
e.printStackTrace();
207-
} finally {
208-
frameThread = Optional.empty();
209-
// This will always run even if a timeout exception occurs
192+
public CameraSource stop() throws TimeoutException, IllegalStateException {
193+
synchronized (this.frameThread) {
194+
if (frameThread.isPresent()) {
195+
final Thread ex = frameThread.get();
196+
ex.interrupt();
210197
try {
211-
grabber.ifPresent(grabber -> {
212-
try {
213-
grabber.stop();
214-
} catch (FrameGrabber.Exception e) {
215-
throw new IllegalStateException("A problem occurred trying to stop the frame grabber", e);
216-
}
217-
});
198+
ex.join(TimeUnit.SECONDS.toMillis(500));
199+
if (ex.isAlive()) {
200+
throw new TimeoutException("Unable to terminate video feed from Web Camera");
201+
}
202+
} catch (InterruptedException e) {
203+
Thread.currentThread().interrupt();
204+
//TODO: Move this into a logging framework
205+
System.out.println("Caught Exception:");
206+
e.printStackTrace();
218207
} finally {
219-
// This will always run even if we fail to stop the grabber
220-
grabber = Optional.empty();
208+
// Clean up this resource as you can't restart a stopped thread
209+
this.frameThread = Optional.empty();
210+
// This will always run even if a timeout exception occurs
211+
try {
212+
grabber.stop();
213+
} catch (FrameGrabber.Exception e) {
214+
throw new IllegalStateException("A problem occurred trying to stop the frame grabber", e);
215+
}
221216
}
217+
} else {
218+
throw new IllegalStateException("Tried to stop a Webcam that is already stopped.");
222219
}
223-
} else {
224-
throw new IllegalStateException("Tried to stop a Webcam that is already stopped.");
220+
}
221+
eventBus.post(new SourceStoppedEvent(this));
222+
frameRateOutputSocket.setValue(0);
223+
return this;
224+
}
225+
226+
@Override
227+
public boolean isRunning() {
228+
synchronized (this.frameThread) {
229+
return this.frameThread.isPresent() && this.frameThread.get().isAlive();
225230
}
226231
}
227232

228233
@Subscribe
229234
public void onSourceRemovedEvent(SourceRemovedEvent event) throws TimeoutException {
230235
if (event.getSource() == this) {
231-
this.stopVideo();
232-
this.eventBus.unregister(this);
236+
try {
237+
this.stop();
238+
} finally {
239+
this.eventBus.unregister(this);
240+
}
233241
}
234242
}
235243

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import edu.wpi.grip.core.SocketHint;
88
import edu.wpi.grip.core.SocketHints;
99
import edu.wpi.grip.core.Source;
10+
import edu.wpi.grip.core.events.SourceStartedEvent;
1011
import org.bytedeco.javacpp.opencv_core.Mat;
1112
import org.bytedeco.javacpp.opencv_imgcodecs;
1213

@@ -31,6 +32,8 @@ public class ImageFileSource extends Source {
3132
private String path;
3233
private final SocketHint<Mat> imageOutputHint = SocketHints.Inputs.createMatSocketHint("Image", true);
3334
private OutputSocket<Mat> outputSocket;
35+
private EventBus eventBus;
36+
private boolean started = false;
3437

3538
/**
3639
* @param eventBus The event bus for the pipeline.
@@ -80,10 +83,25 @@ public void createFromProperties(EventBus eventBus, Properties properties) throw
8083
if (path == null) {
8184
throw new IllegalArgumentException("Cannot create ImageFileSource without a path.");
8285
}
83-
8486
this.initialize(eventBus, path);
8587
}
8688

89+
public ImageFileSource start() throws IOException {
90+
this.started = true;
91+
loadImage(this.path);
92+
return this;
93+
}
94+
95+
@Override
96+
public ImageFileSource stop() {
97+
return this;
98+
}
99+
100+
@Override
101+
public boolean isRunning() {
102+
return this.started;
103+
}
104+
87105
/**
88106
* Loads the image and posts an update to the {@link EventBus}
89107
*
@@ -108,5 +126,6 @@ private void loadImage(String path, final int flags) throws IOException {
108126
// TODO Output Error to GUI about invalid url
109127
throw new IOException("Error loading image " + path);
110128
}
129+
this.eventBus.post(new SourceStartedEvent(this));
111130
}
112131
}

core/src/test/java/edu/wpi/grip/core/sources/ImageFileSourceTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void setUp() throws URISyntaxException {
3232
public void testLoadImageToMat() throws IOException {
3333
// Given above setup
3434
// When
35-
final ImageFileSource fileSource = new ImageFileSource(eventBus, this.imageFile);
35+
final ImageFileSource fileSource = new ImageFileSource(eventBus, this.imageFile).start(eventBus);
3636
OutputSocket<Mat> outputSocket = fileSource.getOutputSockets()[0];
3737

3838
// Then
@@ -45,7 +45,7 @@ public void testLoadImageToMat() throws IOException {
4545

4646
@Test(expected = IOException.class)
4747
public void testReadInTextFile() throws IOException {
48-
final ImageFileSource fileSource = new ImageFileSource(eventBus, this.textFile);
48+
final ImageFileSource fileSource = new ImageFileSource(eventBus, this.textFile).start(eventBus);
4949
OutputSocket<Mat> outputSocket = fileSource.getOutputSockets()[0];
5050
assertTrue("No matrix should have been returned.", outputSocket.getValue().get().empty());
5151
}
@@ -54,7 +54,7 @@ public void testReadInTextFile() throws IOException {
5454
public void testReadInFileWithoutExtension() throws MalformedURLException, IOException {
5555
final File testFile = new File("temp" + File.separator +"fdkajdl3eaf");
5656

57-
final ImageFileSource fileSource = new ImageFileSource(eventBus, testFile);
57+
final ImageFileSource fileSource = new ImageFileSource(eventBus, testFile).start(eventBus);
5858
OutputSocket<Mat> outputSocket = fileSource.getOutputSockets()[0];
5959
assertTrue("No matrix should have been returned.", outputSocket.getValue().get().empty());
6060
}

0 commit comments

Comments
 (0)