001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.vision.processes; 019 020import java.util.ArrayList; 021import java.util.List; 022import java.util.concurrent.Callable; 023import java.util.concurrent.CompletableFuture; 024import java.util.concurrent.Future; 025import java.util.function.Consumer; 026import java.util.function.Supplier; 027import org.photonvision.common.configuration.ConfigManager; 028import org.photonvision.common.dataflow.DataChangeService; 029import org.photonvision.common.dataflow.events.OutgoingUIEvent; 030import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration; 031import org.photonvision.common.logging.LogGroup; 032import org.photonvision.common.logging.Logger; 033import org.photonvision.vision.camera.QuirkyCamera; 034import org.photonvision.vision.frame.Frame; 035import org.photonvision.vision.frame.FrameProvider; 036import org.photonvision.vision.pipe.impl.HSVPipe; 037import org.photonvision.vision.pipeline.AdvancedPipelineSettings; 038import org.photonvision.vision.pipeline.CVPipeline; 039import org.photonvision.vision.pipeline.result.CVPipelineResult; 040 041/** VisionRunner has a frame supplier, a pipeline supplier, and a result consumer */ 042@SuppressWarnings("rawtypes") 043public class VisionRunner { 044 private final Logger logger; 045 private final Thread visionProcessThread; 046 private final FrameProvider frameSupplier; 047 private final Supplier<CVPipeline> pipelineSupplier; 048 private final Consumer<CVPipelineResult> pipelineResultConsumer; 049 private final VisionModuleChangeSubscriber changeSubscriber; 050 private final List<Runnable> runnableList = new ArrayList<Runnable>(); 051 private final QuirkyCamera cameraQuirks; 052 053 private long loopCount; 054 055 /** 056 * VisionRunner contains a thread to run a pipeline, given a frame, and will give the result to 057 * the consumer. 058 * 059 * @param frameSupplier The supplier of the latest frame. 060 * @param pipelineSupplier The supplier of the current pipeline. 061 * @param pipelineResultConsumer The consumer of the latest result. 062 */ 063 public VisionRunner( 064 FrameProvider frameSupplier, 065 Supplier<CVPipeline> pipelineSupplier, 066 Consumer<CVPipelineResult> pipelineResultConsumer, 067 QuirkyCamera cameraQuirks, 068 VisionModuleChangeSubscriber changeSubscriber) { 069 this.frameSupplier = frameSupplier; 070 this.pipelineSupplier = pipelineSupplier; 071 this.pipelineResultConsumer = pipelineResultConsumer; 072 this.cameraQuirks = cameraQuirks; 073 this.changeSubscriber = changeSubscriber; 074 075 visionProcessThread = new Thread(this::update); 076 visionProcessThread.setName("VisionRunner - " + frameSupplier.getName()); 077 logger = new Logger(VisionRunner.class, frameSupplier.getName(), LogGroup.VisionModule); 078 changeSubscriber.processSettingChanges(); 079 } 080 081 public void startProcess() { 082 visionProcessThread.start(); 083 } 084 085 public void stopProcess() { 086 try { 087 System.out.println("Interrupting vision process thread"); 088 visionProcessThread.interrupt(); 089 visionProcessThread.join(); 090 } catch (InterruptedException e) { 091 logger.error("Exception killing process thread", e); 092 } 093 } 094 095 public Future<Void> runSynchronously(Runnable runnable) { 096 CompletableFuture<Void> future = new CompletableFuture<>(); 097 098 synchronized (runnableList) { 099 runnableList.add( 100 () -> { 101 try { 102 runnable.run(); 103 future.complete(null); 104 } catch (Exception ex) { 105 future.completeExceptionally(ex); 106 } 107 }); 108 } 109 return future; 110 } 111 112 public <T> Future<T> runSynchronously(Callable<T> callable) { 113 CompletableFuture<T> future = new CompletableFuture<>(); 114 115 synchronized (runnableList) { 116 runnableList.add( 117 () -> { 118 try { 119 T result = callable.call(); 120 future.complete(result); 121 } catch (Exception ex) { 122 future.completeExceptionally(ex); 123 } 124 }); 125 } 126 127 return future; 128 } 129 130 private void update() { 131 // wait for the camera to connect 132 while (!frameSupplier.checkCameraConnected() && !Thread.interrupted()) { 133 // yield 134 pipelineResultConsumer.accept(new CVPipelineResult(0l, 0, 0, null, new Frame())); 135 try { 136 Thread.sleep(100); 137 } catch (InterruptedException e) { 138 return; 139 } 140 } 141 142 DataChangeService.getInstance() 143 .publishEvent( 144 new OutgoingUIEvent<>( 145 "fullsettings", 146 UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig()))); 147 148 while (!Thread.interrupted()) { 149 changeSubscriber.processSettingChanges(); 150 synchronized (runnableList) { 151 for (var runnable : runnableList) { 152 try { 153 runnable.run(); 154 } catch (Exception ex) { 155 logger.error("Exception running runnable", ex); 156 } 157 } 158 runnableList.clear(); 159 } 160 161 var pipeline = pipelineSupplier.get(); 162 163 // Tell our camera implementation here what kind of pre-processing we need it to 164 // be doing 165 // (pipeline-dependent). I kinda hate how much leak this has... 166 // TODO would a callback object be a better fit? 167 var wantedProcessType = pipeline.getThresholdType(); 168 169 frameSupplier.requestFrameThresholdType(wantedProcessType); 170 var settings = pipeline.getSettings(); 171 if (settings instanceof AdvancedPipelineSettings advanced) { 172 var hsvParams = 173 new HSVPipe.HSVParams( 174 advanced.hsvHue, advanced.hsvSaturation, advanced.hsvValue, advanced.hueInverted); 175 // TODO who should deal with preventing this from happening _every single loop_? 176 frameSupplier.requestHsvSettings(hsvParams); 177 } 178 frameSupplier.requestFrameRotation(settings.inputImageRotationMode); 179 frameSupplier.requestFrameCopies(settings.inputShouldShow, settings.outputShouldShow); 180 181 // Grab the new camera frame 182 var frame = frameSupplier.get(); 183 184 // Frame empty -- no point in trying to do anything more? 185 if (frame.processedImage.getMat().empty() && frame.colorImage.getMat().empty()) { 186 // give up without increasing loop count 187 // Still feed with blank frames just dont run any pipelines 188 189 pipelineResultConsumer.accept(new CVPipelineResult(0l, 0, 0, null, new Frame())); 190 continue; 191 } 192 193 // If the pipeline has changed while we are getting our frame we should scrap 194 // that frame it 195 // may result in incorrect frame settings like hsv values 196 if (pipeline == pipelineSupplier.get()) { 197 // There's no guarantee the processing type change will occur this tick, so 198 // pipelines should 199 // check themselves 200 try { 201 var pipelineResult = pipeline.run(frame, cameraQuirks); 202 pipelineResultConsumer.accept(pipelineResult); 203 } catch (Exception ex) { 204 logger.error("Exception on loop " + loopCount, ex); 205 } 206 } 207 208 loopCount++; 209 } 210 } 211}