001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2016-2017. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj.vision;
009
010import edu.wpi.cscore.CvSink;
011import edu.wpi.cscore.VideoSource;
012import edu.wpi.first.wpilibj.DriverStation;
013import edu.wpi.first.wpilibj.RobotBase;
014import org.opencv.core.Mat;
015
016/**
017 * A vision runner is a convenient wrapper object to make it easy to run vision pipelines
018 * from robot code. The easiest  way to use this is to run it in a {@link VisionThread}
019 * and use the listener to take snapshots of the pipeline's outputs.
020 *
021 * @see VisionPipeline
022 * @see VisionThread
023 * @see edu.wpi.first.wpilibj.vision
024 */
025public class VisionRunner<P extends VisionPipeline> {
026
027  private final CvSink m_cvSink = new CvSink("VisionRunner CvSink");
028  private final P m_pipeline;
029  private final Mat m_image = new Mat();
030  private final Listener<? super P> m_listener;
031
032  /**
033   * Listener interface for a callback that should run after a pipeline has processed its input.
034   *
035   * @param <P> the type of the pipeline this listener is for
036   */
037  @FunctionalInterface
038  public interface Listener<P extends VisionPipeline> {
039
040    /**
041     * Called when the pipeline has run. This shouldn't take much time to run because it will delay
042     * later calls to the pipeline's {@link VisionPipeline#process process} method. Copying the
043     * outputs and code that uses the copies should be <i>synchronized</i> on the same mutex to
044     * prevent multiple threads from reading and writing to the same memory at the same time.
045     *
046     * @param pipeline the vision pipeline that ran
047     */
048    void copyPipelineOutputs(P pipeline);
049
050  }
051
052  /**
053   * Creates a new vision runner. It will take images from the {@code videoSource}, send them to
054   * the {@code pipeline}, and call the {@code listener} when the pipeline has finished to alert
055   * user code when it is safe to access the pipeline's outputs.
056   *
057   * @param videoSource the video source to use to supply images for the pipeline
058   * @param pipeline    the vision pipeline to run
059   * @param listener    a function to call after the pipeline has finished running
060   */
061  public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
062    this.m_pipeline = pipeline;
063    this.m_listener = listener;
064    m_cvSink.setSource(videoSource);
065  }
066
067  /**
068   * Runs the pipeline one time, giving it the next image from the video source specified
069   * in the constructor. This will block until the source either has an image or throws an error.
070   * If the source successfully supplied a frame, the pipeline's image input will be set,
071   * the pipeline will run, and the listener specified in the constructor will be called to notify
072   * it that the pipeline ran.
073   *
074   * <p>This method is exposed to allow teams to add additional functionality or have their own
075   * ways to run the pipeline. Most teams, however, should just use {@link #runForever} in its own
076   * thread using a {@link VisionThread}.</p>
077   */
078  public void runOnce() {
079    if (Thread.currentThread().getId() == RobotBase.MAIN_THREAD_ID) {
080      throw new IllegalStateException(
081          "VisionRunner.runOnce() cannot be called from the main robot thread");
082    }
083    long frameTime = m_cvSink.grabFrame(m_image);
084    if (frameTime == 0) {
085      // There was an error, report it
086      String error = m_cvSink.getError();
087      DriverStation.reportError(error, true);
088    } else {
089      // No errors, process the image
090      m_pipeline.process(m_image);
091      m_listener.copyPipelineOutputs(m_pipeline);
092    }
093  }
094
095  /**
096   * A convenience method that calls {@link #runOnce()} in an infinite loop. This must
097   * be run in a dedicated thread, and cannot be used in the main robot thread because
098   * it will freeze the robot program.
099   *
100   * <p><strong>Do not call this method directly from the main thread.</strong></p>
101   *
102   * @throws IllegalStateException if this is called from the main robot thread
103   * @see VisionThread
104   */
105  public void runForever() {
106    if (Thread.currentThread().getId() == RobotBase.MAIN_THREAD_ID) {
107      throw new IllegalStateException(
108          "VisionRunner.runForever() cannot be called from the main robot thread");
109    }
110    while (!Thread.interrupted()) {
111      runOnce();
112    }
113  }
114
115}