2016-12-23 11:48:56 -05:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2018-01-02 09:20:21 -08:00
|
|
|
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
2016-12-23 11:48:56 -05:00
|
|
|
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
|
|
|
|
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
|
|
|
|
/* the project. */
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
|
2018-09-23 21:20:12 -07:00
|
|
|
package edu.wpi.first.vision;
|
2016-12-23 11:48:56 -05:00
|
|
|
|
2018-05-24 00:31:04 -04:00
|
|
|
import org.opencv.core.Mat;
|
|
|
|
|
|
2016-12-23 11:48:56 -05:00
|
|
|
import edu.wpi.cscore.CvSink;
|
|
|
|
|
import edu.wpi.cscore.VideoSource;
|
2018-09-23 21:20:12 -07:00
|
|
|
import edu.wpi.first.cameraserver.CameraServerSharedStore;
|
2018-04-29 13:29:07 -07:00
|
|
|
|
2016-12-23 11:48:56 -05:00
|
|
|
/**
|
|
|
|
|
* A vision runner is a convenient wrapper object to make it easy to run vision pipelines
|
|
|
|
|
* from robot code. The easiest way to use this is to run it in a {@link VisionThread}
|
|
|
|
|
* and use the listener to take snapshots of the pipeline's outputs.
|
|
|
|
|
*
|
|
|
|
|
* @see VisionPipeline
|
|
|
|
|
* @see VisionThread
|
2018-07-29 12:28:41 -04:00
|
|
|
* @see <a href="package-summary.html">vision</a>
|
2016-12-23 11:48:56 -05:00
|
|
|
*/
|
|
|
|
|
public class VisionRunner<P extends VisionPipeline> {
|
|
|
|
|
private final CvSink m_cvSink = new CvSink("VisionRunner CvSink");
|
|
|
|
|
private final P m_pipeline;
|
|
|
|
|
private final Mat m_image = new Mat();
|
|
|
|
|
private final Listener<? super P> m_listener;
|
2017-12-10 20:58:14 -08:00
|
|
|
private volatile boolean m_enabled = true;
|
2016-12-23 11:48:56 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Listener interface for a callback that should run after a pipeline has processed its input.
|
|
|
|
|
*
|
|
|
|
|
* @param <P> the type of the pipeline this listener is for
|
|
|
|
|
*/
|
|
|
|
|
@FunctionalInterface
|
|
|
|
|
public interface Listener<P extends VisionPipeline> {
|
|
|
|
|
/**
|
|
|
|
|
* Called when the pipeline has run. This shouldn't take much time to run because it will delay
|
|
|
|
|
* later calls to the pipeline's {@link VisionPipeline#process process} method. Copying the
|
|
|
|
|
* outputs and code that uses the copies should be <i>synchronized</i> on the same mutex to
|
|
|
|
|
* prevent multiple threads from reading and writing to the same memory at the same time.
|
|
|
|
|
*
|
|
|
|
|
* @param pipeline the vision pipeline that ran
|
|
|
|
|
*/
|
|
|
|
|
void copyPipelineOutputs(P pipeline);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new vision runner. It will take images from the {@code videoSource}, send them to
|
|
|
|
|
* the {@code pipeline}, and call the {@code listener} when the pipeline has finished to alert
|
|
|
|
|
* user code when it is safe to access the pipeline's outputs.
|
|
|
|
|
*
|
|
|
|
|
* @param videoSource the video source to use to supply images for the pipeline
|
|
|
|
|
* @param pipeline the vision pipeline to run
|
|
|
|
|
* @param listener a function to call after the pipeline has finished running
|
|
|
|
|
*/
|
|
|
|
|
public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
|
|
|
|
|
this.m_pipeline = pipeline;
|
|
|
|
|
this.m_listener = listener;
|
|
|
|
|
m_cvSink.setSource(videoSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Runs the pipeline one time, giving it the next image from the video source specified
|
|
|
|
|
* in the constructor. This will block until the source either has an image or throws an error.
|
|
|
|
|
* If the source successfully supplied a frame, the pipeline's image input will be set,
|
|
|
|
|
* the pipeline will run, and the listener specified in the constructor will be called to notify
|
|
|
|
|
* it that the pipeline ran.
|
|
|
|
|
*
|
|
|
|
|
* <p>This method is exposed to allow teams to add additional functionality or have their own
|
|
|
|
|
* ways to run the pipeline. Most teams, however, should just use {@link #runForever} in its own
|
|
|
|
|
* thread using a {@link VisionThread}.</p>
|
|
|
|
|
*/
|
|
|
|
|
public void runOnce() {
|
2018-04-29 13:29:07 -07:00
|
|
|
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
|
|
|
|
|
|
|
|
|
if (id != null && Thread.currentThread().getId() == id.longValue()) {
|
2016-12-23 11:48:56 -05:00
|
|
|
throw new IllegalStateException(
|
|
|
|
|
"VisionRunner.runOnce() cannot be called from the main robot thread");
|
|
|
|
|
}
|
2018-04-29 13:29:07 -07:00
|
|
|
runOnceInternal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void runOnceInternal() {
|
2016-12-23 11:48:56 -05:00
|
|
|
long frameTime = m_cvSink.grabFrame(m_image);
|
|
|
|
|
if (frameTime == 0) {
|
|
|
|
|
// There was an error, report it
|
|
|
|
|
String error = m_cvSink.getError();
|
2018-04-29 13:29:07 -07:00
|
|
|
CameraServerSharedStore.getCameraServerShared().reportDriverStationError(error);
|
2016-12-23 11:48:56 -05:00
|
|
|
} else {
|
|
|
|
|
// No errors, process the image
|
|
|
|
|
m_pipeline.process(m_image);
|
|
|
|
|
m_listener.copyPipelineOutputs(m_pipeline);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A convenience method that calls {@link #runOnce()} in an infinite loop. This must
|
|
|
|
|
* be run in a dedicated thread, and cannot be used in the main robot thread because
|
|
|
|
|
* it will freeze the robot program.
|
|
|
|
|
*
|
|
|
|
|
* <p><strong>Do not call this method directly from the main thread.</strong></p>
|
|
|
|
|
*
|
|
|
|
|
* @throws IllegalStateException if this is called from the main robot thread
|
|
|
|
|
* @see VisionThread
|
|
|
|
|
*/
|
|
|
|
|
public void runForever() {
|
2018-04-29 13:29:07 -07:00
|
|
|
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
|
|
|
|
|
|
|
|
|
if (id != null && Thread.currentThread().getId() == id.longValue()) {
|
2016-12-23 11:48:56 -05:00
|
|
|
throw new IllegalStateException(
|
|
|
|
|
"VisionRunner.runForever() cannot be called from the main robot thread");
|
|
|
|
|
}
|
2017-12-10 20:58:14 -08:00
|
|
|
while (m_enabled && !Thread.interrupted()) {
|
2018-04-29 13:29:07 -07:00
|
|
|
runOnceInternal();
|
2016-12-23 11:48:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-10 20:58:14 -08:00
|
|
|
/**
|
|
|
|
|
* Stop a RunForever() loop.
|
|
|
|
|
*/
|
|
|
|
|
public void stop() {
|
|
|
|
|
m_enabled = false;
|
|
|
|
|
}
|
2016-12-23 11:48:56 -05:00
|
|
|
}
|