2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2016-12-23 11:48:56 -05:00
|
|
|
|
2018-09-23 21:20:12 -07:00
|
|
|
package edu.wpi.first.vision;
|
2016-12-23 11:48:56 -05:00
|
|
|
|
2018-09-23 21:20:12 -07:00
|
|
|
import edu.wpi.first.cameraserver.CameraServerSharedStore;
|
2021-04-10 11:42:41 -07:00
|
|
|
import edu.wpi.first.cscore.CvSink;
|
|
|
|
|
import edu.wpi.first.cscore.VideoSource;
|
2020-12-29 22:45:16 -08:00
|
|
|
import org.opencv.core.Mat;
|
2018-04-29 13:29:07 -07:00
|
|
|
|
2016-12-23 11:48:56 -05:00
|
|
|
/**
|
2020-12-29 22:45:16 -08: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.
|
2016-12-23 11:48:56 -05:00
|
|
|
*
|
|
|
|
|
* @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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* 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.
|
2016-12-23 11:48:56 -05:00
|
|
|
*
|
|
|
|
|
* @param videoSource the video source to use to supply images for the pipeline
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param pipeline the vision pipeline to run
|
|
|
|
|
* @param listener a function to call after the pipeline has finished running
|
2016-12-23 11:48:56 -05:00
|
|
|
*/
|
|
|
|
|
public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
|
|
|
|
|
this.m_pipeline = pipeline;
|
|
|
|
|
this.m_listener = listener;
|
|
|
|
|
m_cvSink.setSource(videoSource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* 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.
|
2016-12-23 11:48:56 -05:00
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* <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}.
|
2016-12-23 11:48:56 -05:00
|
|
|
*/
|
|
|
|
|
public void runOnce() {
|
2018-04-29 13:29:07 -07:00
|
|
|
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
|
|
|
|
|
2019-08-01 01:19:48 -04:00
|
|
|
if (id != null && Thread.currentThread().getId() == id) {
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* 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.
|
2016-12-23 11:48:56 -05:00
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* <p><strong>Do not call this method directly from the main thread.</strong>
|
2016-12-23 11:48:56 -05:00
|
|
|
*
|
|
|
|
|
* @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();
|
|
|
|
|
|
2019-08-01 01:19:48 -04:00
|
|
|
if (id != null && Thread.currentThread().getId() == id) {
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Stop a RunForever() loop. */
|
2017-12-10 20:58:14 -08:00
|
|
|
public void stop() {
|
|
|
|
|
m_enabled = false;
|
|
|
|
|
}
|
2016-12-23 11:48:56 -05:00
|
|
|
}
|