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