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}