PhotonVision C++ v2027.0.0-alpha-2
Loading...
Searching...
No Matches
VisionSystemSim.h
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) PhotonVision
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25#pragma once
26
27#include <string>
28#include <unordered_map>
29#include <utility>
30#include <vector>
31
32#include <wpi/math/interpolation/TimeInterpolatableBuffer.hpp>
33#include <wpi/smartdashboard/Field2d.hpp>
34#include <wpi/smartdashboard/FieldObject2d.hpp>
35#include <wpi/smartdashboard/SmartDashboard.hpp>
36#include <wpi/system/Timer.hpp>
37
39
40namespace photon {
41/**
42 * A simulated vision system involving a camera(s) and coprocessor(s) mounted on
43 * a mobile robot running PhotonVision, detecting targets placed on the field.
44 * VisionTargetSims added to this class will be detected by the PhotonCameraSim
45 * added to this class. This class should be updated periodically with the
46 * robot's current pose in order to publish the simulated camera target info.
47 */
49 public:
50 /**
51 * A simulated vision system involving a camera(s) and coprocessor(s) mounted
52 * on a mobile robot running PhotonVision, detecting targets placed on the
53 * field. VisionTargetSims added to this class will be detected by the
54 * PhotonCameraSims added to this class. This class should be updated
55 * periodically with the robot's current pose in order to publish the
56 * simulated camera target info.
57 *
58 * @param visionSystemName The specific identifier for this vision system in
59 * NetworkTables.
60 */
61 explicit VisionSystemSim(std::string visionSystemName) {
62 std::string tableName = "VisionSystemSim-" + visionSystemName;
63 wpi::SmartDashboard::PutData(tableName + "/Sim Field", &dbgField);
64 }
65
66 /** Get one of the simulated cameras. */
67 std::optional<PhotonCameraSim*> GetCameraSim(std::string name) {
68 auto it = camSimMap.find(name);
69 if (it != camSimMap.end()) {
70 return std::make_optional(it->second);
71 } else {
72 return std::nullopt;
73 }
74 }
75
76 /** Get all the simulated cameras. */
77 std::vector<PhotonCameraSim*> GetCameraSims() {
78 std::vector<PhotonCameraSim*> retVal;
79 for (auto const& cam : camSimMap) {
80 retVal.emplace_back(cam.second);
81 }
82 return retVal;
83 }
84
85 /**
86 * Adds a simulated camera to this vision system with a specified
87 * robot-to-camera transformation. The vision targets registered with this
88 * vision system simulation will be observed by the simulated PhotonCamera.
89 *
90 * @param cameraSim The camera simulation
91 * @param robotToCamera The transform from the robot pose to the camera pose
92 */
93 void AddCamera(PhotonCameraSim* cameraSim,
94 const wpi::math::Transform3d& robotToCamera) {
95 auto found =
96 camSimMap.find(std::string{cameraSim->GetCamera()->GetCameraName()});
97 if (found == camSimMap.end()) {
98 camSimMap[std::string{cameraSim->GetCamera()->GetCameraName()}] =
99 cameraSim;
100 camTrfMap.insert(
101 std::make_pair(std::move(cameraSim),
102 wpi::math::TimeInterpolatableBuffer<wpi::math::Pose3d>{
103 bufferLength}));
104 camTrfMap.at(cameraSim).AddSample(wpi::Timer::GetMonotonicTimestamp(),
105 wpi::math::Pose3d{} + robotToCamera);
106 }
107 }
108
109 /** Remove all simulated cameras from this vision system. */
111 camSimMap.clear();
112 camTrfMap.clear();
113 }
114
115 /**
116 * Remove a simulated camera from this vision system.
117 *
118 * @param cameraSim The camera to remove
119 * @return If the camera was present and removed
120 */
121 bool RemoveCamera(PhotonCameraSim* cameraSim) {
122 int numOfElementsRemoved =
123 camSimMap.erase(std::string{cameraSim->GetCamera()->GetCameraName()});
124 if (numOfElementsRemoved == 1) {
125 return true;
126 } else {
127 return false;
128 }
129 }
130
131 /**
132 * Get a simulated camera's position relative to the robot. If the requested
133 * camera is invalid, an empty optional is returned.
134 *
135 * @param cameraSim The specific camera to get the robot-to-camera transform
136 * of
137 * @return The transform of this camera, or an empty optional if it is invalid
138 */
139 std::optional<wpi::math::Transform3d> GetRobotToCamera(
140 PhotonCameraSim* cameraSim) {
141 return GetRobotToCamera(cameraSim, wpi::Timer::GetMonotonicTimestamp());
142 }
143
144 /**
145 * Get a simulated camera's position relative to the robot. If the requested
146 * camera is invalid, an empty optional is returned.
147 *
148 * @param cameraSim The specific camera to get the robot-to-camera transform
149 * of
150 * @param time Timestamp of when the transform should be observed
151 * @return The transform of this camera, or an empty optional if it is invalid
152 */
153 std::optional<wpi::math::Transform3d> GetRobotToCamera(
154 PhotonCameraSim* cameraSim, wpi::units::second_t time) {
155 if (camTrfMap.find(cameraSim) != camTrfMap.end()) {
156 wpi::math::TimeInterpolatableBuffer<wpi::math::Pose3d> trfBuffer =
157 camTrfMap.at(cameraSim);
158 std::optional<wpi::math::Pose3d> sample = trfBuffer.Sample(time);
159 if (!sample) {
160 return std::nullopt;
161 } else {
162 return std::make_optional(wpi::math::Transform3d{
163 wpi::math::Pose3d{}, sample.value_or(wpi::math::Pose3d{})});
164 }
165 } else {
166 return std::nullopt;
167 }
168 }
169
170 /**
171 * Get a simulated camera's position on the field. If the requested camera is
172 * invalid, an empty optional is returned.
173 *
174 * @param cameraSim The specific camera to get the field pose of
175 * @return The pose of this camera, or an empty optional if it is invalid
176 */
177 std::optional<wpi::math::Pose3d> GetCameraPose(PhotonCameraSim* cameraSim) {
178 return GetCameraPose(cameraSim, wpi::Timer::GetMonotonicTimestamp());
179 }
180
181 /**
182 * Get a simulated camera's position on the field. If the requested camera is
183 * invalid, an empty optional is returned.
184 *
185 * @param cameraSim The specific camera to get the field pose of
186 * @param time Timestamp of when the pose should be observed
187 * @return The pose of this camera, or an empty optional if it is invalid
188 */
189 std::optional<wpi::math::Pose3d> GetCameraPose(PhotonCameraSim* cameraSim,
190 wpi::units::second_t time) {
191 auto robotToCamera = GetRobotToCamera(cameraSim, time);
192 if (!robotToCamera) {
193 return std::nullopt;
194 } else {
195 return std::make_optional(GetRobotPose(time) + robotToCamera.value());
196 }
197 }
198
199 /**
200 * Adjust a camera's position relative to the robot. Use this if your camera
201 * is on a gimbal or turret or some other mobile platform.
202 *
203 * @param cameraSim The simulated camera to change the relative position of
204 * @param robotToCamera New transform from the robot to the camera
205 * @return If the cameraSim was valid and transform was adjusted
206 */
208 const wpi::math::Transform3d& robotToCamera) {
209 if (camTrfMap.find(cameraSim) != camTrfMap.end()) {
210 camTrfMap.at(cameraSim).AddSample(wpi::Timer::GetMonotonicTimestamp(),
211 wpi::math::Pose3d{} + robotToCamera);
212 return true;
213 } else {
214 return false;
215 }
216 }
217
218 /** Reset the previous transforms for all cameras to their current transform.
219 */
221 for (const auto& pair : camTrfMap) {
222 ResetCameraTransforms(pair.first);
223 }
224 }
225
226 /**
227 * Reset the transform history for this camera to just the current transform.
228 *
229 * @param cameraSim The camera to reset
230 * @return If the cameraSim was valid and transforms were reset
231 */
233 wpi::units::second_t now = wpi::Timer::GetMonotonicTimestamp();
234 if (camTrfMap.find(cameraSim) != camTrfMap.end()) {
235 auto trfBuffer = camTrfMap.at(cameraSim);
236 wpi::math::Transform3d lastTrf{
237 wpi::math::Pose3d{},
238 trfBuffer.Sample(now).value_or(wpi::math::Pose3d{})};
239 trfBuffer.Clear();
240 AdjustCamera(cameraSim, lastTrf);
241 return true;
242 } else {
243 return false;
244 }
245 }
246
247 /**
248 * Returns all the vision targets on the field.
249 *
250 * @return The vision targets
251 */
252 std::vector<VisionTargetSim> GetVisionTargets() {
253 std::vector<VisionTargetSim> all{};
254 for (const auto& entry : targetSets) {
255 for (const auto& target : entry.second) {
256 all.emplace_back(target);
257 }
258 }
259 return all;
260 }
261
262 /**
263 * Returns all the vision targets of the specified type on the field.
264 *
265 * @param type The type of vision targets to return
266 * @return The vision targets
267 */
268 std::vector<VisionTargetSim> GetVisionTargets(std::string type) {
269 return targetSets[type];
270 }
271
272 /**
273 * Adds targets on the field which your vision system is designed to detect.
274 * The PhotonCameras simulated from this system will report the location of
275 * the camera relative to the subset of these targets which are visible from
276 * the given camera position.
277 *
278 * By default these are added under the type "targets".
279 *
280 * @param targets Targets to add to the simulated field
281 */
282 void AddVisionTargets(const std::vector<VisionTargetSim>& targets) {
283 AddVisionTargets("targets", targets);
284 }
285
286 /**
287 * Adds targets on the field which your vision system is designed to detect.
288 * The PhotonCameras simulated from this system will report the location of
289 * the camera relative to the subset of these targets which are visible from
290 * the given camera position.
291 *
292 * @param type Type of target (e.g. "cargo").
293 * @param targets Targets to add to the simulated field
294 */
295 void AddVisionTargets(std::string type,
296 const std::vector<VisionTargetSim>& targets) {
297 if (!targetSets.contains(type)) {
298 targetSets[type] = std::vector<VisionTargetSim>{};
299 }
300 for (const auto& tgt : targets) {
301 targetSets[type].emplace_back(tgt);
302 }
303 }
304
305 /**
306 * Adds targets on the field which your vision system is designed to detect.
307 * The PhotonCameras simulated from this system will report the location of
308 * the camera relative to the subset of these targets which are visible from
309 * the given camera position.
310 *
311 * The AprilTags from this layout will be added as vision targets under the
312 * type "apriltag". The poses added preserve the tag layout's current alliance
313 * origin. If the tag layout's alliance origin is changed, these added tags
314 * will have to be cleared and re-added.
315 *
316 * @param layout The field tag layout to get Apriltag poses and IDs from
317 */
318 void AddAprilTags(const wpi::apriltag::AprilTagFieldLayout& layout) {
319 std::vector<VisionTargetSim> targets;
320 for (const wpi::apriltag::AprilTag& tag : layout.GetTags()) {
321 targets.emplace_back(VisionTargetSim{layout.GetTagPose(tag.ID).value(),
322 photon::kAprilTag36h11, tag.ID});
323 }
324 AddVisionTargets("apriltag", targets);
325 }
326 /** Removes every VisionTargetSim from the simulated field. */
327 void ClearVisionTargets() { targetSets.clear(); }
328 /** Removes all simulated AprilTag targets from the simulated field. */
329 void ClearAprilTags() { RemoveVisionTargets("apriltag"); }
330
331 /**
332 * Removes every VisionTargetSim of the specified type from the simulated
333 * field.
334 *
335 * @param type Type of target (e.g. "cargo"). Same as the type passed into
336 * #addVisionTargets(String, VisionTargetSim...)
337 * @return The removed targets, or null if no targets of the specified type
338 * exist
339 */
340 void RemoveVisionTargets(std::string type) { targetSets.erase(type); }
341
342 /**
343 * Removes the specified VisionTargetSims from the simulated field.
344 *
345 * @param targets The targets to remove
346 * @return The targets that were actually removed
347 */
348 std::vector<VisionTargetSim> RemoveVisionTargets(
349 const std::vector<VisionTargetSim>& targets) {
350 std::vector<VisionTargetSim> removedList;
351 for (auto& entry : targetSets) {
352 auto& vec = entry.second;
353 auto it = vec.begin();
354 while (it != vec.end()) {
355 if (std::find(targets.begin(), targets.end(), *it) != targets.end()) {
356 removedList.emplace_back(*it);
357 it = vec.erase(it);
358 } else {
359 ++it;
360 }
361 }
362 }
363 return removedList;
364 }
365
366 /**
367 * Get the latest robot pose in meters saved by the vision system.
368 *
369 * @return The latest robot pose
370 */
371 wpi::math::Pose3d GetRobotPose() {
372 return GetRobotPose(wpi::Timer::GetMonotonicTimestamp());
373 }
374
375 /**
376 * Get the robot pose in meters saved by the vision system at this timestamp.
377 *
378 * @param timestamp Timestamp of the desired robot pose
379 * @return The robot pose
380 */
381 wpi::math::Pose3d GetRobotPose(wpi::units::second_t timestamp) {
382 return robotPoseBuffer.Sample(timestamp).value_or(wpi::math::Pose3d{});
383 }
384
385 /**
386 * Clears all previous robot poses and sets robotPose at current time.
387 *
388 * @param robotPose The robot pose
389 */
390 void ResetRobotPose(const wpi::math::Pose2d& robotPose) {
391 ResetRobotPose(wpi::math::Pose3d{robotPose});
392 }
393
394 /**
395 * Clears all previous robot poses and sets robotPose at current time.
396 *
397 * @param robotPose The robot pose
398 */
399 void ResetRobotPose(const wpi::math::Pose3d& robotPose) {
400 robotPoseBuffer.Clear();
401 robotPoseBuffer.AddSample(wpi::Timer::GetMonotonicTimestamp(), robotPose);
402 }
403 wpi::Field2d& GetDebugField() { return dbgField; }
404
405 /**
406 * Periodic update. Ensure this is called repeatedly-- camera performance is
407 * used to automatically determine if a new frame should be submitted.
408 *
409 * @param robotPoseMeters The simulated robot pose in meters
410 */
411 void Update(const wpi::math::Pose2d& robotPose) {
412 Update(wpi::math::Pose3d{robotPose});
413 }
414
415 /**
416 * Periodic update. Ensure this is called repeatedly-- camera performance is
417 * used to automatically determine if a new frame should be submitted.
418 *
419 * @param robotPoseMeters The simulated robot pose in meters
420 */
421 void Update(const wpi::math::Pose3d& robotPose) {
422 for (auto& set : targetSets) {
423 std::vector<wpi::math::Pose2d> posesToAdd{};
424 for (auto& target : set.second) {
425 posesToAdd.emplace_back(target.GetPose().ToPose2d());
426 }
427 dbgField.GetObject(set.first)->SetPoses(posesToAdd);
428 }
429
430 wpi::units::second_t now = wpi::Timer::GetMonotonicTimestamp();
431 robotPoseBuffer.AddSample(now, robotPose);
432 dbgField.SetRobotPose(robotPose.ToPose2d());
433
434 std::vector<VisionTargetSim> allTargets{};
435 for (const auto& set : targetSets) {
436 for (const auto& target : set.second) {
437 allTargets.emplace_back(target);
438 }
439 }
440
441 std::vector<wpi::math::Pose2d> visTgtPoses2d{};
442 std::vector<wpi::math::Pose2d> cameraPoses2d{};
443 bool processed{false};
444 for (const auto& entry : camSimMap) {
445 auto camSim = entry.second;
446 auto optTimestamp = camSim->ConsumeNextEntryTime();
447 if (!optTimestamp) {
448 continue;
449 } else {
450 processed = true;
451 }
452 uint64_t timestampNt = optTimestamp.value();
453 wpi::units::second_t latency = camSim->prop.EstLatency();
454 wpi::units::second_t timestampCapture =
455 wpi::units::microsecond_t{static_cast<double>(timestampNt)} - latency;
456
457 wpi::math::Pose3d lateRobotPose = GetRobotPose(timestampCapture);
458 wpi::math::Pose3d lateCameraPose =
459 lateRobotPose + GetRobotToCamera(camSim, timestampCapture).value();
460 cameraPoses2d.push_back(lateCameraPose.ToPose2d());
461
462 auto camResult = camSim->Process(latency, lateCameraPose, allTargets);
463 camSim->SubmitProcessedFrame(camResult, timestampNt);
464 for (const auto& target : camResult.GetTargets()) {
465 auto trf = target.GetBestCameraToTarget();
466 if (trf == kEmptyTrf) {
467 continue;
468 }
469 visTgtPoses2d.push_back(lateCameraPose.TransformBy(trf).ToPose2d());
470 }
471 }
472 if (processed) {
473 dbgField.GetObject("visibleTargetPoses")->SetPoses(visTgtPoses2d);
474 }
475 if (cameraPoses2d.size() != 0) {
476 dbgField.GetObject("cameras")->SetPoses(cameraPoses2d);
477 }
478 }
479
480 private:
481 std::unordered_map<std::string, PhotonCameraSim*> camSimMap{};
482 static constexpr wpi::units::second_t bufferLength{1.5_s};
483 std::unordered_map<PhotonCameraSim*,
484 wpi::math::TimeInterpolatableBuffer<wpi::math::Pose3d>>
485 camTrfMap;
486 wpi::math::TimeInterpolatableBuffer<wpi::math::Pose3d> robotPoseBuffer{
487 bufferLength};
488 std::unordered_map<std::string, std::vector<VisionTargetSim>> targetSets{};
489 wpi::Field2d dbgField{};
490 const wpi::math::Transform3d kEmptyTrf{};
491};
492} // namespace photon
const std::string_view GetCameraName() const
Returns the name of the camera.
Definition PhotonCameraSim.h:42
PhotonCamera * GetCamera()
Returns the camera being simulated.
Definition PhotonCameraSim.h:99
A simulated vision system involving a camera(s) and coprocessor(s) mounted on a mobile robot running ...
Definition VisionSystemSim.h:48
std::optional< PhotonCameraSim * > GetCameraSim(std::string name)
Get one of the simulated cameras.
Definition VisionSystemSim.h:67
void ResetRobotPose(const wpi::math::Pose3d &robotPose)
Clears all previous robot poses and sets robotPose at current time.
Definition VisionSystemSim.h:399
wpi::Field2d & GetDebugField()
Definition VisionSystemSim.h:403
std::optional< wpi::math::Transform3d > GetRobotToCamera(PhotonCameraSim *cameraSim, wpi::units::second_t time)
Get a simulated camera's position relative to the robot.
Definition VisionSystemSim.h:153
std::vector< VisionTargetSim > RemoveVisionTargets(const std::vector< VisionTargetSim > &targets)
Removes the specified VisionTargetSims from the simulated field.
Definition VisionSystemSim.h:348
std::optional< wpi::math::Transform3d > GetRobotToCamera(PhotonCameraSim *cameraSim)
Get a simulated camera's position relative to the robot.
Definition VisionSystemSim.h:139
void AddCamera(PhotonCameraSim *cameraSim, const wpi::math::Transform3d &robotToCamera)
Adds a simulated camera to this vision system with a specified robot-to-camera transformation.
Definition VisionSystemSim.h:93
void AddAprilTags(const wpi::apriltag::AprilTagFieldLayout &layout)
Adds targets on the field which your vision system is designed to detect.
Definition VisionSystemSim.h:318
void AddVisionTargets(std::string type, const std::vector< VisionTargetSim > &targets)
Adds targets on the field which your vision system is designed to detect.
Definition VisionSystemSim.h:295
void ResetCameraTransforms()
Reset the previous transforms for all cameras to their current transform.
Definition VisionSystemSim.h:220
wpi::math::Pose3d GetRobotPose(wpi::units::second_t timestamp)
Get the robot pose in meters saved by the vision system at this timestamp.
Definition VisionSystemSim.h:381
void RemoveVisionTargets(std::string type)
Removes every VisionTargetSim of the specified type from the simulated field.
Definition VisionSystemSim.h:340
bool ResetCameraTransforms(PhotonCameraSim *cameraSim)
Reset the transform history for this camera to just the current transform.
Definition VisionSystemSim.h:232
std::vector< PhotonCameraSim * > GetCameraSims()
Get all the simulated cameras.
Definition VisionSystemSim.h:77
VisionSystemSim(std::string visionSystemName)
A simulated vision system involving a camera(s) and coprocessor(s) mounted on a mobile robot running ...
Definition VisionSystemSim.h:61
std::optional< wpi::math::Pose3d > GetCameraPose(PhotonCameraSim *cameraSim)
Get a simulated camera's position on the field.
Definition VisionSystemSim.h:177
void ClearVisionTargets()
Removes every VisionTargetSim from the simulated field.
Definition VisionSystemSim.h:327
std::vector< VisionTargetSim > GetVisionTargets()
Returns all the vision targets on the field.
Definition VisionSystemSim.h:252
void ResetRobotPose(const wpi::math::Pose2d &robotPose)
Clears all previous robot poses and sets robotPose at current time.
Definition VisionSystemSim.h:390
void AddVisionTargets(const std::vector< VisionTargetSim > &targets)
Adds targets on the field which your vision system is designed to detect.
Definition VisionSystemSim.h:282
std::vector< VisionTargetSim > GetVisionTargets(std::string type)
Returns all the vision targets of the specified type on the field.
Definition VisionSystemSim.h:268
std::optional< wpi::math::Pose3d > GetCameraPose(PhotonCameraSim *cameraSim, wpi::units::second_t time)
Get a simulated camera's position on the field.
Definition VisionSystemSim.h:189
void Update(const wpi::math::Pose2d &robotPose)
Periodic update.
Definition VisionSystemSim.h:411
wpi::math::Pose3d GetRobotPose()
Get the latest robot pose in meters saved by the vision system.
Definition VisionSystemSim.h:371
bool AdjustCamera(PhotonCameraSim *cameraSim, const wpi::math::Transform3d &robotToCamera)
Adjust a camera's position relative to the robot.
Definition VisionSystemSim.h:207
void Update(const wpi::math::Pose3d &robotPose)
Periodic update.
Definition VisionSystemSim.h:421
void ClearAprilTags()
Removes all simulated AprilTag targets from the simulated field.
Definition VisionSystemSim.h:329
void ClearCameras()
Remove all simulated cameras from this vision system.
Definition VisionSystemSim.h:110
bool RemoveCamera(PhotonCameraSim *cameraSim)
Remove a simulated camera from this vision system.
Definition VisionSystemSim.h:121
Describes a vision target located somewhere on the field that your vision system can detect.
Definition VisionTargetSim.h:36
Definition VisionEstimation.h:30
static const TargetModel kAprilTag36h11
Definition TargetModel.h:117