Skip to content

Commit

Permalink
Merge pull request #164 from JLLeitschuh/fix/webcamSourceSelect
Browse files Browse the repository at this point in the history
Source Refactor with MultiImageFileSource
  • Loading branch information
ThomasJClark committed Dec 10, 2015
2 parents 8663b73 + 12be42b commit 5db3adf
Show file tree
Hide file tree
Showing 31 changed files with 1,112 additions and 184 deletions.
7 changes: 2 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ allprojects {
// Turn on test results
test {
testLogging {
afterSuite { desc, result ->
if (!desc.parent && project.hasProperty('printTestResults')) {
println "Test results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
}
}
events "failed"
exceptionFormat "full"
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/PreviousNext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package edu.wpi.grip.core;


/**
* An Object that can switch its value.
*/
public interface PreviousNext {

/**
* Perform the next action on this object.
*/
void next();

/**
* Perform the previous action on this object.
*/
void previous();

}
49 changes: 49 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/StartStoppable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package edu.wpi.grip.core;


import com.google.common.eventbus.EventBus;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
* An Object that can be stopped and started multiple times.
*/
public interface StartStoppable {

/**
* Starts this StartStoppable
*
* @return Itself
* @throws IOException If cleaning up some system resource fails
*/
default <T extends StartStoppable> T start(EventBus eventBus) throws IOException {
start();
eventBus.register(this);
return (T) this;
}

/**
* Any method that overrides this method should post a {@link edu.wpi.grip.core.events.StartedStoppedEvent}
* to the {@link EventBus} if is successfully starts.
*
* @throws IOException If cleaning up some system resource fails
*/
void start() throws IOException;

/**
* Any method that overrides this method should post a {@link edu.wpi.grip.core.events.StartedStoppedEvent}
* to the {@link EventBus} if is successfully stops.
*
* @throws TimeoutException If the thread fails to stop in a timely manner
* @throws IOException If cleaning up some system resource fails.
*/
void stop() throws TimeoutException, IOException;

/**
* Used to indicate if the source is running or stopped
*
* @return true if this source is running
*/
boolean isStarted();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package edu.wpi.grip.core.events;


import edu.wpi.grip.core.StartStoppable;

/**
* An event that occurs when a {@link StartStoppable StartStoppable's} state changes.
*/
public class StartedStoppedEvent {
private final StartStoppable startStoppable;

public StartedStoppedEvent(StartStoppable startStoppable) {
this.startStoppable = startStoppable;
}

public StartStoppable getStartStoppable() {
return this.startStoppable;
}
}
187 changes: 96 additions & 91 deletions core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import edu.wpi.grip.core.OutputSocket;
import edu.wpi.grip.core.SocketHint;
import edu.wpi.grip.core.SocketHints;
import edu.wpi.grip.core.Source;
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
import edu.wpi.grip.core.*;
import edu.wpi.grip.core.events.SourceRemovedEvent;
import edu.wpi.grip.core.events.StartedStoppedEvent;
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.*;

Expand All @@ -27,7 +25,7 @@
* Provides a way to generate a constantly updated {@link Mat} from a camera
*/
@XStreamAlias(value = "grip:Camera")
public class CameraSource extends Source {
public final class CameraSource extends Source implements StartStoppable {

private final static String DEVICE_NUMBER_PROPERTY = "deviceNumber";
private final static String ADDRESS_PROPERTY = "address";
Expand All @@ -42,7 +40,7 @@ public class CameraSource extends Source {
private OutputSocket<Mat> frameOutputSocket;
private OutputSocket<Number> frameRateOutputSocket;
private Optional<Thread> frameThread;
private Optional<FrameGrabber> grabber;
private FrameGrabber grabber;

/**
* Creates a camera source that can be used as an input to a pipeline
Expand Down Expand Up @@ -72,7 +70,6 @@ public CameraSource(EventBus eventBus, String address) throws IOException {
* Used for serialization
*/
public CameraSource() {
this.grabber = Optional.empty();
this.frameThread = Optional.empty();
}

Expand All @@ -81,9 +78,7 @@ private void initialize(EventBus eventBus, FrameGrabber frameGrabber, String nam
this.name = name;
this.frameOutputSocket = new OutputSocket<>(eventBus, imageOutputHint);
this.frameRateOutputSocket = new OutputSocket<>(eventBus, frameRateOutputHint);

this.eventBus.register(this);
this.startVideo(frameGrabber);
this.grabber = frameGrabber;
}

@Override
Expand Down Expand Up @@ -127,109 +122,119 @@ public void createFromProperties(EventBus eventBus, Properties properties) throw
}

/**
* Starts the video capture from the
*
* @param grabber A JavaCV {@link FrameGrabber} instance to capture from
* Starts the video capture from this frame grabber.
*/
private synchronized void startVideo(FrameGrabber grabber) throws IOException {
public void start() throws IOException, IllegalStateException {
final OpenCVFrameConverter.ToMat convertToMat = new OpenCVFrameConverter.ToMat();
if (this.frameThread.isPresent()) {
throw new IllegalStateException("The video retrieval thread has already been started.");
}
if (this.grabber.isPresent()) {
throw new IllegalStateException("The Frame Grabber has already been started.");
}
try {
grabber.start();
} catch (FrameGrabber.Exception e) {
throw new IOException("A problem occurred trying to start the frame grabber for " + this.name, e);
}
synchronized (this) {
if (this.frameThread.isPresent()) {
throw new IllegalStateException("The video retrieval thread has already been started.");
}
try {
grabber.start();
} catch (FrameGrabber.Exception e) {
throw new IOException("A problem occurred trying to start the frame grabber for " + this.name, e);
}

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

final Thread frameExecutor = new Thread(() -> {
long lastFrame = System.currentTimeMillis();
while (!Thread.interrupted()) {
final Frame videoFrame;
try {
videoFrame = grabber.grab();
} catch (FrameGrabber.Exception e) {
throw new IllegalStateException("Failed to grab image", e);
}
final Mat frameMat = convertToMat.convert(videoFrame);
final Mat frameMat = convertToMat.convert(videoFrame);

if (frameMat == null || frameMat.isNull()) {
throw new IllegalStateException("The camera returned a null frame Mat");
if (frameMat == null || frameMat.isNull()) {
throw new IllegalStateException("The camera returned a null frame Mat");
}

frameMat.copyTo(frameOutputSocket.getValue().get());
frameOutputSocket.setValue(frameOutputSocket.getValue().get());
long thisMoment = System.currentTimeMillis();
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
lastFrame = thisMoment;
}
}, "Camera");

frameMat.copyTo(frameOutputSocket.getValue().get());
frameOutputSocket.setValue(frameOutputSocket.getValue().get());
long thisMoment = System.currentTimeMillis();
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
lastFrame = thisMoment;
}
}, "Camera");
frameExecutor.setUncaughtExceptionHandler(
(thread, exception) -> {
eventBus.post(new UnexpectedThrowableEvent(exception, "Webcam Frame Grabber Thread crashed with uncaught exception"));
try {
stopVideo();
} catch (TimeoutException e) {
eventBus.post(new UnexpectedThrowableEvent(e, "Webcam Frame Grabber could not be stopped!"));
frameExecutor.setUncaughtExceptionHandler(
(thread, exception) -> {
// TODO: This should use the ExceptionWitness once that has a UI component added for it
eventBus.post(new UnexpectedThrowableEvent(exception, "Camera Frame Grabber Thread crashed with uncaught exception"));
try {
stop();
} catch (TimeoutException e) {
// TODO: This should use the ExceptionWitness once that has a UI component added for it
eventBus.post(new UnexpectedThrowableEvent(e, "Camera Frame Grabber could not be stopped!"));
}
}
}
);
frameExecutor.setDaemon(true);
frameExecutor.start();
frameThread = Optional.of(frameExecutor);
);
frameExecutor.setDaemon(true);
frameExecutor.start();
this.frameThread = Optional.of(frameExecutor);
// This should only be posted now that it is running
eventBus.post(new StartedStoppedEvent(this));
}
}

/**
* Stops the video feed from updating the output socket.
* Stops this source.
* This will stop the source publishing new socket values after this method returns.
*
* @throws TimeoutException If the thread running the Webcam fails to join this one after a timeout.
* @return The source that was stopped
* @throws TimeoutException if the thread running the source fails to stop.
* @throws IOException If there is a problem stopping the Source
*/
private void stopVideo() throws TimeoutException {
if (frameThread.isPresent()) {
final Thread ex = frameThread.get();
ex.interrupt();
try {
ex.join(TimeUnit.SECONDS.toMillis(2));
if (ex.isAlive()) {
throw new TimeoutException("Unable to terminate video feed from Web Camera");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
//TODO: Move this into a logging framework
System.out.println("Caught Exception:");
e.printStackTrace();
} finally {
frameThread = Optional.empty();
// This will always run even if a timeout exception occurs
public final void stop() throws TimeoutException, IllegalStateException {
synchronized (this) {
if (frameThread.isPresent()) {
final Thread ex = frameThread.get();
ex.interrupt();
try {
grabber.ifPresent(grabber -> {
try {
grabber.stop();
} catch (FrameGrabber.Exception e) {
throw new IllegalStateException("A problem occurred trying to stop the frame grabber", e);
}
});
ex.join(TimeUnit.SECONDS.toMillis(500));
if (ex.isAlive()) {
throw new TimeoutException("Unable to terminate video feed from Web Camera");
}
// This should only be removed if the thread is successfully killed off
frameThread = Optional.empty();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
//TODO: Move this into a logging framework
System.err.println("Caught Exception:");
e.printStackTrace();
} finally {
// This will always run even if we fail to stop the grabber
grabber = Optional.empty();
// This will always run even if a timeout exception occurs
try {
// Calling this multiple times will have no effect
grabber.stop();
} catch (FrameGrabber.Exception e) {
throw new IllegalStateException("A problem occurred trying to stop the frame grabber", e);
}
}
} else {
throw new IllegalStateException("Tried to stop a Webcam that is already stopped.");
}
} else {
throw new IllegalStateException("Tried to stop a Webcam that is already stopped.");
}
eventBus.post(new StartedStoppedEvent(this));
frameRateOutputSocket.setValue(0);
}

@Override
public synchronized boolean isStarted() {
return this.frameThread.isPresent() && this.frameThread.get().isAlive();
}

@Subscribe
public void onSourceRemovedEvent(SourceRemovedEvent event) throws TimeoutException {
if (event.getSource() == this) {
this.stopVideo();
this.eventBus.unregister(this);
try {
if (this.isStarted()) this.stop();
} finally {
this.eventBus.unregister(this);
}
}
}

Expand Down
Loading

0 comments on commit 5db3adf

Please sign in to comment.