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}