mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-05 03:21:40 +00:00
Auto-generate packet dataclasses with Jinja (#1374)
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -48,6 +48,8 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Fetch tags
|
- name: Fetch tags
|
||||||
run: git fetch --tags --force
|
run: git fetch --tags --force
|
||||||
|
- name: Install RoboRIO Toolchain
|
||||||
|
run: ./gradlew installRoboRioToolchain
|
||||||
- name: Install Java 17
|
- name: Install Java 17
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -165,3 +165,6 @@ photon-server/src/main/resources/web/index.html
|
|||||||
photon-lib/src/generate/native/cpp/PhotonVersion.cpp
|
photon-lib/src/generate/native/cpp/PhotonVersion.cpp
|
||||||
|
|
||||||
venv
|
venv
|
||||||
|
|
||||||
|
.venv/*
|
||||||
|
.venv
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ spotless {
|
|||||||
java {
|
java {
|
||||||
target fileTree('.') {
|
target fileTree('.') {
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
exclude '**/build/**', '**/build-*/**', "photon-core\\src\\main\\java\\org\\photonvision\\PhotonVersion.java", "photon-lib\\src\\main\\java\\org\\photonvision\\PhotonVersion.java"
|
exclude '**/build/**', '**/build-*/**', "photon-core\\src\\main\\java\\org\\photonvision\\PhotonVersion.java", "photon-lib\\src\\main\\java\\org\\photonvision\\PhotonVersion.java", "**/src/generated/**"
|
||||||
}
|
}
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
googleJavaFormat()
|
googleJavaFormat()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import base64
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import Union
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import mrcal
|
import mrcal
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ public class NTDataPublisher implements CVPipelineResultConsumer {
|
|||||||
TrackedTarget.simpleFromTrackedTargets(result.targets),
|
TrackedTarget.simpleFromTrackedTargets(result.targets),
|
||||||
result.multiTagResult);
|
result.multiTagResult);
|
||||||
|
|
||||||
ts.resultPublisher.set(simplified, simplified.getPacketSize());
|
// random guess at size of the array
|
||||||
|
ts.resultPublisher.set(simplified, 1024);
|
||||||
if (ConfigManager.getInstance().getConfig().getNetworkConfig().shouldPublishProto) {
|
if (ConfigManager.getInstance().getConfig().getNetworkConfig().shouldPublishProto) {
|
||||||
ts.protoResultPublisher.set(simplified);
|
ts.protoResultPublisher.set(simplified);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,13 +62,14 @@ public class UIDataPublisher implements CVPipelineResultConsumer {
|
|||||||
dataMap.put("classNames", result.objectDetectionClassNames);
|
dataMap.put("classNames", result.objectDetectionClassNames);
|
||||||
|
|
||||||
// Only send Multitag Results if they are present, similar to 3d pose
|
// Only send Multitag Results if they are present, similar to 3d pose
|
||||||
if (result.multiTagResult.estimatedPose.isPresent) {
|
if (result.multiTagResult.isPresent()) {
|
||||||
var multitagData = new HashMap<String, Object>();
|
var multitagData = new HashMap<String, Object>();
|
||||||
multitagData.put(
|
multitagData.put(
|
||||||
"bestTransform",
|
"bestTransform",
|
||||||
SerializationUtils.transformToHashMap(result.multiTagResult.estimatedPose.best));
|
SerializationUtils.transformToHashMap(result.multiTagResult.get().estimatedPose.best));
|
||||||
multitagData.put("bestReprojectionError", result.multiTagResult.estimatedPose.bestReprojErr);
|
multitagData.put(
|
||||||
multitagData.put("fiducialIDsUsed", result.multiTagResult.fiducialIDsUsed);
|
"bestReprojectionError", result.multiTagResult.get().estimatedPose.bestReprojErr);
|
||||||
|
multitagData.put("fiducialIDsUsed", result.multiTagResult.get().fiducialIDsUsed);
|
||||||
dataMap.put("multitagResult", multitagData);
|
dataMap.put("multitagResult", multitagData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.photonvision.vision.pipe.impl;
|
|||||||
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
import edu.wpi.first.apriltag.AprilTagFieldLayout;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.estimation.TargetModel;
|
import org.photonvision.estimation.TargetModel;
|
||||||
@@ -32,13 +33,15 @@ import org.photonvision.vision.target.TrackedTarget;
|
|||||||
/** Estimate the camera pose given multiple Apriltag observations */
|
/** Estimate the camera pose given multiple Apriltag observations */
|
||||||
public class MultiTargetPNPPipe
|
public class MultiTargetPNPPipe
|
||||||
extends CVPipe<
|
extends CVPipe<
|
||||||
List<TrackedTarget>, MultiTargetPNPResult, MultiTargetPNPPipe.MultiTargetPNPPipeParams> {
|
List<TrackedTarget>,
|
||||||
|
Optional<MultiTargetPNPResult>,
|
||||||
|
MultiTargetPNPPipe.MultiTargetPNPPipeParams> {
|
||||||
private static final Logger logger = new Logger(MultiTargetPNPPipe.class, LogGroup.VisionModule);
|
private static final Logger logger = new Logger(MultiTargetPNPPipe.class, LogGroup.VisionModule);
|
||||||
|
|
||||||
private boolean hasWarned = false;
|
private boolean hasWarned = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MultiTargetPNPResult process(List<TrackedTarget> targetList) {
|
protected Optional<MultiTargetPNPResult> process(List<TrackedTarget> targetList) {
|
||||||
if (params == null
|
if (params == null
|
||||||
|| params.cameraCoefficients == null
|
|| params.cameraCoefficients == null
|
||||||
|| params.cameraCoefficients.getCameraIntrinsicsMat() == null
|
|| params.cameraCoefficients.getCameraIntrinsicsMat() == null
|
||||||
@@ -48,23 +51,23 @@ public class MultiTargetPNPPipe
|
|||||||
"Cannot perform solvePNP an uncalibrated camera! Please calibrate this resolution...");
|
"Cannot perform solvePNP an uncalibrated camera! Please calibrate this resolution...");
|
||||||
hasWarned = true;
|
hasWarned = true;
|
||||||
}
|
}
|
||||||
return new MultiTargetPNPResult();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculateCameraInField(targetList);
|
return calculateCameraInField(targetList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiTargetPNPResult calculateCameraInField(List<TrackedTarget> targetList) {
|
private Optional<MultiTargetPNPResult> calculateCameraInField(List<TrackedTarget> targetList) {
|
||||||
// Find tag IDs that exist in the tag layout
|
// Find tag IDs that exist in the tag layout
|
||||||
var tagIDsUsed = new ArrayList<Integer>();
|
var tagIDsUsed = new ArrayList<Short>();
|
||||||
for (var target : targetList) {
|
for (var target : targetList) {
|
||||||
int id = target.getFiducialId();
|
int id = target.getFiducialId();
|
||||||
if (params.atfl.getTagPose(id).isPresent()) tagIDsUsed.add(id);
|
if (params.atfl.getTagPose(id).isPresent()) tagIDsUsed.add((short) id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only run with multiple targets
|
// Only run with multiple targets
|
||||||
if (tagIDsUsed.size() < 2) {
|
if (tagIDsUsed.size() < 2) {
|
||||||
return new MultiTargetPNPResult();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
var estimatedPose =
|
var estimatedPose =
|
||||||
@@ -75,7 +78,11 @@ public class MultiTargetPNPPipe
|
|||||||
params.atfl,
|
params.atfl,
|
||||||
params.targetModel);
|
params.targetModel);
|
||||||
|
|
||||||
return new MultiTargetPNPResult(estimatedPose, tagIDsUsed);
|
if (estimatedPose.isPresent()) {
|
||||||
|
return Optional.of(new MultiTargetPNPResult(estimatedPose.get(), tagIDsUsed));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MultiTargetPNPPipeParams {
|
public static class MultiTargetPNPPipeParams {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import edu.wpi.first.math.geometry.Transform3d;
|
|||||||
import edu.wpi.first.math.util.Units;
|
import edu.wpi.first.math.util.Units;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
import org.photonvision.common.util.math.MathUtils;
|
import org.photonvision.common.util.math.MathUtils;
|
||||||
import org.photonvision.estimation.TargetModel;
|
import org.photonvision.estimation.TargetModel;
|
||||||
@@ -149,7 +150,7 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do multi-tag pose estimation
|
// Do multi-tag pose estimation
|
||||||
MultiTargetPNPResult multiTagResult = new MultiTargetPNPResult();
|
Optional<MultiTargetPNPResult> multiTagResult = Optional.empty();
|
||||||
if (settings.solvePNPEnabled && settings.doMultiTarget) {
|
if (settings.solvePNPEnabled && settings.doMultiTarget) {
|
||||||
var multiTagOutput = multiTagPNPPipe.run(targetList);
|
var multiTagOutput = multiTagPNPPipe.run(targetList);
|
||||||
sumPipeNanosElapsed += multiTagOutput.nanosElapsed;
|
sumPipeNanosElapsed += multiTagOutput.nanosElapsed;
|
||||||
@@ -167,20 +168,21 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
|
|||||||
AprilTagPoseEstimate tagPoseEstimate = null;
|
AprilTagPoseEstimate tagPoseEstimate = null;
|
||||||
// Do single-tag estimation when "always enabled" or if a tag was not used for multitag
|
// Do single-tag estimation when "always enabled" or if a tag was not used for multitag
|
||||||
if (settings.doSingleTargetAlways
|
if (settings.doSingleTargetAlways
|
||||||
|| !multiTagResult.fiducialIDsUsed.contains(Integer.valueOf(detection.getId()))) {
|
|| !(multiTagResult.isPresent()
|
||||||
|
&& multiTagResult.get().fiducialIDsUsed.contains((short) detection.getId()))) {
|
||||||
var poseResult = singleTagPoseEstimatorPipe.run(detection);
|
var poseResult = singleTagPoseEstimatorPipe.run(detection);
|
||||||
sumPipeNanosElapsed += poseResult.nanosElapsed;
|
sumPipeNanosElapsed += poseResult.nanosElapsed;
|
||||||
tagPoseEstimate = poseResult.output;
|
tagPoseEstimate = poseResult.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If single-tag estimation was not done, this is a multi-target tag from the layout
|
// If single-tag estimation was not done, this is a multi-target tag from the layout
|
||||||
if (tagPoseEstimate == null) {
|
if (tagPoseEstimate == null && multiTagResult.isPresent()) {
|
||||||
// compute this tag's camera-to-tag transform using the multitag result
|
// compute this tag's camera-to-tag transform using the multitag result
|
||||||
var tagPose = atfl.getTagPose(detection.getId());
|
var tagPose = atfl.getTagPose(detection.getId());
|
||||||
if (tagPose.isPresent()) {
|
if (tagPose.isPresent()) {
|
||||||
var camToTag =
|
var camToTag =
|
||||||
new Transform3d(
|
new Transform3d(
|
||||||
new Pose3d().plus(multiTagResult.estimatedPose.best), tagPose.get());
|
new Pose3d().plus(multiTagResult.get().estimatedPose.best), tagPose.get());
|
||||||
// match expected AprilTag coordinate system
|
// match expected AprilTag coordinate system
|
||||||
camToTag =
|
camToTag =
|
||||||
CoordinateSystem.convert(camToTag, CoordinateSystem.NWU(), CoordinateSystem.EDN());
|
CoordinateSystem.convert(camToTag, CoordinateSystem.NWU(), CoordinateSystem.EDN());
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import edu.wpi.first.math.geometry.Transform3d;
|
|||||||
import edu.wpi.first.math.util.Units;
|
import edu.wpi.first.math.util.Units;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.opencv.core.Mat;
|
import org.opencv.core.Mat;
|
||||||
import org.opencv.imgproc.Imgproc;
|
import org.opencv.imgproc.Imgproc;
|
||||||
import org.opencv.objdetect.Objdetect;
|
import org.opencv.objdetect.Objdetect;
|
||||||
@@ -170,7 +171,7 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do multi-tag pose estimation
|
// Do multi-tag pose estimation
|
||||||
MultiTargetPNPResult multiTagResult = new MultiTargetPNPResult();
|
Optional<MultiTargetPNPResult> multiTagResult = Optional.empty();
|
||||||
if (settings.solvePNPEnabled && settings.doMultiTarget) {
|
if (settings.solvePNPEnabled && settings.doMultiTarget) {
|
||||||
var multiTagOutput = multiTagPNPPipe.run(targetList);
|
var multiTagOutput = multiTagPNPPipe.run(targetList);
|
||||||
sumPipeNanosElapsed += multiTagOutput.nanosElapsed;
|
sumPipeNanosElapsed += multiTagOutput.nanosElapsed;
|
||||||
@@ -188,20 +189,21 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
|
|||||||
AprilTagPoseEstimate tagPoseEstimate = null;
|
AprilTagPoseEstimate tagPoseEstimate = null;
|
||||||
// Do single-tag estimation when "always enabled" or if a tag was not used for multitag
|
// Do single-tag estimation when "always enabled" or if a tag was not used for multitag
|
||||||
if (settings.doSingleTargetAlways
|
if (settings.doSingleTargetAlways
|
||||||
|| !multiTagResult.fiducialIDsUsed.contains(detection.getId())) {
|
|| !(multiTagResult.isPresent()
|
||||||
|
&& multiTagResult.get().fiducialIDsUsed.contains((short) detection.getId()))) {
|
||||||
var poseResult = singleTagPoseEstimatorPipe.run(detection);
|
var poseResult = singleTagPoseEstimatorPipe.run(detection);
|
||||||
sumPipeNanosElapsed += poseResult.nanosElapsed;
|
sumPipeNanosElapsed += poseResult.nanosElapsed;
|
||||||
tagPoseEstimate = poseResult.output;
|
tagPoseEstimate = poseResult.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If single-tag estimation was not done, this is a multi-target tag from the layout
|
// If single-tag estimation was not done, this is a multi-target tag from the layout
|
||||||
if (tagPoseEstimate == null) {
|
if (tagPoseEstimate == null && multiTagResult.isPresent()) {
|
||||||
// compute this tag's camera-to-tag transform using the multitag result
|
// compute this tag's camera-to-tag transform using the multitag result
|
||||||
var tagPose = atfl.getTagPose(detection.getId());
|
var tagPose = atfl.getTagPose(detection.getId());
|
||||||
if (tagPose.isPresent()) {
|
if (tagPose.isPresent()) {
|
||||||
var camToTag =
|
var camToTag =
|
||||||
new Transform3d(
|
new Transform3d(
|
||||||
new Pose3d().plus(multiTagResult.estimatedPose.best), tagPose.get());
|
new Pose3d().plus(multiTagResult.get().estimatedPose.best), tagPose.get());
|
||||||
// match expected OpenCV coordinate system
|
// match expected OpenCV coordinate system
|
||||||
camToTag =
|
camToTag =
|
||||||
CoordinateSystem.convert(camToTag, CoordinateSystem.NWU(), CoordinateSystem.EDN());
|
CoordinateSystem.convert(camToTag, CoordinateSystem.NWU(), CoordinateSystem.EDN());
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.pipeline.result;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.photonvision.common.util.math.MathUtils;
|
import org.photonvision.common.util.math.MathUtils;
|
||||||
import org.photonvision.targeting.MultiTargetPNPResult;
|
import org.photonvision.targeting.MultiTargetPNPResult;
|
||||||
import org.photonvision.vision.frame.Frame;
|
import org.photonvision.vision.frame.Frame;
|
||||||
@@ -32,7 +33,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
public final double fps;
|
public final double fps;
|
||||||
public final List<TrackedTarget> targets;
|
public final List<TrackedTarget> targets;
|
||||||
public final Frame inputAndOutputFrame;
|
public final Frame inputAndOutputFrame;
|
||||||
public MultiTargetPNPResult multiTagResult;
|
public Optional<MultiTargetPNPResult> multiTagResult;
|
||||||
public final List<String> objectDetectionClassNames;
|
public final List<String> objectDetectionClassNames;
|
||||||
|
|
||||||
public CVPipelineResult(
|
public CVPipelineResult(
|
||||||
@@ -41,14 +42,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
double fps,
|
double fps,
|
||||||
List<TrackedTarget> targets,
|
List<TrackedTarget> targets,
|
||||||
Frame inputFrame) {
|
Frame inputFrame) {
|
||||||
this(
|
this(sequenceID, processingNanos, fps, targets, Optional.empty(), inputFrame, List.of());
|
||||||
sequenceID,
|
|
||||||
processingNanos,
|
|
||||||
fps,
|
|
||||||
targets,
|
|
||||||
new MultiTargetPNPResult(),
|
|
||||||
inputFrame,
|
|
||||||
List.of());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CVPipelineResult(
|
public CVPipelineResult(
|
||||||
@@ -58,14 +52,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
List<TrackedTarget> targets,
|
List<TrackedTarget> targets,
|
||||||
Frame inputFrame,
|
Frame inputFrame,
|
||||||
List<String> classNames) {
|
List<String> classNames) {
|
||||||
this(
|
this(sequenceID, processingNanos, fps, targets, Optional.empty(), inputFrame, classNames);
|
||||||
sequenceID,
|
|
||||||
processingNanos,
|
|
||||||
fps,
|
|
||||||
targets,
|
|
||||||
new MultiTargetPNPResult(),
|
|
||||||
inputFrame,
|
|
||||||
classNames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CVPipelineResult(
|
public CVPipelineResult(
|
||||||
@@ -73,7 +60,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
double processingNanos,
|
double processingNanos,
|
||||||
double fps,
|
double fps,
|
||||||
List<TrackedTarget> targets,
|
List<TrackedTarget> targets,
|
||||||
MultiTargetPNPResult multiTagResult,
|
Optional<MultiTargetPNPResult> multiTagResult,
|
||||||
Frame inputFrame) {
|
Frame inputFrame) {
|
||||||
this(sequenceID, processingNanos, fps, targets, multiTagResult, inputFrame, List.of());
|
this(sequenceID, processingNanos, fps, targets, multiTagResult, inputFrame, List.of());
|
||||||
}
|
}
|
||||||
@@ -83,7 +70,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
double processingNanos,
|
double processingNanos,
|
||||||
double fps,
|
double fps,
|
||||||
List<TrackedTarget> targets,
|
List<TrackedTarget> targets,
|
||||||
MultiTargetPNPResult multiTagResult,
|
Optional<MultiTargetPNPResult> multiTagResult,
|
||||||
Frame inputFrame,
|
Frame inputFrame,
|
||||||
List<String> classNames) {
|
List<String> classNames) {
|
||||||
this.sequenceID = sequenceID;
|
this.sequenceID = sequenceID;
|
||||||
@@ -101,7 +88,7 @@ public class CVPipelineResult implements Releasable {
|
|||||||
double processingNanos,
|
double processingNanos,
|
||||||
double fps,
|
double fps,
|
||||||
List<TrackedTarget> targets,
|
List<TrackedTarget> targets,
|
||||||
MultiTargetPNPResult multiTagResult) {
|
Optional<MultiTargetPNPResult> multiTagResult) {
|
||||||
this(sequenceID, processingNanos, fps, targets, multiTagResult, null, List.of());
|
this(sequenceID, processingNanos, fps, targets, multiTagResult, null, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ doxygen {
|
|||||||
html_timestamp true
|
html_timestamp true
|
||||||
javadoc_autobrief true
|
javadoc_autobrief true
|
||||||
project_name 'PhotonVision C++'
|
project_name 'PhotonVision C++'
|
||||||
project_logo '../wpiutil/src/main/native/resources/wpilib-128.png'
|
project_logo '../photon-client/src/assets/images/logoSmall.svg'
|
||||||
project_number pubVersion
|
project_number pubVersion
|
||||||
quiet true
|
quiet true
|
||||||
recursive true
|
recursive true
|
||||||
|
|||||||
@@ -18,7 +18,25 @@ apply from: "${rootDir}/versioningHelper.gradle"
|
|||||||
|
|
||||||
nativeUtils {
|
nativeUtils {
|
||||||
exportsConfigs {
|
exportsConfigs {
|
||||||
"${nativeName}" {}
|
"${nativeName}" {
|
||||||
|
// From https://github.com/wpilibsuite/allwpilib/blob/a32589831184969939fd3d63f449a2788a0a8542/wpimath/build.gradle#L72
|
||||||
|
// Copyright (c) FIRST and other WPILib contributors.
|
||||||
|
// Open Source Software; you can modify and/or share it under the terms of
|
||||||
|
// the WPILib BSD license file in the root directory of this project.
|
||||||
|
x64ExcludeSymbols = [
|
||||||
|
'_CT??_R0?AV_System_error',
|
||||||
|
'_CT??_R0?AVexception',
|
||||||
|
'_CT??_R0?AVfailure',
|
||||||
|
'_CT??_R0?AVruntime_error',
|
||||||
|
'_CT??_R0?AVsystem_error',
|
||||||
|
'_CTA5?AVfailure',
|
||||||
|
'_TI5?AVfailure',
|
||||||
|
'_CT??_R0?AVout_of_range',
|
||||||
|
'_CTA3?AVout_of_range',
|
||||||
|
'_TI3?AVout_of_range',
|
||||||
|
'_CT??_R0?AVbad_cast'
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
photon-lib/py/buildAndTest.sh
Executable file
14
photon-lib/py/buildAndTest.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
# Uninstall if it already was installed
|
||||||
|
python3 -m pip uninstall -y photonlibpy
|
||||||
|
|
||||||
|
# Build wheel
|
||||||
|
python3 setup.py bdist_wheel
|
||||||
|
|
||||||
|
# Install whatever wheel was made
|
||||||
|
for f in dist/*.whl; do
|
||||||
|
echo "installing $f"
|
||||||
|
python3 -m pip install --no-cache-dir "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run the test suite
|
||||||
|
pytest -rP --full-trace
|
||||||
@@ -1 +1,6 @@
|
|||||||
# No one here but us chickens
|
# No one here but us chickens
|
||||||
|
|
||||||
|
from .packet import Packet # noqa
|
||||||
|
from .estimatedRobotPose import EstimatedRobotPose # noqa
|
||||||
|
from .photonPoseEstimator import PhotonPoseEstimator, PoseStrategy # noqa
|
||||||
|
from .photonCamera import PhotonCamera # noqa
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from wpimath.geometry import Pose3d
|
from wpimath.geometry import Pose3d
|
||||||
|
|
||||||
from .photonTrackedTarget import PhotonTrackedTarget
|
from .targeting.photonTrackedTarget import PhotonTrackedTarget
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .photonPoseEstimator import PoseStrategy
|
from .photonPoseEstimator import PoseStrategy
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class MultiTargetPNPResultSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "ffc1cb847deb6e796a583a5b1885496b"
|
||||||
|
MESSAGE_FORMAT = "PnpResult estimatedPose;int16[?] fiducialIDsUsed;"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "MultiTargetPNPResult":
|
||||||
|
ret = MultiTargetPNPResult()
|
||||||
|
|
||||||
|
# estimatedPose is of non-intrinsic type PnpResult
|
||||||
|
ret.estimatedPose = PnpResult.photonStruct.unpack(packet)
|
||||||
|
|
||||||
|
# fiducialIDsUsed is a custom VLA!
|
||||||
|
ret.fiducialIDsUsed = packet.decodeShortList()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
MultiTargetPNPResult.photonStruct = MultiTargetPNPResultSerde()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class PhotonPipelineMetadataSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "2a7039527bda14d13028a1b9282d40a2"
|
||||||
|
MESSAGE_FORMAT = (
|
||||||
|
"int64 sequenceID;int64 captureTimestampMicros;int64 publishTimestampMicros;"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "PhotonPipelineMetadata":
|
||||||
|
ret = PhotonPipelineMetadata()
|
||||||
|
|
||||||
|
# sequenceID is of intrinsic type int64
|
||||||
|
ret.sequenceID = packet.decodeLong()
|
||||||
|
|
||||||
|
# captureTimestampMicros is of intrinsic type int64
|
||||||
|
ret.captureTimestampMicros = packet.decodeLong()
|
||||||
|
|
||||||
|
# publishTimestampMicros is of intrinsic type int64
|
||||||
|
ret.publishTimestampMicros = packet.decodeLong()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
PhotonPipelineMetadata.photonStruct = PhotonPipelineMetadataSerde()
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class PhotonPipelineResultSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "cb3e1605048ba49325888eb797399fe2"
|
||||||
|
MESSAGE_FORMAT = "PhotonPipelineMetadata metadata;PhotonTrackedTarget[?] targets;MultiTargetPNPResult? multiTagResult;"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "PhotonPipelineResult":
|
||||||
|
ret = PhotonPipelineResult()
|
||||||
|
|
||||||
|
# metadata is of non-intrinsic type PhotonPipelineMetadata
|
||||||
|
ret.metadata = PhotonPipelineMetadata.photonStruct.unpack(packet)
|
||||||
|
|
||||||
|
# targets is a custom VLA!
|
||||||
|
ret.targets = packet.decodeList(PhotonTrackedTarget.photonStruct)
|
||||||
|
|
||||||
|
# multiTagResult is optional! it better not be a VLA too
|
||||||
|
ret.multiTagResult = packet.decodeOptional(MultiTargetPNPResult.photonStruct)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
PhotonPipelineResult.photonStruct = PhotonPipelineResultSerde()
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class PhotonTrackedTargetSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "8fdada56b9162f2e32bd24f0055d7b60"
|
||||||
|
MESSAGE_FORMAT = "float64 yaw;float64 pitch;float64 area;float64 skew;int32 fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d bestCameraToTarget;Transform3d altCameraToTarget;float64 poseAmbiguity;TargetCorner[?] minAreaRectCorners;TargetCorner[?] detectedCorners;"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "PhotonTrackedTarget":
|
||||||
|
ret = PhotonTrackedTarget()
|
||||||
|
|
||||||
|
# yaw is of intrinsic type float64
|
||||||
|
ret.yaw = packet.decodeDouble()
|
||||||
|
|
||||||
|
# pitch is of intrinsic type float64
|
||||||
|
ret.pitch = packet.decodeDouble()
|
||||||
|
|
||||||
|
# area is of intrinsic type float64
|
||||||
|
ret.area = packet.decodeDouble()
|
||||||
|
|
||||||
|
# skew is of intrinsic type float64
|
||||||
|
ret.skew = packet.decodeDouble()
|
||||||
|
|
||||||
|
# fiducialId is of intrinsic type int32
|
||||||
|
ret.fiducialId = packet.decodeInt()
|
||||||
|
|
||||||
|
# objDetectId is of intrinsic type int32
|
||||||
|
ret.objDetectId = packet.decodeInt()
|
||||||
|
|
||||||
|
# objDetectConf is of intrinsic type float32
|
||||||
|
ret.objDetectConf = packet.decodeFloat()
|
||||||
|
|
||||||
|
# field is shimmed!
|
||||||
|
ret.bestCameraToTarget = packet.decodeTransform()
|
||||||
|
|
||||||
|
# field is shimmed!
|
||||||
|
ret.altCameraToTarget = packet.decodeTransform()
|
||||||
|
|
||||||
|
# poseAmbiguity is of intrinsic type float64
|
||||||
|
ret.poseAmbiguity = packet.decodeDouble()
|
||||||
|
|
||||||
|
# minAreaRectCorners is a custom VLA!
|
||||||
|
ret.minAreaRectCorners = packet.decodeList(TargetCorner.photonStruct)
|
||||||
|
|
||||||
|
# detectedCorners is a custom VLA!
|
||||||
|
ret.detectedCorners = packet.decodeList(TargetCorner.photonStruct)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
PhotonTrackedTarget.photonStruct = PhotonTrackedTargetSerde()
|
||||||
55
photon-lib/py/photonlibpy/generated/PnpResultSerde.py
Normal file
55
photon-lib/py/photonlibpy/generated/PnpResultSerde.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class PnpResultSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "0d1f2546b00f24718e30f38d206d4491"
|
||||||
|
MESSAGE_FORMAT = "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 altReprojErr;float64 ambiguity;"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "PnpResult":
|
||||||
|
ret = PnpResult()
|
||||||
|
|
||||||
|
# field is shimmed!
|
||||||
|
ret.best = packet.decodeTransform()
|
||||||
|
|
||||||
|
# field is shimmed!
|
||||||
|
ret.alt = packet.decodeTransform()
|
||||||
|
|
||||||
|
# bestReprojErr is of intrinsic type float64
|
||||||
|
ret.bestReprojErr = packet.decodeDouble()
|
||||||
|
|
||||||
|
# altReprojErr is of intrinsic type float64
|
||||||
|
ret.altReprojErr = packet.decodeDouble()
|
||||||
|
|
||||||
|
# ambiguity is of intrinsic type float64
|
||||||
|
ret.ambiguity = packet.decodeDouble()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
PnpResult.photonStruct = PnpResultSerde()
|
||||||
46
photon-lib/py/photonlibpy/generated/TargetCornerSerde.py
Normal file
46
photon-lib/py/photonlibpy/generated/TargetCornerSerde.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
|
||||||
|
class TargetCornerSerde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "22b1ff7551d10215af6fb3672fe4eda8"
|
||||||
|
MESSAGE_FORMAT = "float64 x;float64 y;"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: "Packet") -> "TargetCorner":
|
||||||
|
ret = TargetCorner()
|
||||||
|
|
||||||
|
# x is of intrinsic type float64
|
||||||
|
ret.x = packet.decodeDouble()
|
||||||
|
|
||||||
|
# y is of intrinsic type float64
|
||||||
|
ret.y = packet.decodeDouble()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
TargetCorner.photonStruct = TargetCornerSerde()
|
||||||
9
photon-lib/py/photonlibpy/generated/__init__.py
Normal file
9
photon-lib/py/photonlibpy/generated/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# no one but us chickens
|
||||||
|
|
||||||
|
from .MultiTargetPNPResultSerde import MultiTargetPNPResultSerde # noqa
|
||||||
|
from .PhotonPipelineMetadataSerde import PhotonPipelineMetadataSerde # noqa
|
||||||
|
from .PhotonPipelineMetadataSerde import PhotonPipelineMetadataSerde # noqa
|
||||||
|
from .PhotonPipelineResultSerde import PhotonPipelineResultSerde # noqa
|
||||||
|
from .PhotonTrackedTargetSerde import PhotonTrackedTargetSerde # noqa
|
||||||
|
from .PnpResultSerde import PnpResultSerde # noqa
|
||||||
|
from .TargetCornerSerde import TargetCornerSerde # noqa
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from wpimath.geometry import Transform3d
|
|
||||||
from photonlibpy.packet import Packet
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PNPResult:
|
|
||||||
_NUM_BYTES_IN_FLOAT = 8
|
|
||||||
PACK_SIZE_BYTES = 1 + (_NUM_BYTES_IN_FLOAT * 7 * 2) + (_NUM_BYTES_IN_FLOAT * 3)
|
|
||||||
|
|
||||||
isPresent: bool = False
|
|
||||||
best: Transform3d = field(default_factory=Transform3d)
|
|
||||||
alt: Transform3d = field(default_factory=Transform3d)
|
|
||||||
ambiguity: float = 0.0
|
|
||||||
bestReprojError: float = 0.0
|
|
||||||
altReprojError: float = 0.0
|
|
||||||
|
|
||||||
def createFromPacket(self, packet: Packet) -> Packet:
|
|
||||||
self.isPresent = packet.decodeBoolean()
|
|
||||||
|
|
||||||
if not self.isPresent:
|
|
||||||
return packet
|
|
||||||
|
|
||||||
self.best = packet.decodeTransform()
|
|
||||||
self.alt = packet.decodeTransform()
|
|
||||||
self.bestReprojError = packet.decodeDouble()
|
|
||||||
self.altReprojError = packet.decodeDouble()
|
|
||||||
self.ambiguity = packet.decodeDouble()
|
|
||||||
return packet
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MultiTargetPNPResult:
|
|
||||||
_MAX_IDS = 32
|
|
||||||
# pnpresult + MAX_IDS possible targets (arbitrary upper limit that should never be hit, ideally)
|
|
||||||
_PACK_SIZE_BYTES = PNPResult.PACK_SIZE_BYTES + (1 * _MAX_IDS)
|
|
||||||
|
|
||||||
estimatedPose: PNPResult = field(default_factory=PNPResult)
|
|
||||||
fiducialIDsUsed: list[int] = field(default_factory=list)
|
|
||||||
|
|
||||||
def createFromPacket(self, packet: Packet) -> Packet:
|
|
||||||
self.estimatedPose = PNPResult()
|
|
||||||
self.estimatedPose.createFromPacket(packet)
|
|
||||||
self.fiducialIDsUsed = []
|
|
||||||
for _ in range(MultiTargetPNPResult._MAX_IDS):
|
|
||||||
fidId = packet.decode16()
|
|
||||||
if fidId >= 0:
|
|
||||||
self.fiducialIDsUsed.append(fidId)
|
|
||||||
return packet
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import struct
|
import struct
|
||||||
|
from typing import Any, Optional, Type
|
||||||
from wpimath.geometry import Transform3d, Translation3d, Rotation3d, Quaternion
|
from wpimath.geometry import Transform3d, Translation3d, Rotation3d, Quaternion
|
||||||
import wpilib
|
import wpilib
|
||||||
|
|
||||||
@@ -82,13 +83,13 @@ class Packet:
|
|||||||
|
|
||||||
def decode16(self) -> int:
|
def decode16(self) -> int:
|
||||||
"""
|
"""
|
||||||
* Returns a single decoded byte from the packet.
|
* Returns a single decoded short from the packet.
|
||||||
*
|
*
|
||||||
* @return A decoded byte from the packet.
|
* @return A decoded short from the packet.
|
||||||
"""
|
"""
|
||||||
return self._decodeGeneric(">h", 2)
|
return self._decodeGeneric(">h", 2)
|
||||||
|
|
||||||
def decode32(self) -> int:
|
def decodeInt(self) -> int:
|
||||||
"""
|
"""
|
||||||
* Returns a decoded int (32 bytes) from the packet.
|
* Returns a decoded int (32 bytes) from the packet.
|
||||||
*
|
*
|
||||||
@@ -104,7 +105,7 @@ class Packet:
|
|||||||
"""
|
"""
|
||||||
return self._decodeGeneric(">f", 4)
|
return self._decodeGeneric(">f", 4)
|
||||||
|
|
||||||
def decodei64(self) -> int:
|
def decodeLong(self) -> int:
|
||||||
"""
|
"""
|
||||||
* Returns a decoded int64 from the packet.
|
* Returns a decoded int64 from the packet.
|
||||||
*
|
*
|
||||||
@@ -131,14 +132,22 @@ class Packet:
|
|||||||
def decodeDoubleArray(self, length: int) -> list[float]:
|
def decodeDoubleArray(self, length: int) -> list[float]:
|
||||||
"""
|
"""
|
||||||
* Returns a decoded array of floats from the packet.
|
* Returns a decoded array of floats from the packet.
|
||||||
*
|
|
||||||
* @return A decoded array of floats from the packet.
|
|
||||||
"""
|
"""
|
||||||
ret = []
|
ret = []
|
||||||
for _ in range(length):
|
for _ in range(length):
|
||||||
ret.append(self.decodeDouble())
|
ret.append(self.decodeDouble())
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def decodeShortList(self) -> list[float]:
|
||||||
|
"""
|
||||||
|
* Returns a decoded array of shorts from the packet.
|
||||||
|
"""
|
||||||
|
length = self.decode8()
|
||||||
|
ret = []
|
||||||
|
for _ in range(length):
|
||||||
|
ret.append(self.decode16())
|
||||||
|
return ret
|
||||||
|
|
||||||
def decodeTransform(self) -> Transform3d:
|
def decodeTransform(self) -> Transform3d:
|
||||||
"""
|
"""
|
||||||
* Returns a decoded Transform3d
|
* Returns a decoded Transform3d
|
||||||
@@ -157,3 +166,16 @@ class Packet:
|
|||||||
rotation = Rotation3d(Quaternion(w, x, y, z))
|
rotation = Rotation3d(Quaternion(w, x, y, z))
|
||||||
|
|
||||||
return Transform3d(translation, rotation)
|
return Transform3d(translation, rotation)
|
||||||
|
|
||||||
|
def decodeList(self, serde: Type):
|
||||||
|
retList = []
|
||||||
|
arr_len = self.decode8()
|
||||||
|
for _ in range(arr_len):
|
||||||
|
retList.append(serde.unpack(self))
|
||||||
|
return retList
|
||||||
|
|
||||||
|
def decodeOptional(self, serde: Type) -> Optional[Any]:
|
||||||
|
if self.decodeBoolean():
|
||||||
|
return serde.unpack(self)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ from typing import List
|
|||||||
import ntcore
|
import ntcore
|
||||||
from wpilib import RobotController, Timer
|
from wpilib import RobotController, Timer
|
||||||
import wpilib
|
import wpilib
|
||||||
from photonlibpy.packet import Packet
|
from .packet import Packet
|
||||||
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
from .targeting.photonPipelineResult import PhotonPipelineResult
|
||||||
from photonlibpy.version import PHOTONVISION_VERSION, PHOTONLIB_VERSION # type: ignore[import-untyped]
|
from .version import PHOTONVISION_VERSION, PHOTONLIB_VERSION # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
# magical import to make serde stuff work
|
||||||
|
import photonlibpy.generated # noqa
|
||||||
|
|
||||||
|
|
||||||
class VisionLEDMode(Enum):
|
class VisionLEDMode(Enum):
|
||||||
@@ -100,11 +103,9 @@ class PhotonCamera:
|
|||||||
else:
|
else:
|
||||||
newResult = PhotonPipelineResult()
|
newResult = PhotonPipelineResult()
|
||||||
pkt = Packet(byteList)
|
pkt = Packet(byteList)
|
||||||
newResult.populateFromPacket(pkt)
|
newResult = PhotonPipelineResult.photonStruct.unpack(pkt)
|
||||||
# NT4 allows us to correct the timestamp based on when the message was sent
|
# NT4 allows us to correct the timestamp based on when the message was sent
|
||||||
newResult.setTimestampSeconds(
|
newResult.ntReceiveTimestampMicros = timestamp / 1e6
|
||||||
timestamp / 1e6 - newResult.getLatencyMillis() / 1e3
|
|
||||||
)
|
|
||||||
ret.append(newResult)
|
ret.append(newResult)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -113,18 +114,17 @@ class PhotonCamera:
|
|||||||
self._versionCheck()
|
self._versionCheck()
|
||||||
|
|
||||||
now = RobotController.getFPGATime()
|
now = RobotController.getFPGATime()
|
||||||
retVal = PhotonPipelineResult()
|
|
||||||
packetWithTimestamp = self._rawBytesEntry.getAtomic()
|
packetWithTimestamp = self._rawBytesEntry.getAtomic()
|
||||||
byteList = packetWithTimestamp.value
|
byteList = packetWithTimestamp.value
|
||||||
timestamp = packetWithTimestamp.time
|
packetWithTimestamp.time
|
||||||
|
|
||||||
if len(byteList) < 1:
|
if len(byteList) < 1:
|
||||||
return retVal
|
return PhotonPipelineResult()
|
||||||
else:
|
else:
|
||||||
pkt = Packet(byteList)
|
pkt = Packet(byteList)
|
||||||
retVal.populateFromPacket(pkt)
|
retVal = PhotonPipelineResult.photonStruct.unpack(pkt)
|
||||||
# We don't trust NT4 time, hack around
|
# We don't trust NT4 time, hack around
|
||||||
retVal.ntRecieveTimestampMicros = now
|
retVal.ntReceiveTimestampMicros = now
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def getDriverMode(self) -> bool:
|
def getDriverMode(self) -> bool:
|
||||||
@@ -233,6 +233,6 @@ class PhotonCamera:
|
|||||||
|
|
||||||
wpilib.reportWarning(bfw)
|
wpilib.reportWarning(bfw)
|
||||||
|
|
||||||
errText = f"Photon version {PHOTONLIB_VERSION} does not match coprocessor version {versionString}. Please install photonlibpy version {PHOTONLIB_VERSION}."
|
errText = f"Photon version {PHOTONLIB_VERSION} does not match coprocessor version {versionString}. Please install photonlibpy version {versionString}, or update your coprocessor to {PHOTONLIB_VERSION}."
|
||||||
wpilib.reportError(errText, True)
|
wpilib.reportError(errText, True)
|
||||||
raise Exception(errText)
|
raise Exception(errText)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import wpilib
|
|||||||
from robotpy_apriltag import AprilTagFieldLayout
|
from robotpy_apriltag import AprilTagFieldLayout
|
||||||
from wpimath.geometry import Transform3d, Pose3d, Pose2d
|
from wpimath.geometry import Transform3d, Pose3d, Pose2d
|
||||||
|
|
||||||
from .photonPipelineResult import PhotonPipelineResult
|
from .targeting.photonPipelineResult import PhotonPipelineResult
|
||||||
from .photonCamera import PhotonCamera
|
from .photonCamera import PhotonCamera
|
||||||
from .estimatedRobotPose import EstimatedRobotPose
|
from .estimatedRobotPose import EstimatedRobotPose
|
||||||
|
|
||||||
|
|||||||
9
photon-lib/py/photonlibpy/targeting/TargetCorner.py
Normal file
9
photon-lib/py/photonlibpy/targeting/TargetCorner.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TargetCorner:
|
||||||
|
x: float = 0
|
||||||
|
y: float = 9
|
||||||
|
|
||||||
|
photonStruct: "TargetCornerSerde" = None
|
||||||
6
photon-lib/py/photonlibpy/targeting/__init__.py
Normal file
6
photon-lib/py/photonlibpy/targeting/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# no one but us chickens
|
||||||
|
|
||||||
|
from .TargetCorner import TargetCorner # noqa
|
||||||
|
from .multiTargetPNPResult import MultiTargetPNPResult, PnpResult # noqa
|
||||||
|
from .photonPipelineResult import PhotonPipelineMetadata, PhotonPipelineResult # noqa
|
||||||
|
from .photonTrackedTarget import PhotonTrackedTarget # noqa
|
||||||
34
photon-lib/py/photonlibpy/targeting/multiTargetPNPResult.py
Normal file
34
photon-lib/py/photonlibpy/targeting/multiTargetPNPResult.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from wpimath.geometry import Transform3d
|
||||||
|
from ..packet import Packet
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PnpResult:
|
||||||
|
best: Transform3d = field(default_factory=Transform3d)
|
||||||
|
alt: Transform3d = field(default_factory=Transform3d)
|
||||||
|
ambiguity: float = 0.0
|
||||||
|
bestReprojError: float = 0.0
|
||||||
|
altReprojError: float = 0.0
|
||||||
|
|
||||||
|
photonStruct: "PNPResultSerde" = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MultiTargetPNPResult:
|
||||||
|
_MAX_IDS = 32
|
||||||
|
|
||||||
|
estimatedPose: PnpResult = field(default_factory=PnpResult)
|
||||||
|
fiducialIDsUsed: list[int] = field(default_factory=list)
|
||||||
|
|
||||||
|
def createFromPacket(self, packet: Packet) -> Packet:
|
||||||
|
self.estimatedPose = PnpResult()
|
||||||
|
self.estimatedPose.createFromPacket(packet)
|
||||||
|
self.fiducialIDsUsed = []
|
||||||
|
for _ in range(MultiTargetPNPResult._MAX_IDS):
|
||||||
|
fidId = packet.decode16()
|
||||||
|
if fidId >= 0:
|
||||||
|
self.fiducialIDsUsed.append(fidId)
|
||||||
|
return packet
|
||||||
|
|
||||||
|
photonStruct: "MultiTargetPNPResultSerde" = None
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult
|
from .multiTargetPNPResult import MultiTargetPNPResult
|
||||||
from photonlibpy.packet import Packet
|
from .photonTrackedTarget import PhotonTrackedTarget
|
||||||
from photonlibpy.photonTrackedTarget import PhotonTrackedTarget
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PhotonPipelineResult:
|
class PhotonPipelineMetadata:
|
||||||
# Image capture and NT publish timestamp, in microseconds and in the coprocessor timebase. As
|
# Image capture and NT publish timestamp, in microseconds and in the coprocessor timebase. As
|
||||||
# reported by WPIUtilJNI::now.
|
# reported by WPIUtilJNI::now.
|
||||||
captureTimestampMicros: int = -1
|
captureTimestampMicros: int = -1
|
||||||
@@ -15,49 +15,44 @@ class PhotonPipelineResult:
|
|||||||
# Mirror of the heartbeat entry -- monotonically increasing
|
# Mirror of the heartbeat entry -- monotonically increasing
|
||||||
sequenceID: int = -1
|
sequenceID: int = -1
|
||||||
|
|
||||||
|
photonStruct: "PhotonPipelineMetadataSerde" = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PhotonPipelineResult:
|
||||||
# Since we don't trust NT time sync, keep track of when we got this packet into robot code
|
# Since we don't trust NT time sync, keep track of when we got this packet into robot code
|
||||||
ntRecieveTimestampMicros: int = -1
|
ntReceiveTimestampMicros: int = -1
|
||||||
|
|
||||||
targets: list[PhotonTrackedTarget] = field(default_factory=list)
|
targets: list[PhotonTrackedTarget] = field(default_factory=list)
|
||||||
multiTagResult: MultiTargetPNPResult = field(default_factory=MultiTargetPNPResult)
|
metadata: PhotonPipelineMetadata = field(default_factory=PhotonPipelineMetadata)
|
||||||
|
multiTagResult: Optional[MultiTargetPNPResult] = None
|
||||||
def populateFromPacket(self, packet: Packet) -> Packet:
|
|
||||||
self.targets = []
|
|
||||||
|
|
||||||
self.sequenceID = packet.decodei64()
|
|
||||||
self.captureTimestampMicros = packet.decodei64()
|
|
||||||
self.publishTimestampMicros = packet.decodei64()
|
|
||||||
|
|
||||||
targetCount = packet.decode8()
|
|
||||||
|
|
||||||
for _ in range(targetCount):
|
|
||||||
target = PhotonTrackedTarget()
|
|
||||||
target.createFromPacket(packet)
|
|
||||||
self.targets.append(target)
|
|
||||||
|
|
||||||
self.multiTagResult = MultiTargetPNPResult()
|
|
||||||
self.multiTagResult.createFromPacket(packet)
|
|
||||||
|
|
||||||
return packet
|
|
||||||
|
|
||||||
def getLatencyMillis(self) -> float:
|
def getLatencyMillis(self) -> float:
|
||||||
return (self.publishTimestampMicros - self.captureTimestampMicros) / 1e3
|
return (
|
||||||
|
self.metadata.publishTimestampMicros - self.metadata.captureTimestampMicros
|
||||||
|
) / 1e3
|
||||||
|
|
||||||
def getTimestampSeconds(self) -> float:
|
def getTimestampSeconds(self) -> float:
|
||||||
"""
|
"""
|
||||||
Returns the estimated time the frame was taken, in the recieved system's time base. This is
|
Returns the estimated time the frame was taken, in the Received system's time base. This is
|
||||||
calculated as (NT recieve time (robot base) - (publish timestamp, coproc timebase - capture
|
calculated as (NT Receive time (robot base) - (publish timestamp, coproc timebase - capture
|
||||||
timestamp, coproc timebase))
|
timestamp, coproc timebase))
|
||||||
"""
|
"""
|
||||||
# TODO - we don't trust NT4 to correctly latency-compensate ntRecieveTimestampMicros
|
# TODO - we don't trust NT4 to correctly latency-compensate ntReceiveTimestampMicros
|
||||||
return (
|
return (
|
||||||
self.ntRecieveTimestampMicros
|
self.ntReceiveTimestampMicros
|
||||||
- (self.publishTimestampMicros - self.captureTimestampMicros)
|
- (
|
||||||
|
self.metadata.publishTimestampMicros
|
||||||
|
- self.metadata.captureTimestampMicros
|
||||||
|
)
|
||||||
) / 1e6
|
) / 1e6
|
||||||
|
|
||||||
def getTargets(self) -> list[PhotonTrackedTarget]:
|
def getTargets(self) -> list[PhotonTrackedTarget]:
|
||||||
return self.targets
|
return self.targets
|
||||||
|
|
||||||
|
def hasTargets(self) -> bool:
|
||||||
|
return len(self.targets) > 0
|
||||||
|
|
||||||
def getBestTarget(self) -> PhotonTrackedTarget:
|
def getBestTarget(self) -> PhotonTrackedTarget:
|
||||||
"""
|
"""
|
||||||
Returns the best target in this pipeline result. If there are no targets, this method will
|
Returns the best target in this pipeline result. If there are no targets, this method will
|
||||||
@@ -67,5 +62,4 @@ class PhotonPipelineResult:
|
|||||||
return None
|
return None
|
||||||
return self.getTargets()[0]
|
return self.getTargets()[0]
|
||||||
|
|
||||||
def hasTargets(self) -> bool:
|
photonStruct: "PhotonPipelineResultSerde" = None
|
||||||
return len(self.targets) > 0
|
|
||||||
@@ -1,20 +1,11 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from wpimath.geometry import Transform3d
|
from wpimath.geometry import Transform3d
|
||||||
from photonlibpy.packet import Packet
|
from ..packet import Packet
|
||||||
|
from .TargetCorner import TargetCorner
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TargetCorner:
|
|
||||||
x: float
|
|
||||||
y: float
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PhotonTrackedTarget:
|
class PhotonTrackedTarget:
|
||||||
_MAX_CORNERS = 8
|
|
||||||
_NUM_BYTES_IN_FLOAT = 8
|
|
||||||
_PACK_SIZE_BYTES = _NUM_BYTES_IN_FLOAT * (5 + 7 + 2 * 4 + 1 + 7 + 2 * _MAX_CORNERS)
|
|
||||||
|
|
||||||
yaw: float = 0.0
|
yaw: float = 0.0
|
||||||
pitch: float = 0.0
|
pitch: float = 0.0
|
||||||
area: float = 0.0
|
area: float = 0.0
|
||||||
@@ -64,22 +55,4 @@ class PhotonTrackedTarget:
|
|||||||
retList.append(TargetCorner(cx, cy))
|
retList.append(TargetCorner(cx, cy))
|
||||||
return retList
|
return retList
|
||||||
|
|
||||||
def createFromPacket(self, packet: Packet) -> Packet:
|
photonStruct: "PhotonTrackedTargetSerde" = None
|
||||||
self.yaw = packet.decodeDouble()
|
|
||||||
self.pitch = packet.decodeDouble()
|
|
||||||
self.area = packet.decodeDouble()
|
|
||||||
self.skew = packet.decodeDouble()
|
|
||||||
self.fiducialId = packet.decode32()
|
|
||||||
|
|
||||||
self.classId = packet.decode32()
|
|
||||||
self.objDetectConf = packet.decodeFloat()
|
|
||||||
|
|
||||||
self.bestCameraToTarget = packet.decodeTransform()
|
|
||||||
self.altCameraToTarget = packet.decodeTransform()
|
|
||||||
|
|
||||||
self.poseAmbiguity = packet.decodeDouble()
|
|
||||||
|
|
||||||
self.minAreaRectCorners = self._decodeTargetList(packet, 4) # always four
|
|
||||||
numCorners = packet.decode8()
|
|
||||||
self.detectedCorners = self._decodeTargetList(packet, numCorners)
|
|
||||||
return packet
|
|
||||||
@@ -48,10 +48,7 @@ with open("photonlibpy/version.py", "w", encoding="utf-8") as fp:
|
|||||||
fp.write(f'PHOTONVISION_VERSION="{gitDescribeResult}"\n')
|
fp.write(f'PHOTONVISION_VERSION="{gitDescribeResult}"\n')
|
||||||
|
|
||||||
|
|
||||||
descriptionStr = f"""
|
descriptionStr = f"Pure-python implementation of PhotonLib for interfacing with PhotonVision on coprocessors. Implemented with PhotonVision version {gitDescribeResult} ."
|
||||||
Pure-python implementation of PhotonLib for interfacing with PhotonVision on coprocessors.
|
|
||||||
Implemented with PhotonVision version {gitDescribeResult} .
|
|
||||||
"""
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="photonlibpy",
|
name="photonlibpy",
|
||||||
|
|||||||
@@ -1,251 +1,244 @@
|
|||||||
from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult, PNPResult
|
# from photonlibpy import MultiTargetPNPResult, PnpResult
|
||||||
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
# from photonlibpy import PhotonPipelineResult
|
||||||
from photonlibpy.photonPoseEstimator import PhotonPoseEstimator, PoseStrategy
|
# from photonlibpy import PhotonPoseEstimator, PoseStrategy
|
||||||
from photonlibpy.photonTrackedTarget import PhotonTrackedTarget, TargetCorner
|
# from photonlibpy import PhotonTrackedTarget, TargetCorner, PhotonPipelineMetadata
|
||||||
from robotpy_apriltag import AprilTag, AprilTagFieldLayout
|
# from robotpy_apriltag import AprilTag, AprilTagFieldLayout
|
||||||
from wpimath.geometry import Pose3d, Rotation3d, Transform3d, Translation3d
|
# from wpimath.geometry import Pose3d, Rotation3d, Transform3d, Translation3d
|
||||||
|
|
||||||
|
|
||||||
class PhotonCameraInjector:
|
# class PhotonCameraInjector:
|
||||||
result: PhotonPipelineResult
|
# result: PhotonPipelineResult
|
||||||
|
|
||||||
def getLatestResult(self) -> PhotonPipelineResult:
|
# def getLatestResult(self) -> PhotonPipelineResult:
|
||||||
return self.result
|
# return self.result
|
||||||
|
|
||||||
|
|
||||||
def setupCommon() -> AprilTagFieldLayout:
|
# def setupCommon() -> AprilTagFieldLayout:
|
||||||
tagList = []
|
# tagList = []
|
||||||
tagPoses = (
|
# tagPoses = (
|
||||||
Pose3d(3, 3, 3, Rotation3d()),
|
# Pose3d(3, 3, 3, Rotation3d()),
|
||||||
Pose3d(5, 5, 5, Rotation3d()),
|
# Pose3d(5, 5, 5, Rotation3d()),
|
||||||
)
|
# )
|
||||||
for id_, pose in enumerate(tagPoses):
|
# for id_, pose in enumerate(tagPoses):
|
||||||
aprilTag = AprilTag()
|
# aprilTag = AprilTag()
|
||||||
aprilTag.ID = id_
|
# aprilTag.ID = id_
|
||||||
aprilTag.pose = pose
|
# aprilTag.pose = pose
|
||||||
tagList.append(aprilTag)
|
# tagList.append(aprilTag)
|
||||||
|
|
||||||
fieldLength = 54 / 3.281 # 54 ft -> meters
|
# fieldLength = 54 / 3.281 # 54 ft -> meters
|
||||||
fieldWidth = 27 / 3.281 # 24 ft -> meters
|
# fieldWidth = 27 / 3.281 # 24 ft -> meters
|
||||||
|
|
||||||
return AprilTagFieldLayout(tagList, fieldLength, fieldWidth)
|
# return AprilTagFieldLayout(tagList, fieldLength, fieldWidth)
|
||||||
|
|
||||||
|
|
||||||
def test_lowestAmbiguityStrategy():
|
# def test_lowestAmbiguityStrategy():
|
||||||
aprilTags = setupCommon()
|
# aprilTags = setupCommon()
|
||||||
|
|
||||||
cameraOne = PhotonCameraInjector()
|
# cameraOne = PhotonCameraInjector()
|
||||||
cameraOne.result = PhotonPipelineResult(
|
# cameraOne.result = PhotonPipelineResult(
|
||||||
0,
|
# 11 * 1e6,
|
||||||
2 * 1e3,
|
# [
|
||||||
1,
|
# PhotonTrackedTarget(
|
||||||
11 * 1e6,
|
# 3.0,
|
||||||
[
|
# -4.0,
|
||||||
PhotonTrackedTarget(
|
# 9.0,
|
||||||
3.0,
|
# 4.0,
|
||||||
-4.0,
|
# 0,
|
||||||
9.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
4.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
0,
|
# [
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(1, 2),
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# [
|
||||||
TargetCorner(7, 8),
|
# TargetCorner(1, 2),
|
||||||
],
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# 0.7,
|
||||||
TargetCorner(7, 8),
|
# ),
|
||||||
],
|
# PhotonTrackedTarget(
|
||||||
0.7,
|
# 3.0,
|
||||||
),
|
# -4.0,
|
||||||
PhotonTrackedTarget(
|
# 9.1,
|
||||||
3.0,
|
# 6.7,
|
||||||
-4.0,
|
# 1,
|
||||||
9.1,
|
# Transform3d(Translation3d(4, 2, 3), Rotation3d(0, 0, 0)),
|
||||||
6.7,
|
# Transform3d(Translation3d(4, 2, 3), Rotation3d(1, 5, 3)),
|
||||||
1,
|
# [
|
||||||
Transform3d(Translation3d(4, 2, 3), Rotation3d(0, 0, 0)),
|
# TargetCorner(1, 2),
|
||||||
Transform3d(Translation3d(4, 2, 3), Rotation3d(1, 5, 3)),
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# [
|
||||||
TargetCorner(7, 8),
|
# TargetCorner(1, 2),
|
||||||
],
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# 0.3,
|
||||||
TargetCorner(7, 8),
|
# ),
|
||||||
],
|
# PhotonTrackedTarget(
|
||||||
0.3,
|
# 9.0,
|
||||||
),
|
# -2.0,
|
||||||
PhotonTrackedTarget(
|
# 19.0,
|
||||||
9.0,
|
# 3.0,
|
||||||
-2.0,
|
# 0,
|
||||||
19.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
3.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
0,
|
# [
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(1, 2),
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# [
|
||||||
TargetCorner(7, 8),
|
# TargetCorner(1, 2),
|
||||||
],
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# 0.4,
|
||||||
TargetCorner(7, 8),
|
# ),
|
||||||
],
|
# ],
|
||||||
0.4,
|
# None,
|
||||||
),
|
# metadata=PhotonPipelineMetadata(0, 2 * 1e3, 0),
|
||||||
],
|
# )
|
||||||
)
|
|
||||||
|
|
||||||
estimator = PhotonPoseEstimator(
|
# estimator = PhotonPoseEstimator(
|
||||||
aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
# aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
||||||
)
|
# )
|
||||||
|
|
||||||
estimatedPose = estimator.update()
|
# estimatedPose = estimator.update()
|
||||||
pose = estimatedPose.estimatedPose
|
# pose = estimatedPose.estimatedPose
|
||||||
|
|
||||||
assertEquals(11 - 0.002, estimatedPose.timestampSeconds, 1e-3)
|
# assertEquals(11 - 0.002, estimatedPose.timestampSeconds, 1e-3)
|
||||||
assertEquals(1, pose.x, 0.01)
|
# assertEquals(1, pose.x, 0.01)
|
||||||
assertEquals(3, pose.y, 0.01)
|
# assertEquals(3, pose.y, 0.01)
|
||||||
assertEquals(2, pose.z, 0.01)
|
# assertEquals(2, pose.z, 0.01)
|
||||||
|
|
||||||
|
|
||||||
def test_multiTagOnCoprocStrategy():
|
# def test_multiTagOnCoprocStrategy():
|
||||||
cameraOne = PhotonCameraInjector()
|
# cameraOne = PhotonCameraInjector()
|
||||||
cameraOne.result = PhotonPipelineResult(
|
# cameraOne.result = PhotonPipelineResult(
|
||||||
0,
|
# 11 * 1e6,
|
||||||
2 * 1e3,
|
# # There needs to be at least one target present for pose estimation to work
|
||||||
1,
|
# # Doesn't matter which/how many targets for this test
|
||||||
11 * 1e6,
|
# [
|
||||||
# There needs to be at least one target present for pose estimation to work
|
# PhotonTrackedTarget(
|
||||||
# Doesn't matter which/how many targets for this test
|
# 3.0,
|
||||||
[
|
# -4.0,
|
||||||
PhotonTrackedTarget(
|
# 9.0,
|
||||||
3.0,
|
# 4.0,
|
||||||
-4.0,
|
# 0,
|
||||||
9.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
4.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
0,
|
# [
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(1, 2),
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# [
|
||||||
TargetCorner(7, 8),
|
# TargetCorner(1, 2),
|
||||||
],
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# 0.7,
|
||||||
TargetCorner(7, 8),
|
# )
|
||||||
],
|
# ],
|
||||||
0.7,
|
# multiTagResult=MultiTargetPNPResult(
|
||||||
)
|
# PnpResult(True, Transform3d(1, 3, 2, Rotation3d()))
|
||||||
],
|
# ),
|
||||||
multiTagResult=MultiTargetPNPResult(
|
# metadata=PhotonPipelineMetadata(0, 2 * 1e3, 0),
|
||||||
PNPResult(True, Transform3d(1, 3, 2, Rotation3d()))
|
# )
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
estimator = PhotonPoseEstimator(
|
# estimator = PhotonPoseEstimator(
|
||||||
AprilTagFieldLayout(),
|
# AprilTagFieldLayout(),
|
||||||
PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
|
# PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
|
||||||
cameraOne,
|
# cameraOne,
|
||||||
Transform3d(),
|
# Transform3d(),
|
||||||
)
|
# )
|
||||||
|
|
||||||
estimatedPose = estimator.update()
|
# estimatedPose = estimator.update()
|
||||||
pose = estimatedPose.estimatedPose
|
# pose = estimatedPose.estimatedPose
|
||||||
|
|
||||||
assertEquals(11 - 2e-3, estimatedPose.timestampSeconds, 1e-3)
|
# assertEquals(11 - 2e-3, estimatedPose.timestampSeconds, 1e-3)
|
||||||
assertEquals(1, pose.x, 0.01)
|
# assertEquals(1, pose.x, 0.01)
|
||||||
assertEquals(3, pose.y, 0.01)
|
# assertEquals(3, pose.y, 0.01)
|
||||||
assertEquals(2, pose.z, 0.01)
|
# assertEquals(2, pose.z, 0.01)
|
||||||
|
|
||||||
|
|
||||||
def test_cacheIsInvalidated():
|
# def test_cacheIsInvalidated():
|
||||||
aprilTags = setupCommon()
|
# aprilTags = setupCommon()
|
||||||
|
|
||||||
cameraOne = PhotonCameraInjector()
|
# cameraOne = PhotonCameraInjector()
|
||||||
result = PhotonPipelineResult(
|
# result = PhotonPipelineResult(
|
||||||
0,
|
# 20 * 1e6,
|
||||||
2 * 1e3,
|
# [
|
||||||
1,
|
# PhotonTrackedTarget(
|
||||||
20 * 1e6,
|
# 3.0,
|
||||||
[
|
# -4.0,
|
||||||
PhotonTrackedTarget(
|
# 9.0,
|
||||||
3.0,
|
# 4.0,
|
||||||
-4.0,
|
# 0,
|
||||||
9.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
4.0,
|
# Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
0,
|
# [
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(1, 2),
|
||||||
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# [
|
||||||
TargetCorner(7, 8),
|
# TargetCorner(1, 2),
|
||||||
],
|
# TargetCorner(3, 4),
|
||||||
[
|
# TargetCorner(5, 6),
|
||||||
TargetCorner(1, 2),
|
# TargetCorner(7, 8),
|
||||||
TargetCorner(3, 4),
|
# ],
|
||||||
TargetCorner(5, 6),
|
# 0.7,
|
||||||
TargetCorner(7, 8),
|
# )
|
||||||
],
|
# ],
|
||||||
0.7,
|
# metadata=PhotonPipelineMetadata(0, 2 * 1e3, 0),
|
||||||
)
|
# )
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
estimator = PhotonPoseEstimator(
|
# estimator = PhotonPoseEstimator(
|
||||||
aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
# aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Empty result, expect empty result
|
# # Empty result, expect empty result
|
||||||
cameraOne.result = PhotonPipelineResult(
|
# cameraOne.result = PhotonPipelineResult(0)
|
||||||
captureTimestampMicros=0, publishTimestampMicros=0, ntRecieveTimestampMicros=1e6
|
# estimatedPose = estimator.update()
|
||||||
)
|
# assert estimatedPose is None
|
||||||
estimatedPose = estimator.update()
|
|
||||||
assert estimatedPose is None
|
|
||||||
|
|
||||||
# Set actual result
|
# # Set actual result
|
||||||
cameraOne.result = result
|
# cameraOne.result = result
|
||||||
estimatedPose = estimator.update()
|
# estimatedPose = estimator.update()
|
||||||
assert estimatedPose is not None
|
# assert estimatedPose is not None
|
||||||
assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
# assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
||||||
assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
# assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
||||||
|
|
||||||
# And again -- pose cache should mean this is empty
|
# # And again -- pose cache should mean this is empty
|
||||||
cameraOne.result = result
|
# cameraOne.result = result
|
||||||
estimatedPose = estimator.update()
|
# estimatedPose = estimator.update()
|
||||||
assert estimatedPose is None
|
# assert estimatedPose is None
|
||||||
# Expect the old timestamp to still be here
|
# # Expect the old timestamp to still be here
|
||||||
assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
# assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
||||||
|
|
||||||
# Set new field layout -- right after, the pose cache timestamp should be -1
|
# # Set new field layout -- right after, the pose cache timestamp should be -1
|
||||||
estimator.fieldTags = AprilTagFieldLayout([AprilTag()], 0, 0)
|
# estimator.fieldTags = AprilTagFieldLayout([AprilTag()], 0, 0)
|
||||||
assertEquals(-1, estimator._poseCacheTimestampSeconds)
|
# assertEquals(-1, estimator._poseCacheTimestampSeconds)
|
||||||
# Update should cache the current timestamp (20) again
|
# # Update should cache the current timestamp (20) again
|
||||||
cameraOne.result = result
|
# cameraOne.result = result
|
||||||
estimatedPose = estimator.update()
|
# estimatedPose = estimator.update()
|
||||||
assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
# assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
||||||
assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
# assertEquals(20 - 2e-3, estimator._poseCacheTimestampSeconds, 1e-3)
|
||||||
|
|
||||||
|
|
||||||
def assertEquals(expected, actual, epsilon=0.0):
|
# def assertEquals(expected, actual, epsilon=0.0):
|
||||||
assert abs(expected - actual) <= epsilon
|
# assert abs(expected - actual) <= epsilon
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
|
from time import sleep
|
||||||
|
from photonlibpy import PhotonCamera
|
||||||
|
import ntcore
|
||||||
|
from photonlibpy.photonCamera import setVersionCheckEnabled
|
||||||
|
|
||||||
|
|
||||||
def test_roundTrip():
|
def test_roundTrip():
|
||||||
# TODO implement packet encoding, or just kill me
|
|
||||||
assert True
|
ntcore.NetworkTableInstance.getDefault().stopServer()
|
||||||
|
ntcore.NetworkTableInstance.getDefault().setServer("localhost")
|
||||||
|
ntcore.NetworkTableInstance.getDefault().startClient4("meme")
|
||||||
|
|
||||||
|
camera = PhotonCamera("WPI2024")
|
||||||
|
|
||||||
|
setVersionCheckEnabled(False)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
sleep(0.1)
|
||||||
|
result = camera.getLatestResult()
|
||||||
|
print(result)
|
||||||
|
print(camera._rawBytesEntry.getTopic().getProperties())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_roundTrip()
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
PubSubOption.periodic(0.01),
|
PubSubOption.periodic(0.01),
|
||||||
PubSubOption.sendAll(true),
|
PubSubOption.sendAll(true),
|
||||||
PubSubOption.pollStorage(20));
|
PubSubOption.pollStorage(20));
|
||||||
resultSubscriber = new PacketSubscriber<>(rawBytesEntry, PhotonPipelineResult.serde);
|
resultSubscriber = new PacketSubscriber<>(rawBytesEntry, PhotonPipelineResult.photonStruct);
|
||||||
driverModePublisher = cameraTable.getBooleanTopic("driverModeRequest").publish();
|
driverModePublisher = cameraTable.getBooleanTopic("driverModeRequest").publish();
|
||||||
driverModeSubscriber = cameraTable.getBooleanTopic("driverMode").subscribe(false);
|
driverModeSubscriber = cameraTable.getBooleanTopic("driverMode").subscribe(false);
|
||||||
inputSaveImgEntry = cameraTable.getIntegerTopic("inputSaveImgCmd").getEntry(0);
|
inputSaveImgEntry = cameraTable.getIntegerTopic("inputSaveImgCmd").getEntry(0);
|
||||||
@@ -193,7 +193,7 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
// make time sync more reliable.
|
// make time sync more reliable.
|
||||||
for (var c : changes) {
|
for (var c : changes) {
|
||||||
var result = c.value;
|
var result = c.value;
|
||||||
result.setRecieveTimestampMicros(c.timestamp);
|
result.setReceiveTimestampMicros(c.timestamp);
|
||||||
ret.add(result);
|
ret.add(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the latest pipeline result. This is simply the most recent result recieved via NT.
|
* Returns the latest pipeline result. This is simply the most recent result Received via NT.
|
||||||
* Calling this multiple times will always return the most recent result.
|
* Calling this multiple times will always return the most recent result.
|
||||||
*
|
*
|
||||||
* <p>Replaced by {@link #getAllUnreadResults()} over getLatestResult, as this function can miss
|
* <p>Replaced by {@link #getAllUnreadResults()} over getLatestResult, as this function can miss
|
||||||
@@ -221,7 +221,7 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
// contains a thing with time knowledge, set it here.
|
// contains a thing with time knowledge, set it here.
|
||||||
// getLatestChange returns in microseconds, so we divide by 1e6 to convert to seconds.
|
// getLatestChange returns in microseconds, so we divide by 1e6 to convert to seconds.
|
||||||
// TODO: NT4 time sync is Not To Be Trusted, we should do something else?
|
// TODO: NT4 time sync is Not To Be Trusted, we should do something else?
|
||||||
result.setRecieveTimestampMicros(ret.timestamp);
|
result.setReceiveTimestampMicros(ret.timestamp);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -411,9 +411,20 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
"PhotonVision coprocessor at path " + path + " is not sending new data.", true);
|
"PhotonVision coprocessor at path " + path + " is not sending new data.", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for version. Warn if the versions aren't aligned.
|
|
||||||
String versionString = versionEntry.get("");
|
String versionString = versionEntry.get("");
|
||||||
if (!versionString.isEmpty() && !PhotonVersion.versionMatches(versionString)) {
|
|
||||||
|
// Check mdef UUID
|
||||||
|
String local_uuid = PhotonPipelineResult.photonStruct.getInterfaceUUID();
|
||||||
|
String remote_uuid = resultSubscriber.getInterfaceUUID();
|
||||||
|
|
||||||
|
if (remote_uuid == null || remote_uuid.isEmpty()) {
|
||||||
|
// not connected yet?
|
||||||
|
DriverStation.reportWarning(
|
||||||
|
"PhotonVision coprocessor at path "
|
||||||
|
+ path
|
||||||
|
+ " has note reported a message interface UUID - is your coprocessor's camera started?",
|
||||||
|
true);
|
||||||
|
} else if (!local_uuid.equals(remote_uuid)) {
|
||||||
// Error on a verified version mismatch
|
// Error on a verified version mismatch
|
||||||
// But stay silent otherwise
|
// But stay silent otherwise
|
||||||
|
|
||||||
@@ -439,8 +450,14 @@ public class PhotonCamera implements AutoCloseable {
|
|||||||
var versionMismatchMessage =
|
var versionMismatchMessage =
|
||||||
"Photon version "
|
"Photon version "
|
||||||
+ PhotonVersion.versionString
|
+ PhotonVersion.versionString
|
||||||
|
+ " (message definition version "
|
||||||
|
+ local_uuid
|
||||||
|
+ ")"
|
||||||
+ " does not match coprocessor version "
|
+ " does not match coprocessor version "
|
||||||
+ versionString
|
+ versionString
|
||||||
|
+ " (message definition version "
|
||||||
|
+ remote_uuid
|
||||||
|
+ ")"
|
||||||
+ "!";
|
+ "!";
|
||||||
DriverStation.reportError(versionMismatchMessage, false);
|
DriverStation.reportError(versionMismatchMessage, false);
|
||||||
throw new UnsupportedOperationException(versionMismatchMessage);
|
throw new UnsupportedOperationException(versionMismatchMessage);
|
||||||
|
|||||||
@@ -394,8 +394,8 @@ public class PhotonPoseEstimator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<EstimatedRobotPose> multiTagOnCoprocStrategy(PhotonPipelineResult result) {
|
private Optional<EstimatedRobotPose> multiTagOnCoprocStrategy(PhotonPipelineResult result) {
|
||||||
if (result.getMultiTagResult().estimatedPose.isPresent) {
|
if (result.getMultiTagResult().isPresent()) {
|
||||||
var best_tf = result.getMultiTagResult().estimatedPose.best;
|
var best_tf = result.getMultiTagResult().get().estimatedPose.best;
|
||||||
var best =
|
var best =
|
||||||
new Pose3d()
|
new Pose3d()
|
||||||
.plus(best_tf) // field-to-camera
|
.plus(best_tf) // field-to-camera
|
||||||
@@ -427,11 +427,11 @@ public class PhotonPoseEstimator {
|
|||||||
VisionEstimation.estimateCamPosePNP(
|
VisionEstimation.estimateCamPosePNP(
|
||||||
cameraMatrixOpt.get(), distCoeffsOpt.get(), result.getTargets(), fieldTags, tagModel);
|
cameraMatrixOpt.get(), distCoeffsOpt.get(), result.getTargets(), fieldTags, tagModel);
|
||||||
// try fallback strategy if solvePNP fails for some reason
|
// try fallback strategy if solvePNP fails for some reason
|
||||||
if (!pnpResult.isPresent)
|
if (!pnpResult.isPresent())
|
||||||
return update(result, cameraMatrixOpt, distCoeffsOpt, this.multiTagFallbackStrategy);
|
return update(result, cameraMatrixOpt, distCoeffsOpt, this.multiTagFallbackStrategy);
|
||||||
var best =
|
var best =
|
||||||
new Pose3d()
|
new Pose3d()
|
||||||
.plus(pnpResult.best) // field-to-camera
|
.plus(pnpResult.get().best) // field-to-camera
|
||||||
.plus(robotToCamera.inverse()); // field-to-robot
|
.plus(robotToCamera.inverse()); // field-to-robot
|
||||||
|
|
||||||
return Optional.of(
|
return Optional.of(
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ import org.photonvision.estimation.RotTrlTransform3d;
|
|||||||
import org.photonvision.estimation.TargetModel;
|
import org.photonvision.estimation.TargetModel;
|
||||||
import org.photonvision.estimation.VisionEstimation;
|
import org.photonvision.estimation.VisionEstimation;
|
||||||
import org.photonvision.targeting.MultiTargetPNPResult;
|
import org.photonvision.targeting.MultiTargetPNPResult;
|
||||||
import org.photonvision.targeting.PNPResult;
|
|
||||||
import org.photonvision.targeting.PhotonPipelineResult;
|
import org.photonvision.targeting.PhotonPipelineResult;
|
||||||
import org.photonvision.targeting.PhotonTrackedTarget;
|
import org.photonvision.targeting.PhotonTrackedTarget;
|
||||||
|
import org.photonvision.targeting.PnpResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handle for simulating {@link PhotonCamera} values. Processing simulated targets through this
|
* A handle for simulating {@link PhotonCamera} values. Processing simulated targets through this
|
||||||
@@ -420,14 +420,15 @@ public class PhotonCameraSim implements AutoCloseable {
|
|||||||
// projected target can't be detected, skip to next
|
// projected target can't be detected, skip to next
|
||||||
if (!(canSeeCorners(noisyTargetCorners) && areaPercent >= minTargetAreaPercent)) continue;
|
if (!(canSeeCorners(noisyTargetCorners) && areaPercent >= minTargetAreaPercent)) continue;
|
||||||
|
|
||||||
var pnpSim = new PNPResult();
|
var pnpSim = new PnpResult();
|
||||||
if (tgt.fiducialID >= 0 && tgt.getFieldVertices().size() == 4) { // single AprilTag solvePNP
|
if (tgt.fiducialID >= 0 && tgt.getFieldVertices().size() == 4) { // single AprilTag solvePNP
|
||||||
pnpSim =
|
pnpSim =
|
||||||
OpenCVHelp.solvePNP_SQUARE(
|
OpenCVHelp.solvePNP_SQPNP(
|
||||||
prop.getIntrinsics(),
|
prop.getIntrinsics(),
|
||||||
prop.getDistCoeffs(),
|
prop.getDistCoeffs(),
|
||||||
tgt.getModel().vertices,
|
tgt.getModel().vertices,
|
||||||
noisyTargetCorners);
|
noisyTargetCorners)
|
||||||
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
detectableTgts.add(
|
detectableTgts.add(
|
||||||
@@ -519,13 +520,13 @@ public class PhotonCameraSim implements AutoCloseable {
|
|||||||
} else videoSimProcessed.setConnectionStrategy(ConnectionStrategy.kForceClose);
|
} else videoSimProcessed.setConnectionStrategy(ConnectionStrategy.kForceClose);
|
||||||
|
|
||||||
// calculate multitag results
|
// calculate multitag results
|
||||||
var multitagResult = new MultiTargetPNPResult();
|
Optional<MultiTargetPNPResult> multitagResult = Optional.empty();
|
||||||
// TODO: Implement ATFL subscribing in backend
|
// TODO: Implement ATFL subscribing in backend
|
||||||
// var tagLayout = cam.getAprilTagFieldLayout();
|
// var tagLayout = cam.getAprilTagFieldLayout();
|
||||||
var visibleLayoutTags = VisionEstimation.getVisibleLayoutTags(detectableTgts, tagLayout);
|
var visibleLayoutTags = VisionEstimation.getVisibleLayoutTags(detectableTgts, tagLayout);
|
||||||
if (visibleLayoutTags.size() > 1) {
|
if (visibleLayoutTags.size() > 1) {
|
||||||
List<Integer> usedIDs =
|
List<Short> usedIDs =
|
||||||
visibleLayoutTags.stream().map(t -> t.ID).sorted().collect(Collectors.toList());
|
visibleLayoutTags.stream().map(t -> (short) t.ID).sorted().collect(Collectors.toList());
|
||||||
var pnpResult =
|
var pnpResult =
|
||||||
VisionEstimation.estimateCamPosePNP(
|
VisionEstimation.estimateCamPosePNP(
|
||||||
prop.getIntrinsics(),
|
prop.getIntrinsics(),
|
||||||
@@ -533,7 +534,10 @@ public class PhotonCameraSim implements AutoCloseable {
|
|||||||
detectableTgts,
|
detectableTgts,
|
||||||
tagLayout,
|
tagLayout,
|
||||||
TargetModel.kAprilTag36h11);
|
TargetModel.kAprilTag36h11);
|
||||||
multitagResult = new MultiTargetPNPResult(pnpResult, usedIDs);
|
|
||||||
|
if (pnpResult.isPresent()) {
|
||||||
|
multitagResult = Optional.of(new MultiTargetPNPResult(pnpResult.get(), usedIDs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort target order
|
// sort target order
|
||||||
@@ -550,7 +554,7 @@ public class PhotonCameraSim implements AutoCloseable {
|
|||||||
now,
|
now,
|
||||||
detectableTgts,
|
detectableTgts,
|
||||||
multitagResult);
|
multitagResult);
|
||||||
ret.setRecieveTimestampMicros(now);
|
ret.setReceiveTimestampMicros(now);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,9 +577,10 @@ public class PhotonCameraSim implements AutoCloseable {
|
|||||||
* @param receiveTimestamp The (sim) timestamp when this result was read by NT in microseconds
|
* @param receiveTimestamp The (sim) timestamp when this result was read by NT in microseconds
|
||||||
*/
|
*/
|
||||||
public void submitProcessedFrame(PhotonPipelineResult result, long receiveTimestamp) {
|
public void submitProcessedFrame(PhotonPipelineResult result, long receiveTimestamp) {
|
||||||
ts.latencyMillisEntry.set(result.getLatencyMillis(), receiveTimestamp);
|
ts.latencyMillisEntry.set(result.metadata.getLatencyMillis(), receiveTimestamp);
|
||||||
|
|
||||||
ts.resultPublisher.set(result, result.getPacketSize());
|
// Results are now dynamically sized, so let's guess 1024 bytes is big enough
|
||||||
|
ts.resultPublisher.set(result, 1024);
|
||||||
|
|
||||||
boolean hasTargets = result.hasTargets();
|
boolean hasTargets = result.hasTargets();
|
||||||
ts.hasTargetEntry.set(hasTargets, receiveTimestamp);
|
ts.hasTargetEntry.set(hasTargets, receiveTimestamp);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include <frc/Timer.h>
|
#include <frc/Timer.h>
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <opencv2/core/mat.hpp>
|
#include <opencv2/core/mat.hpp>
|
||||||
|
#include <wpi/json.h>
|
||||||
|
|
||||||
#include "PhotonVersion.h"
|
#include "PhotonVersion.h"
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
@@ -121,20 +122,18 @@ PhotonPipelineResult PhotonCamera::GetLatestResult() {
|
|||||||
// Prints warning if not connected
|
// Prints warning if not connected
|
||||||
VerifyVersion();
|
VerifyVersion();
|
||||||
|
|
||||||
// Create the new result;
|
|
||||||
PhotonPipelineResult result;
|
|
||||||
|
|
||||||
// Fill the packet with latest data and populate result.
|
// Fill the packet with latest data and populate result.
|
||||||
units::microsecond_t now =
|
units::microsecond_t now =
|
||||||
units::microsecond_t(frc::RobotController::GetFPGATime());
|
units::microsecond_t(frc::RobotController::GetFPGATime());
|
||||||
const auto value = rawBytesEntry.Get();
|
const auto value = rawBytesEntry.Get();
|
||||||
if (!value.size()) return result;
|
if (!value.size()) return PhotonPipelineResult{};
|
||||||
|
|
||||||
photon::Packet packet{value};
|
photon::Packet packet{value};
|
||||||
|
|
||||||
packet >> result;
|
// Create the new result;
|
||||||
|
PhotonPipelineResult result = packet.Unpack<PhotonPipelineResult>();
|
||||||
|
|
||||||
result.SetRecieveTimestamp(now);
|
result.SetReceiveTimestamp(now);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -149,8 +148,9 @@ std::vector<PhotonPipelineResult> PhotonCamera::GetAllUnreadResults() {
|
|||||||
|
|
||||||
const auto changes = rawBytesEntry.ReadQueue();
|
const auto changes = rawBytesEntry.ReadQueue();
|
||||||
|
|
||||||
// Create the new result list -- these will be updated in-place
|
// Create the new result list
|
||||||
std::vector<PhotonPipelineResult> ret(changes.size());
|
std::vector<PhotonPipelineResult> ret;
|
||||||
|
ret.reserve(changes.size());
|
||||||
|
|
||||||
for (size_t i = 0; i < changes.size(); i++) {
|
for (size_t i = 0; i < changes.size(); i++) {
|
||||||
const nt::Timestamped<std::vector<uint8_t>>& value = changes[i];
|
const nt::Timestamped<std::vector<uint8_t>>& value = changes[i];
|
||||||
@@ -161,13 +161,14 @@ std::vector<PhotonPipelineResult> PhotonCamera::GetAllUnreadResults() {
|
|||||||
|
|
||||||
// Fill the packet with latest data and populate result.
|
// Fill the packet with latest data and populate result.
|
||||||
photon::Packet packet{value.value};
|
photon::Packet packet{value.value};
|
||||||
|
auto result = packet.Unpack<PhotonPipelineResult>();
|
||||||
|
|
||||||
PhotonPipelineResult& result = ret[i];
|
|
||||||
packet >> result;
|
|
||||||
// TODO: NT4 timestamps are still not to be trusted. But it's the best we
|
// TODO: NT4 timestamps are still not to be trusted. But it's the best we
|
||||||
// can do until we can make time sync more reliable.
|
// can do until we can make time sync more reliable.
|
||||||
result.SetRecieveTimestamp(units::microsecond_t(value.time) -
|
result.SetReceiveTimestamp(units::microsecond_t(value.time) -
|
||||||
result.GetLatency());
|
result.GetLatency());
|
||||||
|
|
||||||
|
ret.push_back(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -209,9 +210,11 @@ std::optional<PhotonCamera::CameraMatrix> PhotonCamera::GetCameraMatrix() {
|
|||||||
auto camCoeffs = cameraIntrinsicsSubscriber.Get();
|
auto camCoeffs = cameraIntrinsicsSubscriber.Get();
|
||||||
if (camCoeffs.size() == 9) {
|
if (camCoeffs.size() == 9) {
|
||||||
PhotonCamera::CameraMatrix retVal =
|
PhotonCamera::CameraMatrix retVal =
|
||||||
Eigen::Map<const PhotonCamera::CameraMatrix>(camCoeffs.data());
|
Eigen::Map<const Eigen::Matrix<double, 3, 3, Eigen::RowMajor>>(
|
||||||
|
camCoeffs.data());
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,22 +233,6 @@ std::optional<PhotonCamera::DistortionMatrix> PhotonCamera::GetDistCoeffs() {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool VersionMatches(std::string them_str) {
|
|
||||||
std::smatch match;
|
|
||||||
std::regex versionPattern{"v[0-9]+.[0-9]+.[0-9]+"};
|
|
||||||
|
|
||||||
std::string us_str = PhotonVersion::versionString;
|
|
||||||
|
|
||||||
// Check that both versions are in the right format
|
|
||||||
if (std::regex_search(us_str, match, versionPattern) &&
|
|
||||||
std::regex_search(them_str, match, versionPattern)) {
|
|
||||||
// If they are, check string equality
|
|
||||||
return (us_str == them_str);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhotonCamera::VerifyVersion() {
|
void PhotonCamera::VerifyVersion() {
|
||||||
if (!PhotonCamera::VERSION_CHECK_ENABLED) {
|
if (!PhotonCamera::VERSION_CHECK_ENABLED) {
|
||||||
return;
|
return;
|
||||||
@@ -282,13 +269,20 @@ void PhotonCamera::VerifyVersion() {
|
|||||||
"Found the following PhotonVision cameras on NetworkTables:{}",
|
"Found the following PhotonVision cameras on NetworkTables:{}",
|
||||||
cameraNameOutString);
|
cameraNameOutString);
|
||||||
}
|
}
|
||||||
} else if (!VersionMatches(versionString)) {
|
} else {
|
||||||
FRC_ReportError(frc::warn::Warning, bfw);
|
std::string local_uuid{SerdeType<PhotonPipelineResult>::GetSchemaHash()};
|
||||||
std::string error_str = fmt::format(
|
std::string remote_uuid =
|
||||||
"Photonlib version {} does not match coprocessor version {}!",
|
rawBytesEntry.GetTopic().GetProperty("message_uuid");
|
||||||
PhotonVersion::versionString, versionString);
|
|
||||||
FRC_ReportError(frc::err::Error, "{}", error_str);
|
if (local_uuid != remote_uuid) {
|
||||||
throw std::runtime_error(error_str);
|
FRC_ReportError(frc::warn::Warning, bfw);
|
||||||
|
std::string error_str = fmt::format(
|
||||||
|
"Photonlib version {} (message definition version {}) does not match "
|
||||||
|
"coprocessor version {} (message definition version {})!",
|
||||||
|
PhotonVersion::versionString, local_uuid, versionString, remote_uuid);
|
||||||
|
FRC_ReportError(frc::err::Error, "{}", error_str);
|
||||||
|
throw std::runtime_error(error_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ std::optional<EstimatedRobotPose> PhotonPoseEstimator::Update(
|
|||||||
std::optional<PhotonCamera::DistortionMatrix> cameraDistCoeffs) {
|
std::optional<PhotonCamera::DistortionMatrix> cameraDistCoeffs) {
|
||||||
// Time in the past -- give up, since the following if expects times > 0
|
// Time in the past -- give up, since the following if expects times > 0
|
||||||
if (result.GetTimestamp() < 0_s) {
|
if (result.GetTimestamp() < 0_s) {
|
||||||
|
FRC_ReportError(frc::warn::Warning,
|
||||||
|
"Result timestamp was reported in the past!");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ std::optional<EstimatedRobotPose> PhotonPoseEstimator::Update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
lastPose = ret.value().estimatedPose;
|
lastPose = ret->estimatedPose;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -197,8 +199,7 @@ std::optional<EstimatedRobotPose> PhotonPoseEstimator::LowestAmbiguityStrategy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return EstimatedRobotPose{
|
return EstimatedRobotPose{
|
||||||
fiducialPose.value()
|
fiducialPose->TransformBy(bestTarget.GetBestCameraToTarget().Inverse())
|
||||||
.TransformBy(bestTarget.GetBestCameraToTarget().Inverse())
|
|
||||||
.TransformBy(m_robotToCamera.Inverse()),
|
.TransformBy(m_robotToCamera.Inverse()),
|
||||||
result.GetTimestamp(), result.GetTargets(), LOWEST_AMBIGUITY};
|
result.GetTimestamp(), result.GetTargets(), LOWEST_AMBIGUITY};
|
||||||
}
|
}
|
||||||
@@ -220,7 +221,7 @@ PhotonPoseEstimator::ClosestToCameraHeightStrategy(
|
|||||||
target.GetFiducialId());
|
target.GetFiducialId());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
frc::Pose3d const targetPose = fiducialPose.value();
|
frc::Pose3d const targetPose = *fiducialPose;
|
||||||
|
|
||||||
units::meter_t const alternativeDifference = units::math::abs(
|
units::meter_t const alternativeDifference = units::math::abs(
|
||||||
m_robotToCamera.Z() -
|
m_robotToCamera.Z() -
|
||||||
@@ -349,8 +350,8 @@ frc::Pose3d detail::ToPose3d(const cv::Mat& tvec, const cv::Mat& rvec) {
|
|||||||
|
|
||||||
std::optional<EstimatedRobotPose> PhotonPoseEstimator::MultiTagOnCoprocStrategy(
|
std::optional<EstimatedRobotPose> PhotonPoseEstimator::MultiTagOnCoprocStrategy(
|
||||||
PhotonPipelineResult result) {
|
PhotonPipelineResult result) {
|
||||||
if (result.MultiTagResult().result.isPresent) {
|
if (result.MultiTagResult()) {
|
||||||
const auto field2camera = result.MultiTagResult().result.best;
|
const auto field2camera = result.MultiTagResult()->estimatedPose.best;
|
||||||
|
|
||||||
const auto fieldToRobot =
|
const auto fieldToRobot =
|
||||||
frc::Pose3d() + field2camera + m_robotToCamera.Inverse();
|
frc::Pose3d() + field2camera + m_robotToCamera.Inverse();
|
||||||
@@ -398,8 +399,8 @@ std::optional<EstimatedRobotPose> PhotonPoseEstimator::MultiTagOnRioStrategy(
|
|||||||
tagCorners.has_value()) {
|
tagCorners.has_value()) {
|
||||||
auto const targetCorners = target.GetDetectedCorners();
|
auto const targetCorners = target.GetDetectedCorners();
|
||||||
for (size_t cornerIdx = 0; cornerIdx < 4; ++cornerIdx) {
|
for (size_t cornerIdx = 0; cornerIdx < 4; ++cornerIdx) {
|
||||||
imagePoints.emplace_back(targetCorners[cornerIdx].first,
|
imagePoints.emplace_back(targetCorners[cornerIdx].x,
|
||||||
targetCorners[cornerIdx].second);
|
targetCorners[cornerIdx].y);
|
||||||
objectPoints.emplace_back((*tagCorners)[cornerIdx]);
|
objectPoints.emplace_back((*tagCorners)[cornerIdx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,33 +201,34 @@ PhotonPipelineResult PhotonCameraSim::Process(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PNPResult pnpSim{};
|
std::optional<photon::PnpResult> pnpSim = std::nullopt;
|
||||||
if (tgt.fiducialId >= 0 && tgt.GetFieldVertices().size() == 4) {
|
if (tgt.fiducialId >= 0 && tgt.GetFieldVertices().size() == 4) {
|
||||||
pnpSim = OpenCVHelp::SolvePNP_Square(
|
pnpSim = OpenCVHelp::SolvePNP_SQPNP(
|
||||||
prop.GetIntrinsics(), prop.GetDistCoeffs(),
|
prop.GetIntrinsics(), prop.GetDistCoeffs(),
|
||||||
tgt.GetModel().GetVertices(), noisyTargetCorners);
|
tgt.GetModel().GetVertices(), noisyTargetCorners);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<float, float>> tempCorners =
|
std::vector<std::pair<float, float>> tempCorners =
|
||||||
OpenCVHelp::PointsToCorners(minAreaRectPts);
|
OpenCVHelp::PointsToCorners(minAreaRectPts);
|
||||||
wpi::SmallVector<std::pair<double, double>, 4> smallVec;
|
std::vector<TargetCorner> smallVec;
|
||||||
|
|
||||||
for (const auto& corner : tempCorners) {
|
for (const auto& corner : tempCorners) {
|
||||||
smallVec.emplace_back(std::make_pair(static_cast<double>(corner.first),
|
smallVec.emplace_back(static_cast<double>(corner.first),
|
||||||
static_cast<double>(corner.second)));
|
static_cast<double>(corner.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<float, float>> cornersFloat =
|
auto cornersFloat = OpenCVHelp::PointsToTargetCorners(noisyTargetCorners);
|
||||||
OpenCVHelp::PointsToCorners(noisyTargetCorners);
|
|
||||||
|
|
||||||
std::vector<std::pair<double, double>> cornersDouble{cornersFloat.begin(),
|
std::vector<TargetCorner> cornersDouble{cornersFloat.begin(),
|
||||||
cornersFloat.end()};
|
cornersFloat.end()};
|
||||||
detectableTgts.emplace_back(
|
detectableTgts.emplace_back(
|
||||||
-centerRot.Z().convert<units::degrees>().to<double>(),
|
-centerRot.Z().convert<units::degrees>().to<double>(),
|
||||||
-centerRot.Y().convert<units::degrees>().to<double>(), areaPercent,
|
-centerRot.Y().convert<units::degrees>().to<double>(), areaPercent,
|
||||||
centerRot.X().convert<units::degrees>().to<double>(), tgt.fiducialId,
|
centerRot.X().convert<units::degrees>().to<double>(), tgt.fiducialId,
|
||||||
tgt.objDetClassId, tgt.objDetConf, pnpSim.best, pnpSim.alt,
|
tgt.objDetClassId, tgt.objDetConf,
|
||||||
pnpSim.ambiguity, smallVec, cornersDouble);
|
pnpSim ? pnpSim->best : frc::Transform3d{},
|
||||||
|
pnpSim ? pnpSim->alt : frc::Transform3d{},
|
||||||
|
pnpSim ? pnpSim->ambiguity : -1, smallVec, cornersDouble);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoSimRawEnabled) {
|
if (videoSimRawEnabled) {
|
||||||
@@ -275,36 +276,27 @@ PhotonPipelineResult PhotonCameraSim::Process(
|
|||||||
cv::LINE_AA);
|
cv::LINE_AA);
|
||||||
for (const auto& tgt : detectableTgts) {
|
for (const auto& tgt : detectableTgts) {
|
||||||
auto detectedCornersDouble = tgt.GetDetectedCorners();
|
auto detectedCornersDouble = tgt.GetDetectedCorners();
|
||||||
std::vector<std::pair<float, float>> detectedCornerFloat{
|
|
||||||
detectedCornersDouble.begin(), detectedCornersDouble.end()};
|
|
||||||
if (tgt.GetFiducialId() >= 0) {
|
if (tgt.GetFiducialId() >= 0) {
|
||||||
VideoSimUtil::DrawTagDetection(
|
VideoSimUtil::DrawTagDetection(
|
||||||
tgt.GetFiducialId(),
|
tgt.GetFiducialId(),
|
||||||
OpenCVHelp::CornersToPoints(detectedCornerFloat),
|
OpenCVHelp::CornersToPoints(detectedCornersDouble),
|
||||||
videoSimFrameProcessed);
|
videoSimFrameProcessed);
|
||||||
} else {
|
} else {
|
||||||
cv::rectangle(videoSimFrameProcessed,
|
cv::rectangle(videoSimFrameProcessed,
|
||||||
OpenCVHelp::GetBoundingRect(
|
OpenCVHelp::GetBoundingRect(
|
||||||
OpenCVHelp::CornersToPoints(detectedCornerFloat)),
|
OpenCVHelp::CornersToPoints(detectedCornersDouble)),
|
||||||
cv::Scalar{0, 0, 255},
|
cv::Scalar{0, 0, 255},
|
||||||
static_cast<int>(VideoSimUtil::GetScaledThickness(
|
static_cast<int>(VideoSimUtil::GetScaledThickness(
|
||||||
1, videoSimFrameProcessed)),
|
1, videoSimFrameProcessed)),
|
||||||
cv::LINE_AA);
|
cv::LINE_AA);
|
||||||
|
|
||||||
wpi::SmallVector<std::pair<double, double>, 4> smallVec =
|
auto smallVec = tgt.GetMinAreaRectCorners();
|
||||||
tgt.GetMinAreaRectCorners();
|
|
||||||
|
|
||||||
std::vector<std::pair<float, float>> cornersCopy{};
|
std::vector<std::pair<float, float>> cornersCopy{};
|
||||||
cornersCopy.reserve(4);
|
cornersCopy.reserve(4);
|
||||||
|
|
||||||
for (const auto& corner : smallVec) {
|
|
||||||
cornersCopy.emplace_back(
|
|
||||||
std::make_pair(static_cast<float>(corner.first),
|
|
||||||
static_cast<float>(corner.second)));
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoSimUtil::DrawPoly(
|
VideoSimUtil::DrawPoly(
|
||||||
OpenCVHelp::CornersToPoints(cornersCopy),
|
OpenCVHelp::CornersToPoints(smallVec),
|
||||||
static_cast<int>(
|
static_cast<int>(
|
||||||
VideoSimUtil::GetScaledThickness(1, videoSimFrameProcessed)),
|
VideoSimUtil::GetScaledThickness(1, videoSimFrameProcessed)),
|
||||||
cv::Scalar{255, 30, 30}, true, videoSimFrameProcessed);
|
cv::Scalar{255, 30, 30}, true, videoSimFrameProcessed);
|
||||||
@@ -316,75 +308,81 @@ PhotonPipelineResult PhotonCameraSim::Process(
|
|||||||
cs::VideoSource::ConnectionStrategy::kConnectionForceClose);
|
cs::VideoSource::ConnectionStrategy::kConnectionForceClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiTargetPNPResult multiTagResults{};
|
std::optional<MultiTargetPNPResult> multiTagResults = std::nullopt;
|
||||||
|
|
||||||
std::vector<frc::AprilTag> visibleLayoutTags =
|
std::vector<frc::AprilTag> visibleLayoutTags =
|
||||||
VisionEstimation::GetVisibleLayoutTags(detectableTgts, tagLayout);
|
VisionEstimation::GetVisibleLayoutTags(detectableTgts, tagLayout);
|
||||||
if (visibleLayoutTags.size() > 1) {
|
if (visibleLayoutTags.size() > 1) {
|
||||||
wpi::SmallVector<int16_t, 32> usedIds{};
|
std::vector<int16_t> usedIds{};
|
||||||
|
usedIds.resize(visibleLayoutTags.size());
|
||||||
std::transform(visibleLayoutTags.begin(), visibleLayoutTags.end(),
|
std::transform(visibleLayoutTags.begin(), visibleLayoutTags.end(),
|
||||||
usedIds.begin(),
|
usedIds.begin(),
|
||||||
[](const frc::AprilTag& tag) { return tag.ID; });
|
[](const frc::AprilTag& tag) { return tag.ID; });
|
||||||
std::sort(usedIds.begin(), usedIds.end());
|
std::sort(usedIds.begin(), usedIds.end());
|
||||||
PNPResult pnpResult = VisionEstimation::EstimateCamPosePNP(
|
auto pnpResult = VisionEstimation::EstimateCamPosePNP(
|
||||||
prop.GetIntrinsics(), prop.GetDistCoeffs(), detectableTgts, tagLayout,
|
prop.GetIntrinsics(), prop.GetDistCoeffs(), detectableTgts, tagLayout,
|
||||||
kAprilTag36h11);
|
kAprilTag36h11);
|
||||||
multiTagResults = MultiTargetPNPResult{pnpResult, usedIds};
|
if (pnpResult) {
|
||||||
|
multiTagResults = MultiTargetPNPResult{*pnpResult, usedIds};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heartbeatCounter++;
|
heartbeatCounter++;
|
||||||
return PhotonPipelineResult{heartbeatCounter, 0_s, latency, detectableTgts,
|
return PhotonPipelineResult{
|
||||||
multiTagResults};
|
PhotonPipelineMetadata{heartbeatCounter, 0,
|
||||||
|
units::microsecond_t{latency}.to<int64_t>()},
|
||||||
|
detectableTgts, multiTagResults};
|
||||||
}
|
}
|
||||||
void PhotonCameraSim::SubmitProcessedFrame(const PhotonPipelineResult& result) {
|
void PhotonCameraSim::SubmitProcessedFrame(const PhotonPipelineResult& result) {
|
||||||
SubmitProcessedFrame(result, wpi::Now());
|
SubmitProcessedFrame(result, wpi::Now());
|
||||||
}
|
}
|
||||||
void PhotonCameraSim::SubmitProcessedFrame(const PhotonPipelineResult& result,
|
void PhotonCameraSim::SubmitProcessedFrame(const PhotonPipelineResult& result,
|
||||||
uint64_t recieveTimestamp) {
|
uint64_t ReceiveTimestamp) {
|
||||||
ts.latencyMillisEntry.Set(
|
ts.latencyMillisEntry.Set(
|
||||||
result.GetLatency().convert<units::milliseconds>().to<double>(),
|
result.GetLatency().convert<units::milliseconds>().to<double>(),
|
||||||
recieveTimestamp);
|
ReceiveTimestamp);
|
||||||
|
|
||||||
Packet newPacket{};
|
Packet newPacket{};
|
||||||
newPacket << result;
|
newPacket.Pack(result);
|
||||||
|
|
||||||
ts.rawBytesEntry.Set(newPacket.GetData(), recieveTimestamp);
|
ts.rawBytesEntry.Set(newPacket.GetData(), ReceiveTimestamp);
|
||||||
|
|
||||||
bool hasTargets = result.HasTargets();
|
bool hasTargets = result.HasTargets();
|
||||||
ts.hasTargetEntry.Set(hasTargets, recieveTimestamp);
|
ts.hasTargetEntry.Set(hasTargets, ReceiveTimestamp);
|
||||||
if (!hasTargets) {
|
if (!hasTargets) {
|
||||||
ts.targetPitchEntry.Set(0.0, recieveTimestamp);
|
ts.targetPitchEntry.Set(0.0, ReceiveTimestamp);
|
||||||
ts.targetYawEntry.Set(0.0, recieveTimestamp);
|
ts.targetYawEntry.Set(0.0, ReceiveTimestamp);
|
||||||
ts.targetAreaEntry.Set(0.0, recieveTimestamp);
|
ts.targetAreaEntry.Set(0.0, ReceiveTimestamp);
|
||||||
std::array<double, 3> poseData{0.0, 0.0, 0.0};
|
std::array<double, 3> poseData{0.0, 0.0, 0.0};
|
||||||
ts.targetPoseEntry.Set(poseData, recieveTimestamp);
|
ts.targetPoseEntry.Set(poseData, ReceiveTimestamp);
|
||||||
ts.targetSkewEntry.Set(0.0, recieveTimestamp);
|
ts.targetSkewEntry.Set(0.0, ReceiveTimestamp);
|
||||||
} else {
|
} else {
|
||||||
PhotonTrackedTarget bestTarget = result.GetBestTarget();
|
PhotonTrackedTarget bestTarget = result.GetBestTarget();
|
||||||
|
|
||||||
ts.targetPitchEntry.Set(bestTarget.GetPitch(), recieveTimestamp);
|
ts.targetPitchEntry.Set(bestTarget.GetPitch(), ReceiveTimestamp);
|
||||||
ts.targetYawEntry.Set(bestTarget.GetYaw(), recieveTimestamp);
|
ts.targetYawEntry.Set(bestTarget.GetYaw(), ReceiveTimestamp);
|
||||||
ts.targetAreaEntry.Set(bestTarget.GetArea(), recieveTimestamp);
|
ts.targetAreaEntry.Set(bestTarget.GetArea(), ReceiveTimestamp);
|
||||||
ts.targetSkewEntry.Set(bestTarget.GetSkew(), recieveTimestamp);
|
ts.targetSkewEntry.Set(bestTarget.GetSkew(), ReceiveTimestamp);
|
||||||
|
|
||||||
frc::Transform3d transform = bestTarget.GetBestCameraToTarget();
|
frc::Transform3d transform = bestTarget.GetBestCameraToTarget();
|
||||||
std::array<double, 4> poseData{
|
std::array<double, 4> poseData{
|
||||||
transform.X().to<double>(), transform.Y().to<double>(),
|
transform.X().to<double>(), transform.Y().to<double>(),
|
||||||
transform.Rotation().ToRotation2d().Degrees().to<double>()};
|
transform.Rotation().ToRotation2d().Degrees().to<double>()};
|
||||||
ts.targetPoseEntry.Set(poseData, recieveTimestamp);
|
ts.targetPoseEntry.Set(poseData, ReceiveTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto intrinsics = prop.GetIntrinsics();
|
Eigen::Matrix<double, 3, 3, Eigen::RowMajor> intrinsics =
|
||||||
std::vector<double> intrinsicsView{intrinsics.data(),
|
prop.GetIntrinsics();
|
||||||
intrinsics.data() + intrinsics.size()};
|
std::span<double> intrinsicsView{intrinsics.data(),
|
||||||
ts.cameraIntrinsicsPublisher.Set(intrinsicsView, recieveTimestamp);
|
intrinsics.data() + intrinsics.size()};
|
||||||
|
ts.cameraIntrinsicsPublisher.Set(intrinsicsView, ReceiveTimestamp);
|
||||||
|
|
||||||
auto distortion = prop.GetDistCoeffs();
|
auto distortion = prop.GetDistCoeffs();
|
||||||
std::vector<double> distortionView{distortion.data(),
|
std::vector<double> distortionView{distortion.data(),
|
||||||
distortion.data() + distortion.size()};
|
distortion.data() + distortion.size()};
|
||||||
ts.cameraDistortionPublisher.Set(distortionView, recieveTimestamp);
|
ts.cameraDistortionPublisher.Set(distortionView, ReceiveTimestamp);
|
||||||
|
|
||||||
ts.heartbeatPublisher.Set(heartbeatCounter, recieveTimestamp);
|
ts.heartbeatPublisher.Set(heartbeatCounter, ReceiveTimestamp);
|
||||||
|
|
||||||
ts.subTable->GetInstance().Flush();
|
ts.subTable->GetInstance().Flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
#include <networktables/StringTopic.h>
|
#include <networktables/StringTopic.h>
|
||||||
#include <units/time.h>
|
#include <units/time.h>
|
||||||
|
|
||||||
#include "photon/targeting//PhotonPipelineResult.h"
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
|
|
||||||
namespace cv {
|
namespace cv {
|
||||||
class Mat;
|
class Mat;
|
||||||
|
|||||||
@@ -34,9 +34,9 @@
|
|||||||
#include "photon/PhotonCamera.h"
|
#include "photon/PhotonCamera.h"
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
#include "photon/targeting/MultiTargetPNPResult.h"
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
#include "photon/targeting/PNPResult.h"
|
|
||||||
#include "photon/targeting/PhotonPipelineResult.h"
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
#include "photon/targeting/PhotonTrackedTarget.h"
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
namespace photon {
|
namespace photon {
|
||||||
enum PoseStrategy {
|
enum PoseStrategy {
|
||||||
|
|||||||
@@ -28,9 +28,9 @@
|
|||||||
|
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
#include "photon/targeting/MultiTargetPNPResult.h"
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
#include "photon/targeting/PNPResult.h"
|
|
||||||
#include "photon/targeting/PhotonPipelineResult.h"
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
#include "photon/targeting/PhotonTrackedTarget.h"
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
namespace photon {
|
namespace photon {
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@
|
|||||||
|
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
#include "photon/targeting/MultiTargetPNPResult.h"
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
#include "photon/targeting/PNPResult.h"
|
|
||||||
#include "photon/targeting/PhotonPipelineResult.h"
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
#include "photon/targeting/PhotonTrackedTarget.h"
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
namespace photon {
|
namespace photon {
|
||||||
class PhotonUtils {
|
class PhotonUtils {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class PhotonCameraSim {
|
|||||||
|
|
||||||
void SubmitProcessedFrame(const PhotonPipelineResult& result);
|
void SubmitProcessedFrame(const PhotonPipelineResult& result);
|
||||||
void SubmitProcessedFrame(const PhotonPipelineResult& result,
|
void SubmitProcessedFrame(const PhotonPipelineResult& result,
|
||||||
uint64_t recieveTimestamp);
|
uint64_t ReceiveTimestamp);
|
||||||
|
|
||||||
SimCameraProperties prop;
|
SimCameraProperties prop;
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,11 @@ public class OpenCVTest {
|
|||||||
prop.getIntrinsics(), prop.getDistCoeffs(), camRt, target.getFieldVertices());
|
prop.getIntrinsics(), prop.getDistCoeffs(), camRt, target.getFieldVertices());
|
||||||
var pnpSim =
|
var pnpSim =
|
||||||
OpenCVHelp.solvePNP_SQUARE(
|
OpenCVHelp.solvePNP_SQUARE(
|
||||||
prop.getIntrinsics(), prop.getDistCoeffs(), target.getModel().vertices, targetCorners);
|
prop.getIntrinsics(),
|
||||||
|
prop.getDistCoeffs(),
|
||||||
|
target.getModel().vertices,
|
||||||
|
targetCorners)
|
||||||
|
.get();
|
||||||
|
|
||||||
// check solvePNP estimation accuracy
|
// check solvePNP estimation accuracy
|
||||||
assertSame(relTarget.getRotation(), pnpSim.best.getRotation());
|
assertSame(relTarget.getRotation(), pnpSim.best.getRotation());
|
||||||
@@ -212,7 +216,11 @@ public class OpenCVTest {
|
|||||||
prop.getIntrinsics(), prop.getDistCoeffs(), camRt, target.getFieldVertices());
|
prop.getIntrinsics(), prop.getDistCoeffs(), camRt, target.getFieldVertices());
|
||||||
var pnpSim =
|
var pnpSim =
|
||||||
OpenCVHelp.solvePNP_SQPNP(
|
OpenCVHelp.solvePNP_SQPNP(
|
||||||
prop.getIntrinsics(), prop.getDistCoeffs(), target.getModel().vertices, targetCorners);
|
prop.getIntrinsics(),
|
||||||
|
prop.getDistCoeffs(),
|
||||||
|
target.getModel().vertices,
|
||||||
|
targetCorners)
|
||||||
|
.get();
|
||||||
|
|
||||||
// check solvePNP estimation accuracy
|
// check solvePNP estimation accuracy
|
||||||
assertSame(relTarget.getRotation(), pnpSim.best.getRotation());
|
assertSame(relTarget.getRotation(), pnpSim.best.getRotation());
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ class PhotonCameraTest {
|
|||||||
var packet = new Packet(1);
|
var packet = new Packet(1);
|
||||||
var ret = new PhotonPipelineResult();
|
var ret = new PhotonPipelineResult();
|
||||||
packet.setData(new byte[0]);
|
packet.setData(new byte[0]);
|
||||||
if (packet.getSize() < 1) {
|
PhotonPipelineResult.photonStruct.pack(packet, ret);
|
||||||
return;
|
|
||||||
}
|
|
||||||
PhotonPipelineResult.serde.pack(packet, ret);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (11 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (11 * 1e6));
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(aprilTags, PoseStrategy.LOWEST_AMBIGUITY, new Transform3d());
|
new PhotonPoseEstimator(aprilTags, PoseStrategy.LOWEST_AMBIGUITY, new Transform3d());
|
||||||
@@ -217,7 +217,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
|
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (4 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (4 * 1e6));
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(
|
new PhotonPoseEstimator(
|
||||||
@@ -306,7 +306,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (17 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (17 * 1e6));
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(
|
new PhotonPoseEstimator(
|
||||||
@@ -396,7 +396,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (1 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (1 * 1e6));
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(
|
new PhotonPoseEstimator(
|
||||||
@@ -478,7 +478,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (7 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (7 * 1e6));
|
||||||
|
|
||||||
estimatedPose = estimator.update(cameraOne.result);
|
estimatedPose = estimator.update(cameraOne.result);
|
||||||
pose = estimatedPose.get().estimatedPose;
|
pose = estimatedPose.get().estimatedPose;
|
||||||
@@ -519,7 +519,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8)))));
|
new TargetCorner(7, 8)))));
|
||||||
result.setRecieveTimestampMicros((long) (20 * 1e6));
|
result.setReceiveTimestampMicros((long) (20 * 1e6));
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(
|
new PhotonPoseEstimator(
|
||||||
@@ -529,7 +529,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
|
|
||||||
// Empty result, expect empty result
|
// Empty result, expect empty result
|
||||||
cameraOne.result = new PhotonPipelineResult();
|
cameraOne.result = new PhotonPipelineResult();
|
||||||
cameraOne.result.setRecieveTimestampMicros((long) (1 * 1e6));
|
cameraOne.result.setReceiveTimestampMicros((long) (1 * 1e6));
|
||||||
Optional<EstimatedRobotPose> estimatedPose = estimator.update(cameraOne.result);
|
Optional<EstimatedRobotPose> estimatedPose = estimator.update(cameraOne.result);
|
||||||
assertFalse(estimatedPose.isPresent());
|
assertFalse(estimatedPose.isPresent());
|
||||||
|
|
||||||
@@ -629,7 +629,7 @@ class PhotonPoseEstimatorTest {
|
|||||||
new TargetCorner(3, 4),
|
new TargetCorner(3, 4),
|
||||||
new TargetCorner(5, 6),
|
new TargetCorner(5, 6),
|
||||||
new TargetCorner(7, 8))))); // 3 3 3 ambig .4
|
new TargetCorner(7, 8))))); // 3 3 3 ambig .4
|
||||||
cameraOne.result.setRecieveTimestampMicros(20 * 1000000);
|
cameraOne.result.setReceiveTimestampMicros(20 * 1000000);
|
||||||
|
|
||||||
PhotonPoseEstimator estimator =
|
PhotonPoseEstimator estimator =
|
||||||
new PhotonPoseEstimator(
|
new PhotonPoseEstimator(
|
||||||
|
|||||||
@@ -481,11 +481,12 @@ class VisionSystemSimTest {
|
|||||||
visionSysSim.update(robotPose);
|
visionSysSim.update(robotPose);
|
||||||
var results =
|
var results =
|
||||||
VisionEstimation.estimateCamPosePNP(
|
VisionEstimation.estimateCamPosePNP(
|
||||||
camera.getCameraMatrix().get(),
|
camera.getCameraMatrix().get(),
|
||||||
camera.getDistCoeffs().get(),
|
camera.getDistCoeffs().get(),
|
||||||
camera.getLatestResult().getTargets(),
|
camera.getLatestResult().getTargets(),
|
||||||
layout,
|
layout,
|
||||||
TargetModel.kAprilTag16h5);
|
TargetModel.kAprilTag16h5)
|
||||||
|
.get();
|
||||||
Pose3d pose = new Pose3d().plus(results.best);
|
Pose3d pose = new Pose3d().plus(results.best);
|
||||||
assertEquals(5, pose.getX(), .01);
|
assertEquals(5, pose.getX(), .01);
|
||||||
assertEquals(1, pose.getY(), .01);
|
assertEquals(1, pose.getY(), .01);
|
||||||
@@ -500,11 +501,12 @@ class VisionSystemSimTest {
|
|||||||
visionSysSim.update(robotPose);
|
visionSysSim.update(robotPose);
|
||||||
results =
|
results =
|
||||||
VisionEstimation.estimateCamPosePNP(
|
VisionEstimation.estimateCamPosePNP(
|
||||||
camera.getCameraMatrix().get(),
|
camera.getCameraMatrix().get(),
|
||||||
camera.getDistCoeffs().get(),
|
camera.getDistCoeffs().get(),
|
||||||
camera.getLatestResult().getTargets(),
|
camera.getLatestResult().getTargets(),
|
||||||
layout,
|
layout,
|
||||||
TargetModel.kAprilTag16h5);
|
TargetModel.kAprilTag16h5)
|
||||||
|
.get();
|
||||||
pose = new Pose3d().plus(results.best);
|
pose = new Pose3d().plus(results.best);
|
||||||
assertEquals(5, pose.getX(), .01);
|
assertEquals(5, pose.getX(), .01);
|
||||||
assertEquals(1, pose.getY(), .01);
|
assertEquals(1, pose.getY(), .01);
|
||||||
|
|||||||
@@ -39,9 +39,9 @@
|
|||||||
#include "photon/PhotonPoseEstimator.h"
|
#include "photon/PhotonPoseEstimator.h"
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
#include "photon/targeting/MultiTargetPNPResult.h"
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
#include "photon/targeting/PNPResult.h"
|
|
||||||
#include "photon/targeting/PhotonPipelineResult.h"
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
#include "photon/targeting/PhotonTrackedTarget.h"
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
static std::vector<frc::AprilTag> tags = {
|
static std::vector<frc::AprilTag> tags = {
|
||||||
{0, frc::Pose3d(units::meter_t(3), units::meter_t(3), units::meter_t(3),
|
{0, frc::Pose3d(units::meter_t(3), units::meter_t(3), units::meter_t(3),
|
||||||
@@ -51,31 +51,33 @@ static std::vector<frc::AprilTag> tags = {
|
|||||||
|
|
||||||
static frc::AprilTagFieldLayout aprilTags{tags, 54_ft, 27_ft};
|
static frc::AprilTagFieldLayout aprilTags{tags, 54_ft, 27_ft};
|
||||||
|
|
||||||
static wpi::SmallVector<std::pair<double, double>, 4> corners{
|
static std::vector<photon::TargetCorner> corners{
|
||||||
std::pair{1, 2}, std::pair{3, 4}, std::pair{5, 6}, std::pair{7, 8}};
|
photon::TargetCorner{1., 2.}, photon::TargetCorner{3., 4.},
|
||||||
static std::vector<std::pair<double, double>> detectedCorners{
|
photon::TargetCorner{5., 6.}, photon::TargetCorner{7., 8.}};
|
||||||
std::pair{1, 2}, std::pair{3, 4}, std::pair{5, 6}, std::pair{7, 8}};
|
static std::vector<photon::TargetCorner> detectedCorners{
|
||||||
|
photon::TargetCorner{1., 2.}, photon::TargetCorner{3., 4.},
|
||||||
|
photon::TargetCorner{5., 6.}, photon::TargetCorner{7., 8.}};
|
||||||
|
|
||||||
TEST(PhotonPoseEstimatorTest, LowestAmbiguityStrategy) {
|
TEST(PhotonPoseEstimatorTest, LowestAmbiguityStrategy) {
|
||||||
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 0, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
||||||
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
||||||
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(4_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(4_m, 2_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(4_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(4_m, 2_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
||||||
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
frc::Rotation3d(1_rad, 2_rad, 3_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
frc::Transform3d(frc::Translation3d(1_m, 2_m, 3_m),
|
||||||
@@ -83,8 +85,9 @@ TEST(PhotonPoseEstimatorTest, LowestAmbiguityStrategy) {
|
|||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.test = true;
|
cameraOne.test = true;
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(11));
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(11));
|
||||||
|
|
||||||
photon::PhotonPoseEstimator estimator(aprilTags, photon::LOWEST_AMBIGUITY,
|
photon::PhotonPoseEstimator estimator(aprilTags, photon::LOWEST_AMBIGUITY,
|
||||||
frc::Transform3d{});
|
frc::Transform3d{});
|
||||||
@@ -93,6 +96,7 @@ TEST(PhotonPoseEstimatorTest, LowestAmbiguityStrategy) {
|
|||||||
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
||||||
estimatedPose = estimator.Update(result);
|
estimatedPose = estimator.Update(result);
|
||||||
}
|
}
|
||||||
|
ASSERT_TRUE(estimatedPose);
|
||||||
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
||||||
|
|
||||||
EXPECT_NEAR(11, units::unit_cast<double>(estimatedPose.value().timestamp),
|
EXPECT_NEAR(11, units::unit_cast<double>(estimatedPose.value().timestamp),
|
||||||
@@ -118,23 +122,23 @@ TEST(PhotonPoseEstimatorTest, ClosestToCameraHeightStrategy) {
|
|||||||
// ID 0 at 3,3,3
|
// ID 0 at 3,3,3
|
||||||
// ID 1 at 5,5,5
|
// ID 1 at 5,5,5
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 1, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(4_m, 4_m, 4_m),
|
frc::Transform3d(frc::Translation3d(4_m, 4_m, 4_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(5_m, 5_m, 5_m),
|
frc::Transform3d(frc::Translation3d(5_m, 5_m, 5_m),
|
||||||
@@ -142,8 +146,9 @@ TEST(PhotonPoseEstimatorTest, ClosestToCameraHeightStrategy) {
|
|||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.test = true;
|
cameraOne.test = true;
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(17_s);
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(17_s);
|
||||||
|
|
||||||
photon::PhotonPoseEstimator estimator(
|
photon::PhotonPoseEstimator estimator(
|
||||||
aprilTags, photon::CLOSEST_TO_CAMERA_HEIGHT, {{0_m, 0_m, 4_m}, {}});
|
aprilTags, photon::CLOSEST_TO_CAMERA_HEIGHT, {{0_m, 0_m, 4_m}, {}});
|
||||||
@@ -152,6 +157,7 @@ TEST(PhotonPoseEstimatorTest, ClosestToCameraHeightStrategy) {
|
|||||||
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
||||||
estimatedPose = estimator.Update(result);
|
estimatedPose = estimator.Update(result);
|
||||||
}
|
}
|
||||||
|
ASSERT_TRUE(estimatedPose);
|
||||||
|
|
||||||
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
||||||
|
|
||||||
@@ -165,23 +171,23 @@ TEST(PhotonPoseEstimatorTest, ClosestToCameraHeightStrategy) {
|
|||||||
TEST(PhotonPoseEstimatorTest, ClosestToReferencePoseStrategy) {
|
TEST(PhotonPoseEstimatorTest, ClosestToReferencePoseStrategy) {
|
||||||
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 1, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2.2_m, 2.2_m, 2.2_m),
|
frc::Transform3d(frc::Translation3d(2.2_m, 2.2_m, 2.2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
||||||
@@ -189,8 +195,9 @@ TEST(PhotonPoseEstimatorTest, ClosestToReferencePoseStrategy) {
|
|||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.test = true;
|
cameraOne.test = true;
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(17));
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(17));
|
||||||
|
|
||||||
photon::PhotonPoseEstimator estimator(aprilTags,
|
photon::PhotonPoseEstimator estimator(aprilTags,
|
||||||
photon::CLOSEST_TO_REFERENCE_POSE, {});
|
photon::CLOSEST_TO_REFERENCE_POSE, {});
|
||||||
@@ -202,6 +209,7 @@ TEST(PhotonPoseEstimatorTest, ClosestToReferencePoseStrategy) {
|
|||||||
estimatedPose = estimator.Update(result);
|
estimatedPose = estimator.Update(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(estimatedPose);
|
||||||
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
||||||
|
|
||||||
EXPECT_NEAR(17, units::unit_cast<double>(estimatedPose.value().timestamp),
|
EXPECT_NEAR(17, units::unit_cast<double>(estimatedPose.value().timestamp),
|
||||||
@@ -214,23 +222,23 @@ TEST(PhotonPoseEstimatorTest, ClosestToReferencePoseStrategy) {
|
|||||||
TEST(PhotonPoseEstimatorTest, ClosestToLastPose) {
|
TEST(PhotonPoseEstimatorTest, ClosestToLastPose) {
|
||||||
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 1, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2.2_m, 2.2_m, 2.2_m),
|
frc::Transform3d(frc::Translation3d(2.2_m, 2.2_m, 2.2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
||||||
@@ -238,8 +246,9 @@ TEST(PhotonPoseEstimatorTest, ClosestToLastPose) {
|
|||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.test = true;
|
cameraOne.test = true;
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(17));
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(17));
|
||||||
|
|
||||||
photon::PhotonPoseEstimator estimator(aprilTags, photon::CLOSEST_TO_LAST_POSE,
|
photon::PhotonPoseEstimator estimator(aprilTags, photon::CLOSEST_TO_LAST_POSE,
|
||||||
{});
|
{});
|
||||||
@@ -254,31 +263,32 @@ TEST(PhotonPoseEstimatorTest, ClosestToLastPose) {
|
|||||||
ASSERT_TRUE(estimatedPose);
|
ASSERT_TRUE(estimatedPose);
|
||||||
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targetsThree{
|
std::vector<photon::PhotonTrackedTarget> targetsThree{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 1, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 0, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2.1_m, 1.9_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2.1_m, 1.9_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2.4_m, 2.4_m, 2.2_m),
|
frc::Transform3d(frc::Translation3d(2.4_m, 2.4_m, 2.2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 1_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 1_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targetsThree}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(21));
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targetsThree, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(21));
|
||||||
|
|
||||||
// std::optional<photon::EstimatedRobotPose> estimatedPose;
|
// std::optional<photon::EstimatedRobotPose> estimatedPose;
|
||||||
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
||||||
@@ -298,23 +308,23 @@ TEST(PhotonPoseEstimatorTest, ClosestToLastPose) {
|
|||||||
TEST(PhotonPoseEstimatorTest, AverageBestPoses) {
|
TEST(PhotonPoseEstimatorTest, AverageBestPoses) {
|
||||||
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
photon::PhotonCamera cameraOne = photon::PhotonCamera("test");
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 0, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
||||||
@@ -322,8 +332,9 @@ TEST(PhotonPoseEstimatorTest, AverageBestPoses) {
|
|||||||
0.4, corners, detectedCorners}};
|
0.4, corners, detectedCorners}};
|
||||||
|
|
||||||
cameraOne.test = true;
|
cameraOne.test = true;
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(15));
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(15));
|
||||||
|
|
||||||
photon::PhotonPoseEstimator estimator(aprilTags, photon::AVERAGE_BEST_TARGETS,
|
photon::PhotonPoseEstimator estimator(aprilTags, photon::AVERAGE_BEST_TARGETS,
|
||||||
{});
|
{});
|
||||||
@@ -333,6 +344,7 @@ TEST(PhotonPoseEstimatorTest, AverageBestPoses) {
|
|||||||
estimatedPose = estimator.Update(result);
|
estimatedPose = estimator.Update(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(estimatedPose);
|
||||||
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
frc::Pose3d pose = estimatedPose.value().estimatedPose;
|
||||||
|
|
||||||
EXPECT_NEAR(15.0, units::unit_cast<double>(estimatedPose.value().timestamp),
|
EXPECT_NEAR(15.0, units::unit_cast<double>(estimatedPose.value().timestamp),
|
||||||
@@ -345,23 +357,23 @@ TEST(PhotonPoseEstimatorTest, AverageBestPoses) {
|
|||||||
TEST(PhotonPoseEstimatorTest, PoseCache) {
|
TEST(PhotonPoseEstimatorTest, PoseCache) {
|
||||||
photon::PhotonCamera cameraOne = photon::PhotonCamera("test2");
|
photon::PhotonCamera cameraOne = photon::PhotonCamera("test2");
|
||||||
|
|
||||||
wpi::SmallVector<photon::PhotonTrackedTarget, 3> targets{
|
std::vector<photon::PhotonTrackedTarget> targets{
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.0, 4.0, 0, -1, -1,
|
3.0, -4.0, 9.0, 4.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
frc::Transform3d(frc::Translation3d(2_m, 2_m, 2_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
frc::Transform3d(frc::Translation3d(1_m, 1_m, 1_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.7, corners, detectedCorners},
|
0.7, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
3.0, -4.0, 9.1, 6.7, 1, -1, -1,
|
3.0, -4.0, 9.1, 6.7, 1, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
frc::Transform3d(frc::Translation3d(3_m, 3_m, 3_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
0.3, corners, detectedCorners},
|
0.3, corners, detectedCorners},
|
||||||
photon::PhotonTrackedTarget{
|
photon::PhotonTrackedTarget{
|
||||||
9.0, -2.0, 19.0, 3.0, 0, -1, -1,
|
9.0, -2.0, 19.0, 3.0, 0, -1, -1.f,
|
||||||
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
frc::Transform3d(frc::Translation3d(0_m, 0_m, 0_m),
|
||||||
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
frc::Rotation3d(0_rad, 0_rad, 0_rad)),
|
||||||
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
frc::Transform3d(frc::Translation3d(2_m, 1.9_m, 2.1_m),
|
||||||
@@ -374,8 +386,10 @@ TEST(PhotonPoseEstimatorTest, PoseCache) {
|
|||||||
{});
|
{});
|
||||||
|
|
||||||
// empty input, expect empty out
|
// empty input, expect empty out
|
||||||
cameraOne.testResult = {{0, 0_s, 2_ms, {}}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(1));
|
photon::PhotonPipelineMetadata{0, 0, 2000},
|
||||||
|
std::vector<photon::PhotonTrackedTarget>{}, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(1));
|
||||||
|
|
||||||
std::optional<photon::EstimatedRobotPose> estimatedPose;
|
std::optional<photon::EstimatedRobotPose> estimatedPose;
|
||||||
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
||||||
@@ -385,14 +399,15 @@ TEST(PhotonPoseEstimatorTest, PoseCache) {
|
|||||||
EXPECT_FALSE(estimatedPose);
|
EXPECT_FALSE(estimatedPose);
|
||||||
|
|
||||||
// Set result, and update -- expect present and timestamp to be 15
|
// Set result, and update -- expect present and timestamp to be 15
|
||||||
cameraOne.testResult = {{0, 0_s, 3_ms, targets}};
|
cameraOne.testResult = {photon::PhotonPipelineResult{
|
||||||
cameraOne.testResult[0].SetRecieveTimestamp(units::second_t(15));
|
photon::PhotonPipelineMetadata{0, 0, 3000}, targets, std::nullopt}};
|
||||||
|
cameraOne.testResult[0].SetReceiveTimestamp(units::second_t(15));
|
||||||
|
|
||||||
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
for (const auto& result : cameraOne.GetAllUnreadResults()) {
|
||||||
estimatedPose = estimator.Update(result);
|
estimatedPose = estimator.Update(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_TRUE(estimatedPose);
|
ASSERT_TRUE(estimatedPose);
|
||||||
EXPECT_NEAR((15_s - 3_ms).to<double>(),
|
EXPECT_NEAR((15_s - 3_ms).to<double>(),
|
||||||
estimatedPose.value().timestamp.to<double>(), 1e-6);
|
estimatedPose.value().timestamp.to<double>(), 1e-6);
|
||||||
|
|
||||||
@@ -403,3 +418,15 @@ TEST(PhotonPoseEstimatorTest, PoseCache) {
|
|||||||
|
|
||||||
EXPECT_FALSE(estimatedPose);
|
EXPECT_FALSE(estimatedPose);
|
||||||
}
|
}
|
||||||
|
TEST(PhotonPoseEstimatorTest, CopyResult) {
|
||||||
|
std::vector<photon::PhotonTrackedTarget> targets{};
|
||||||
|
|
||||||
|
auto testResult = photon::PhotonPipelineResult{
|
||||||
|
photon::PhotonPipelineMetadata{0, 0, 2000}, targets, std::nullopt};
|
||||||
|
testResult.SetReceiveTimestamp(units::second_t(11));
|
||||||
|
|
||||||
|
auto test2 = testResult;
|
||||||
|
|
||||||
|
EXPECT_NEAR(testResult.GetTimestamp().to<double>(),
|
||||||
|
test2.GetTimestamp().to<double>(), 0.001);
|
||||||
|
}
|
||||||
|
|||||||
@@ -439,9 +439,10 @@ TEST_F(VisionSystemSimTest, TestPoseEstimation) {
|
|||||||
for (photon::PhotonTrackedTarget tar : targetSpan) {
|
for (photon::PhotonTrackedTarget tar : targetSpan) {
|
||||||
targets.push_back(tar);
|
targets.push_back(tar);
|
||||||
}
|
}
|
||||||
photon::PNPResult results = photon::VisionEstimation::EstimateCamPosePNP(
|
auto results = photon::VisionEstimation::EstimateCamPosePNP(
|
||||||
camEigen, distEigen, targets, layout, photon::kAprilTag16h5);
|
camEigen, distEigen, targets, layout, photon::kAprilTag16h5);
|
||||||
frc::Pose3d pose = frc::Pose3d{} + results.best;
|
ASSERT_TRUE(results);
|
||||||
|
frc::Pose3d pose = frc::Pose3d{} + results->best;
|
||||||
ASSERT_NEAR(5, pose.X().to<double>(), 0.01);
|
ASSERT_NEAR(5, pose.X().to<double>(), 0.01);
|
||||||
ASSERT_NEAR(1, pose.Y().to<double>(), 0.01);
|
ASSERT_NEAR(1, pose.Y().to<double>(), 0.01);
|
||||||
ASSERT_NEAR(0, pose.Z().to<double>(), 0.01);
|
ASSERT_NEAR(0, pose.Z().to<double>(), 0.01);
|
||||||
@@ -460,11 +461,12 @@ TEST_F(VisionSystemSimTest, TestPoseEstimation) {
|
|||||||
for (photon::PhotonTrackedTarget tar : targetSpan2) {
|
for (photon::PhotonTrackedTarget tar : targetSpan2) {
|
||||||
targets2.push_back(tar);
|
targets2.push_back(tar);
|
||||||
}
|
}
|
||||||
photon::PNPResult results2 = photon::VisionEstimation::EstimateCamPosePNP(
|
auto results2 = photon::VisionEstimation::EstimateCamPosePNP(
|
||||||
camEigen, distEigen, targets2, layout, photon::kAprilTag16h5);
|
camEigen, distEigen, targets2, layout, photon::kAprilTag16h5);
|
||||||
frc::Pose3d pose2 = frc::Pose3d{} + results2.best;
|
ASSERT_TRUE(results2);
|
||||||
ASSERT_NEAR(5, pose2.X().to<double>(), 0.01);
|
frc::Pose3d pose2 = frc::Pose3d{} + results2->best;
|
||||||
ASSERT_NEAR(1, pose2.Y().to<double>(), 0.01);
|
ASSERT_NEAR(robotPose.X().to<double>(), pose2.X().to<double>(), 0.01);
|
||||||
|
ASSERT_NEAR(robotPose.Y().to<double>(), pose2.Y().to<double>(), 0.01);
|
||||||
ASSERT_NEAR(0, pose2.Z().to<double>(), 0.01);
|
ASSERT_NEAR(0, pose2.Z().to<double>(), 0.01);
|
||||||
ASSERT_NEAR(units::degree_t{5}.convert<units::radians>().to<double>(),
|
ASSERT_NEAR(units::degree_t{5}.convert<units::radians>().to<double>(),
|
||||||
pose2.Rotation().Z().to<double>(), 0.01);
|
pose2.Rotation().Z().to<double>(), 0.01);
|
||||||
|
|||||||
24
photon-serde/README.md
Normal file
24
photon-serde/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Photon Serde Autocode
|
||||||
|
|
||||||
|
Like Rosmsg. But worse.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- As fast as possible (only slightly slower than packed structs, ideally)
|
||||||
|
- Support for variable length arrays and optional types
|
||||||
|
- Allow deserialization into user-defined, possibly nested, types. See [ResultList](src/targeting/resultlist.h) for an example of this.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
The code for a single type is split across 3 files. Let's look at PnpResult:
|
||||||
|
- [The struct definition](src/struct/pnpresult_struct.h): This is the data the object holds. Auto-generated. The data this object holds can be primitives or other, fully-deserialized types (like Vec2)
|
||||||
|
- [The user class](src/targeting/pnpresult_struct.h): This is the fully-deserialized PnpResult type. This contains extra functions users might need to expose like `Amgiguity`, or other computed helper things.
|
||||||
|
- [The serde interface](src/serde/pnpresult_struct.h): This is a template specilization for converting the user class to/from bytes
|
||||||
|
|
||||||
|
## Prior art
|
||||||
|
|
||||||
|
- Protobuf: slow on embedded platforms (at least quickbuf is)
|
||||||
|
- Wpi's struct: no VLAs/optionals
|
||||||
|
- Rosmsg: I'm not using ros, but I'm stealing their message hash idea
|
||||||
314
photon-serde/generate_messages.py
Normal file
314
photon-serde/generate_messages.py
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import copy
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, TypedDict, cast
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
|
||||||
|
class SerdeField(TypedDict):
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
# optional extra args
|
||||||
|
optional: bool
|
||||||
|
vla: bool
|
||||||
|
|
||||||
|
|
||||||
|
class MessageType(TypedDict):
|
||||||
|
name: str
|
||||||
|
fields: List[SerdeField]
|
||||||
|
# will be 'shim' if shimmed, and the shims will be set
|
||||||
|
shimmed: bool
|
||||||
|
java_decode_shim: str
|
||||||
|
java_encode_shim: str
|
||||||
|
# C++ helpers
|
||||||
|
cpp_include: str
|
||||||
|
# python shim types
|
||||||
|
python_decode_shim: str
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_to_dict(path: str):
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
yaml_file_path = os.path.join(script_dir, path)
|
||||||
|
|
||||||
|
with open(yaml_file_path, "r") as file:
|
||||||
|
file_dict: dict = yaml.safe_load(file)
|
||||||
|
|
||||||
|
return file_dict
|
||||||
|
|
||||||
|
|
||||||
|
data_types = yaml_to_dict("message_data_types.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
# Helper to check if we need to use our own decoder
|
||||||
|
def is_intrinsic_type(type_str: str):
|
||||||
|
ret = type_str in data_types.keys()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Deal with shimmed types
|
||||||
|
def get_shimmed_filter(message_db):
|
||||||
|
def is_shimmed(message_name: str):
|
||||||
|
# We don't (yet) support shimming intrinsic types
|
||||||
|
if is_intrinsic_type(message_name):
|
||||||
|
return False
|
||||||
|
|
||||||
|
message = get_message_by_name(message_db, message_name)
|
||||||
|
return "shimmed" in message and message["shimmed"] == True
|
||||||
|
|
||||||
|
return is_shimmed
|
||||||
|
|
||||||
|
|
||||||
|
def get_qualified_cpp_name(
|
||||||
|
message_db: List[MessageType], data_types, field: SerdeField
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get the full name of the type encoded. Eg:
|
||||||
|
std::optional<photon::TargetCorner>
|
||||||
|
std::array<frc::Transform3d>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if get_shimmed_filter(message_db)(field["type"]):
|
||||||
|
base_type = get_message_by_name(message_db, field["type"])["cpp_type"]
|
||||||
|
else:
|
||||||
|
base_type = data_types[field["type"]]["cpp_type"]
|
||||||
|
|
||||||
|
if "optional" in field and field["optional"] == True:
|
||||||
|
typestr = f"std::optional<{base_type}>"
|
||||||
|
elif "vla" in field and field["vla"] == True:
|
||||||
|
typestr = f"std::vector<{base_type}>"
|
||||||
|
else:
|
||||||
|
typestr = base_type
|
||||||
|
|
||||||
|
return typestr
|
||||||
|
|
||||||
|
|
||||||
|
def get_message_by_name(message_db: List[MessageType], message_name: str):
|
||||||
|
try:
|
||||||
|
return next(
|
||||||
|
message for message in message_db if message["name"] == message_name
|
||||||
|
)
|
||||||
|
except StopIteration as e:
|
||||||
|
raise Exception("Could not find " + message_name) from e
|
||||||
|
|
||||||
|
|
||||||
|
def get_field_by_name(message: MessageType, field_name: str):
|
||||||
|
return next(f for f in message["fields"] if f["name"] == field_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_message_hash(message_db: List[MessageType], message: MessageType):
|
||||||
|
"""
|
||||||
|
Calculate a unique message hash via MD5 sum. This is a very similar approach to rosmsg, documented:
|
||||||
|
http://wiki.ros.org/ROS/Technical%20Overview#Message_serialization_and_msg_MD5_sums
|
||||||
|
|
||||||
|
For non-intrinsic (user-defined) types, replace its type-string with the md5sum of the submessage definition
|
||||||
|
"""
|
||||||
|
|
||||||
|
# replace the non-intrinsic typename with its hash
|
||||||
|
modified_message = copy.deepcopy(message)
|
||||||
|
fields_to_hash = [
|
||||||
|
field
|
||||||
|
for field in modified_message["fields"]
|
||||||
|
if not is_intrinsic_type(field["type"])
|
||||||
|
]
|
||||||
|
|
||||||
|
for field in fields_to_hash:
|
||||||
|
sub_message = get_message_by_name(message_db, field["type"])
|
||||||
|
subhash = get_message_hash(message_db, sub_message)
|
||||||
|
|
||||||
|
# change the type to be our new md5sum
|
||||||
|
field["type"] = subhash.hexdigest()
|
||||||
|
|
||||||
|
# base case: message is all intrinsic types
|
||||||
|
# Hash a comments-stripped version for message integrity checking
|
||||||
|
cleaned_yaml = yaml.dump(modified_message, default_flow_style=False).strip()
|
||||||
|
message_hash = hashlib.md5(cleaned_yaml.encode("ascii"))
|
||||||
|
return message_hash
|
||||||
|
|
||||||
|
|
||||||
|
def get_includes(db, message: MessageType) -> str:
|
||||||
|
includes = []
|
||||||
|
for field in message["fields"]:
|
||||||
|
if not is_intrinsic_type(field["type"]):
|
||||||
|
field_msg = get_message_by_name(db, field["type"])
|
||||||
|
|
||||||
|
if "shimmed" in field_msg and field_msg["shimmed"] == True:
|
||||||
|
includes.append(field_msg["cpp_include"])
|
||||||
|
else:
|
||||||
|
# must be a photon type.
|
||||||
|
includes.append(f"\"photon/targeting/{field_msg['name']}.h\"")
|
||||||
|
|
||||||
|
if "optional" in field and field["optional"] == True:
|
||||||
|
includes.append("<optional>")
|
||||||
|
if "vla" in field and field["vla"] == True:
|
||||||
|
includes.append("<vector>")
|
||||||
|
|
||||||
|
# stdint types
|
||||||
|
includes.append("<stdint.h>")
|
||||||
|
|
||||||
|
return sorted(set(includes))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_yaml():
|
||||||
|
Path(__file__).resolve().parent
|
||||||
|
config = yaml_to_dict("messages.yaml")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_struct_schema_str(message: MessageType):
|
||||||
|
ret = ""
|
||||||
|
|
||||||
|
for field in message["fields"]:
|
||||||
|
typestr = field["type"]
|
||||||
|
if "optional" in field and field["optional"] == True:
|
||||||
|
typestr += "?"
|
||||||
|
if "vla" in field and field["vla"] == True:
|
||||||
|
typestr += "[?]"
|
||||||
|
ret += f"{typestr} {field['name']};"
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def generate_photon_messages(cpp_java_root, py_root, template_root):
|
||||||
|
messages = parse_yaml()
|
||||||
|
|
||||||
|
env = Environment(
|
||||||
|
loader=FileSystemLoader(str(template_root)),
|
||||||
|
# autoescape=False,
|
||||||
|
# keep_trailing_newline=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
env.filters["is_intrinsic"] = is_intrinsic_type
|
||||||
|
env.filters["is_shimmed"] = get_shimmed_filter(messages)
|
||||||
|
|
||||||
|
# add our custom types
|
||||||
|
extended_data_types = data_types.copy()
|
||||||
|
for message in messages:
|
||||||
|
name = message["name"]
|
||||||
|
extended_data_types[name] = {
|
||||||
|
"len": -1,
|
||||||
|
"java_type": name,
|
||||||
|
"cpp_type": "photon::" + name,
|
||||||
|
}
|
||||||
|
|
||||||
|
java_output_dir = Path(cpp_java_root) / "main/java/org/photonvision/struct"
|
||||||
|
java_output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
cpp_serde_header_dir = Path(cpp_java_root) / "main/native/include/photon/serde/"
|
||||||
|
cpp_serde_header_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
cpp_serde_source_dir = Path(cpp_java_root) / "main/native/cpp/photon/serde/"
|
||||||
|
cpp_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
cpp_struct_header_dir = Path(cpp_java_root) / "main/native/include/photon/struct/"
|
||||||
|
cpp_struct_header_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
py_serde_source_dir = Path(py_root)
|
||||||
|
py_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
env.filters["get_qualified_name"] = lambda field: get_qualified_cpp_name(
|
||||||
|
messages, extended_data_types, field
|
||||||
|
)
|
||||||
|
|
||||||
|
for message in messages:
|
||||||
|
# don't generate shimmed types
|
||||||
|
if get_shimmed_filter(messages)(message["name"]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
message = cast(MessageType, message)
|
||||||
|
|
||||||
|
java_name = f"{message['name']}Serde.java"
|
||||||
|
cpp_serde_header_name = f"{message['name']}Serde.h"
|
||||||
|
cpp_serde_source_name = f"{message['name']}Serde.cpp"
|
||||||
|
cpp_struct_header_name = f"{message['name']}Struct.h"
|
||||||
|
py_name = f"{message['name']}Serde.py"
|
||||||
|
|
||||||
|
java_template = env.get_template("Message.java.jinja")
|
||||||
|
|
||||||
|
cpp_serde_header_template = env.get_template("ThingSerde.h.jinja")
|
||||||
|
cpp_serde_source_template = env.get_template("ThingSerde.cpp.jinja")
|
||||||
|
cpp_struct_header_template = env.get_template("ThingStruct.h.jinja")
|
||||||
|
|
||||||
|
py_template = env.get_template("ThingSerde.py.jinja")
|
||||||
|
|
||||||
|
message_hash = get_message_hash(messages, message)
|
||||||
|
|
||||||
|
for output_name, template, output_folder in [
|
||||||
|
[java_name, java_template, java_output_dir],
|
||||||
|
[cpp_serde_header_name, cpp_serde_header_template, cpp_serde_header_dir],
|
||||||
|
[cpp_serde_source_name, cpp_serde_source_template, cpp_serde_source_dir],
|
||||||
|
[cpp_struct_header_name, cpp_struct_header_template, cpp_struct_header_dir],
|
||||||
|
[py_name, py_template, py_serde_source_dir],
|
||||||
|
]:
|
||||||
|
# Hack in our message getter
|
||||||
|
template.globals["get_message_by_name"] = lambda name: get_message_by_name(
|
||||||
|
messages, name
|
||||||
|
)
|
||||||
|
|
||||||
|
output_file = output_folder / output_name
|
||||||
|
output_file.write_text(
|
||||||
|
template.render(
|
||||||
|
message,
|
||||||
|
type_map=extended_data_types,
|
||||||
|
message_fmt=get_struct_schema_str(message),
|
||||||
|
message_hash=message_hash.hexdigest(),
|
||||||
|
cpp_includes=get_includes(messages, message),
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
script_path = Path(__file__).resolve()
|
||||||
|
dirname = script_path.parent
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--cpp_java_output_dir",
|
||||||
|
help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script",
|
||||||
|
default=dirname.parent / "photon-targeting/src/generated",
|
||||||
|
type=Path,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--py_output_dir",
|
||||||
|
help="Optional. If set, will spit Python serde files here",
|
||||||
|
default=dirname.parent / "photon-lib/py/photonlibpy/generated",
|
||||||
|
type=Path,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--template_root",
|
||||||
|
help="Optional. If set, will use this directory as the root for the jinja templates",
|
||||||
|
default=dirname / "templates",
|
||||||
|
type=Path,
|
||||||
|
)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
generate_photon_messages(
|
||||||
|
args.cpp_java_output_dir, args.py_output_dir, args.template_root
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
||||||
33
photon-serde/message_data_types.yaml
Normal file
33
photon-serde/message_data_types.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
bool:
|
||||||
|
# length in bytes
|
||||||
|
len: 1
|
||||||
|
java_type: bool
|
||||||
|
cpp_type: bool
|
||||||
|
java_decode_method: decodeBoolean
|
||||||
|
int16:
|
||||||
|
len: 2
|
||||||
|
java_type: short
|
||||||
|
cpp_type: int16_t
|
||||||
|
java_decode_method: decodeShort
|
||||||
|
java_list_decode_method: decodeShortList
|
||||||
|
int32:
|
||||||
|
len: 4
|
||||||
|
java_type: int
|
||||||
|
cpp_type: int32_t
|
||||||
|
java_decode_method: decodeInt
|
||||||
|
int64:
|
||||||
|
len: 8
|
||||||
|
java_type: long
|
||||||
|
cpp_type: int64_t
|
||||||
|
java_decode_method: decodeLong
|
||||||
|
float32:
|
||||||
|
len: 4
|
||||||
|
java_type: float
|
||||||
|
cpp_type: float
|
||||||
|
java_decode_method: decodeFloat
|
||||||
|
float64:
|
||||||
|
len: 8
|
||||||
|
java_type: double
|
||||||
|
cpp_type: double
|
||||||
|
java_decode_method: decodeDouble
|
||||||
90
photon-serde/messages.yaml
Normal file
90
photon-serde/messages.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
- name: PhotonPipelineMetadata
|
||||||
|
fields:
|
||||||
|
- name: sequenceID
|
||||||
|
type: int64
|
||||||
|
- name: captureTimestampMicros
|
||||||
|
type: int64
|
||||||
|
- name: publishTimestampMicros
|
||||||
|
type: int64
|
||||||
|
|
||||||
|
- name: Transform3d
|
||||||
|
shimmed: True
|
||||||
|
java_decode_shim: PacketUtils.unpackTransform3d
|
||||||
|
java_encode_shim: PacketUtils.packTransform3d
|
||||||
|
cpp_type: frc::Transform3d
|
||||||
|
cpp_include: "<frc/geometry/Transform3d.h>"
|
||||||
|
python_decode_shim: packet.decodeTransform
|
||||||
|
# shim since we expect fields to at least exist
|
||||||
|
fields: []
|
||||||
|
|
||||||
|
|
||||||
|
- name: TargetCorner
|
||||||
|
fields:
|
||||||
|
- name: x
|
||||||
|
type: float64
|
||||||
|
- name: y
|
||||||
|
type: float64
|
||||||
|
|
||||||
|
- name: PhotonTrackedTarget
|
||||||
|
fields:
|
||||||
|
- name: yaw
|
||||||
|
type: float64
|
||||||
|
- name: pitch
|
||||||
|
type: float64
|
||||||
|
- name: area
|
||||||
|
type: float64
|
||||||
|
- name: skew
|
||||||
|
type: float64
|
||||||
|
- name: fiducialId
|
||||||
|
type: int32
|
||||||
|
- name: objDetectId
|
||||||
|
type: int32
|
||||||
|
- name: objDetectConf
|
||||||
|
type: float32
|
||||||
|
- name: bestCameraToTarget
|
||||||
|
type: Transform3d
|
||||||
|
- name: altCameraToTarget
|
||||||
|
type: Transform3d
|
||||||
|
- name: poseAmbiguity
|
||||||
|
type: float64
|
||||||
|
- name: minAreaRectCorners
|
||||||
|
type: TargetCorner
|
||||||
|
vla: True
|
||||||
|
- name: detectedCorners
|
||||||
|
type: TargetCorner
|
||||||
|
vla: True
|
||||||
|
|
||||||
|
- name: PnpResult
|
||||||
|
fields:
|
||||||
|
- name: best
|
||||||
|
type: Transform3d
|
||||||
|
comment: "This is a comment"
|
||||||
|
- name: alt
|
||||||
|
type: Transform3d
|
||||||
|
- name: bestReprojErr
|
||||||
|
type: float64
|
||||||
|
- name: altReprojErr
|
||||||
|
type: float64
|
||||||
|
- name: ambiguity
|
||||||
|
type: float64
|
||||||
|
|
||||||
|
- name: MultiTargetPNPResult
|
||||||
|
fields:
|
||||||
|
- name: estimatedPose
|
||||||
|
type: PnpResult
|
||||||
|
- name: fiducialIDsUsed
|
||||||
|
type: int16
|
||||||
|
vla: True
|
||||||
|
|
||||||
|
|
||||||
|
- name: PhotonPipelineResult
|
||||||
|
fields:
|
||||||
|
- name: metadata
|
||||||
|
type: PhotonPipelineMetadata
|
||||||
|
- name: targets
|
||||||
|
type: PhotonTrackedTarget
|
||||||
|
vla: True
|
||||||
|
- name: multitagResult
|
||||||
|
type: MultiTargetPNPResult
|
||||||
|
optional: True
|
||||||
103
photon-serde/templates/Message.java.jinja
Normal file
103
photon-serde/templates/Message.java.jinja
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for {{name}}
|
||||||
|
*/
|
||||||
|
public class {{ name }}Serde implements PacketSerde<{{name}}> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "{{ message_hash }}";
|
||||||
|
public static final String MESSAGE_FORMAT = "{{ message_fmt }}";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, {{ name }} value) {
|
||||||
|
{%- for field in fields -%}
|
||||||
|
{%- if field.type | is_shimmed %}
|
||||||
|
// field is shimmed!
|
||||||
|
{{ get_message_by_name(field.type).java_encode_shim }}(packet, value.{{ field.name }});
|
||||||
|
{%- elif field.optional == True %}
|
||||||
|
// {{ field.name }} is optional! it better not be a VLA too
|
||||||
|
packet.encodeOptional(value.{{ field.name }});
|
||||||
|
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||||
|
// {{ field.name }} is a intrinsic VLA!
|
||||||
|
packet.encode(value.{{ field.name }});
|
||||||
|
{%- elif field.vla == True %}
|
||||||
|
// {{ field.name }} is a custom VLA!
|
||||||
|
packet.encodeList(value.{{ field.name }});
|
||||||
|
{%- elif field.type | is_intrinsic %}
|
||||||
|
// field {{ field.name }} is of intrinsic type {{ field.type }}
|
||||||
|
packet.encode(({{ type_map[field.type].java_type }}) value.{{ field.name }});
|
||||||
|
{%- else %}
|
||||||
|
// field {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||||
|
{{ field.type }}.photonStruct.pack(packet, value.{{ field.name }});
|
||||||
|
{%- endif %}
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor%}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public {{ name }} unpack(Packet packet) {
|
||||||
|
var ret = new {{ name }}();
|
||||||
|
{% for field in fields -%}
|
||||||
|
{%- if field.type | is_shimmed %}
|
||||||
|
// field is shimmed!
|
||||||
|
ret.{{ field.name }} = {{ get_message_by_name(field.type).java_decode_shim }}(packet);
|
||||||
|
{%- elif field.optional == True %}
|
||||||
|
// {{ field.name }} is optional! it better not be a VLA too
|
||||||
|
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct);
|
||||||
|
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||||
|
// {{ field.name }} is a custom VLA!
|
||||||
|
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct);
|
||||||
|
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||||
|
// {{ field.name }} is a custom VLA!
|
||||||
|
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List();
|
||||||
|
{%- elif field.type | is_intrinsic %}
|
||||||
|
// {{ field.name }} is of intrinsic type {{ field.type }}
|
||||||
|
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}();
|
||||||
|
{%- else %}
|
||||||
|
// {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||||
|
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet);
|
||||||
|
{%- endif %}
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor%}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
photon-serde/templates/ThingSerde.cpp.jinja
Normal file
44
photon-serde/templates/ThingSerde.cpp.jinja
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/{{ name }}Serde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<{{ name }}>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const {{ name }}& value) {
|
||||||
|
{% for field in fields -%}
|
||||||
|
packet.Pack<{{ field | get_qualified_name }}>(value.{{ field.name }});
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ name }} StructType::Unpack(Packet& packet) {
|
||||||
|
return {{ name }}{ {{ name }}_PhotonStruct{
|
||||||
|
{% for field in fields -%}
|
||||||
|
.{{ field.name}} = packet.Unpack<{{ field | get_qualified_name }}>(),
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
51
photon-serde/templates/ThingSerde.h.jinja
Normal file
51
photon-serde/templates/ThingSerde.h.jinja
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/{{ name }}.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
{% for include in cpp_includes -%}
|
||||||
|
#include {{ include }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<{{ name }}> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "{{ message_hash }}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "{{ message_fmt }}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::{{ name }} Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet, const photon::{{ name }}& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::{{ name }}>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
62
photon-serde/templates/ThingSerde.py.jinja
Normal file
62
photon-serde/templates/ThingSerde.py.jinja
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Copyright (C) Photon Vision.
|
||||||
|
###############################################################################
|
||||||
|
## This program is free software: you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation, either version 3 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||||
|
## --> DO NOT MODIFY <--
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from ..targeting import *
|
||||||
|
|
||||||
|
class {{ name }}Serde:
|
||||||
|
|
||||||
|
# Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
MESSAGE_VERSION = "{{ message_hash }}"
|
||||||
|
MESSAGE_FORMAT = "{{ message_fmt }}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack(packet: 'Packet') -> '{{ name }}':
|
||||||
|
ret = {{ name }}()
|
||||||
|
{% for field in fields -%}
|
||||||
|
{%- if field.type | is_shimmed %}
|
||||||
|
# field is shimmed!
|
||||||
|
ret.{{ field.name }} = {{ get_message_by_name(field.type).python_decode_shim }}()
|
||||||
|
{%- elif field.optional == True %}
|
||||||
|
# {{ field.name }} is optional! it better not be a VLA too
|
||||||
|
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct)
|
||||||
|
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||||
|
# {{ field.name }} is a custom VLA!
|
||||||
|
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct)
|
||||||
|
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||||
|
# {{ field.name }} is a custom VLA!
|
||||||
|
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List()
|
||||||
|
{%- elif field.type | is_intrinsic %}
|
||||||
|
# {{ field.name }} is of intrinsic type {{ field.type }}
|
||||||
|
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}()
|
||||||
|
{%- else %}
|
||||||
|
# {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||||
|
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet)
|
||||||
|
{%- endif %}
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor%}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# Hack ourselves into the base class
|
||||||
|
{{ name }}.photonStruct = {{ name }}Serde()
|
||||||
39
photon-serde/templates/ThingStruct.h.jinja
Normal file
39
photon-serde/templates/ThingStruct.h.jinja
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
{% for include in cpp_includes -%}
|
||||||
|
#include {{ include }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct {{ name }}_PhotonStruct {
|
||||||
|
{% for field in fields -%}
|
||||||
|
{{ field | get_qualified_name }} {{ field.name }};
|
||||||
|
{%- if not loop.last %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
friend bool operator==({{ name }}_PhotonStruct const&, {{ name }}_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -17,17 +17,19 @@ nativeUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"
|
||||||
|
|
||||||
model {
|
model {
|
||||||
components {
|
components {
|
||||||
"${nativeName}"(NativeLibrarySpec) {
|
"${nativeName}"(NativeLibrarySpec) {
|
||||||
sources {
|
sources {
|
||||||
cpp {
|
cpp {
|
||||||
source {
|
source {
|
||||||
srcDirs 'src/main/native/cpp', "$buildDir/generated/source/proto/main/cpp"
|
srcDirs 'src/main/native/cpp', "$buildDir/generated/source/proto/main/cpp", 'src/generated/main/native/cpp'
|
||||||
include '**/*.cpp', '**/*.cc'
|
include '**/*.cpp', '**/*.cc'
|
||||||
}
|
}
|
||||||
exportedHeaders {
|
exportedHeaders {
|
||||||
srcDirs 'src/main/native/include', "$buildDir/generated/source/proto/main/cpp"
|
srcDirs 'src/main/native/include', 'src/generated/main/native/include', "$buildDir/generated/source/proto/main/cpp", 'src/generated/main/native/include'
|
||||||
if (project.hasProperty('generatedHeaders')) {
|
if (project.hasProperty('generatedHeaders')) {
|
||||||
srcDir generatedHeaders
|
srcDir generatedHeaders
|
||||||
}
|
}
|
||||||
@@ -120,3 +122,10 @@ model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "${rootDir}/shared/javacpp/publish.gradle"
|
apply from: "${rootDir}/shared/javacpp/publish.gradle"
|
||||||
|
|
||||||
|
// Add photon serde headers to our published sources
|
||||||
|
cppHeadersZip {
|
||||||
|
from('src/generated/main/native/include') {
|
||||||
|
into '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for MultiTargetPNPResult
|
||||||
|
*/
|
||||||
|
public class MultiTargetPNPResultSerde implements PacketSerde<MultiTargetPNPResult> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "ffc1cb847deb6e796a583a5b1885496b";
|
||||||
|
public static final String MESSAGE_FORMAT = "PnpResult estimatedPose;int16[?] fiducialIDsUsed;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, MultiTargetPNPResult value) {
|
||||||
|
// field estimatedPose is of non-intrinsic type PnpResult
|
||||||
|
PnpResult.photonStruct.pack(packet, value.estimatedPose);
|
||||||
|
|
||||||
|
// fiducialIDsUsed is a intrinsic VLA!
|
||||||
|
packet.encode(value.fiducialIDsUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiTargetPNPResult unpack(Packet packet) {
|
||||||
|
var ret = new MultiTargetPNPResult();
|
||||||
|
|
||||||
|
// estimatedPose is of non-intrinsic type PnpResult
|
||||||
|
ret.estimatedPose = PnpResult.photonStruct.unpack(packet);
|
||||||
|
|
||||||
|
// fiducialIDsUsed is a custom VLA!
|
||||||
|
ret.fiducialIDsUsed = packet.decodeShortList();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for PhotonPipelineMetadata
|
||||||
|
*/
|
||||||
|
public class PhotonPipelineMetadataSerde implements PacketSerde<PhotonPipelineMetadata> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "2a7039527bda14d13028a1b9282d40a2";
|
||||||
|
public static final String MESSAGE_FORMAT = "int64 sequenceID;int64 captureTimestampMicros;int64 publishTimestampMicros;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, PhotonPipelineMetadata value) {
|
||||||
|
// field sequenceID is of intrinsic type int64
|
||||||
|
packet.encode((long) value.sequenceID);
|
||||||
|
|
||||||
|
// field captureTimestampMicros is of intrinsic type int64
|
||||||
|
packet.encode((long) value.captureTimestampMicros);
|
||||||
|
|
||||||
|
// field publishTimestampMicros is of intrinsic type int64
|
||||||
|
packet.encode((long) value.publishTimestampMicros);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhotonPipelineMetadata unpack(Packet packet) {
|
||||||
|
var ret = new PhotonPipelineMetadata();
|
||||||
|
|
||||||
|
// sequenceID is of intrinsic type int64
|
||||||
|
ret.sequenceID = packet.decodeLong();
|
||||||
|
|
||||||
|
// captureTimestampMicros is of intrinsic type int64
|
||||||
|
ret.captureTimestampMicros = packet.decodeLong();
|
||||||
|
|
||||||
|
// publishTimestampMicros is of intrinsic type int64
|
||||||
|
ret.publishTimestampMicros = packet.decodeLong();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for PhotonPipelineResult
|
||||||
|
*/
|
||||||
|
public class PhotonPipelineResultSerde implements PacketSerde<PhotonPipelineResult> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "cb3e1605048ba49325888eb797399fe2";
|
||||||
|
public static final String MESSAGE_FORMAT = "PhotonPipelineMetadata metadata;PhotonTrackedTarget[?] targets;MultiTargetPNPResult? multitagResult;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, PhotonPipelineResult value) {
|
||||||
|
// field metadata is of non-intrinsic type PhotonPipelineMetadata
|
||||||
|
PhotonPipelineMetadata.photonStruct.pack(packet, value.metadata);
|
||||||
|
|
||||||
|
// targets is a custom VLA!
|
||||||
|
packet.encodeList(value.targets);
|
||||||
|
|
||||||
|
// multitagResult is optional! it better not be a VLA too
|
||||||
|
packet.encodeOptional(value.multitagResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhotonPipelineResult unpack(Packet packet) {
|
||||||
|
var ret = new PhotonPipelineResult();
|
||||||
|
|
||||||
|
// metadata is of non-intrinsic type PhotonPipelineMetadata
|
||||||
|
ret.metadata = PhotonPipelineMetadata.photonStruct.unpack(packet);
|
||||||
|
|
||||||
|
// targets is a custom VLA!
|
||||||
|
ret.targets = packet.decodeList(PhotonTrackedTarget.photonStruct);
|
||||||
|
|
||||||
|
// multitagResult is optional! it better not be a VLA too
|
||||||
|
ret.multitagResult = packet.decodeOptional(MultiTargetPNPResult.photonStruct);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for PhotonTrackedTarget
|
||||||
|
*/
|
||||||
|
public class PhotonTrackedTargetSerde implements PacketSerde<PhotonTrackedTarget> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "8fdada56b9162f2e32bd24f0055d7b60";
|
||||||
|
public static final String MESSAGE_FORMAT = "float64 yaw;float64 pitch;float64 area;float64 skew;int32 fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d bestCameraToTarget;Transform3d altCameraToTarget;float64 poseAmbiguity;TargetCorner[?] minAreaRectCorners;TargetCorner[?] detectedCorners;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, PhotonTrackedTarget value) {
|
||||||
|
// field yaw is of intrinsic type float64
|
||||||
|
packet.encode((double) value.yaw);
|
||||||
|
|
||||||
|
// field pitch is of intrinsic type float64
|
||||||
|
packet.encode((double) value.pitch);
|
||||||
|
|
||||||
|
// field area is of intrinsic type float64
|
||||||
|
packet.encode((double) value.area);
|
||||||
|
|
||||||
|
// field skew is of intrinsic type float64
|
||||||
|
packet.encode((double) value.skew);
|
||||||
|
|
||||||
|
// field fiducialId is of intrinsic type int32
|
||||||
|
packet.encode((int) value.fiducialId);
|
||||||
|
|
||||||
|
// field objDetectId is of intrinsic type int32
|
||||||
|
packet.encode((int) value.objDetectId);
|
||||||
|
|
||||||
|
// field objDetectConf is of intrinsic type float32
|
||||||
|
packet.encode((float) value.objDetectConf);
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
PacketUtils.packTransform3d(packet, value.bestCameraToTarget);
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
PacketUtils.packTransform3d(packet, value.altCameraToTarget);
|
||||||
|
|
||||||
|
// field poseAmbiguity is of intrinsic type float64
|
||||||
|
packet.encode((double) value.poseAmbiguity);
|
||||||
|
|
||||||
|
// minAreaRectCorners is a custom VLA!
|
||||||
|
packet.encodeList(value.minAreaRectCorners);
|
||||||
|
|
||||||
|
// detectedCorners is a custom VLA!
|
||||||
|
packet.encodeList(value.detectedCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhotonTrackedTarget unpack(Packet packet) {
|
||||||
|
var ret = new PhotonTrackedTarget();
|
||||||
|
|
||||||
|
// yaw is of intrinsic type float64
|
||||||
|
ret.yaw = packet.decodeDouble();
|
||||||
|
|
||||||
|
// pitch is of intrinsic type float64
|
||||||
|
ret.pitch = packet.decodeDouble();
|
||||||
|
|
||||||
|
// area is of intrinsic type float64
|
||||||
|
ret.area = packet.decodeDouble();
|
||||||
|
|
||||||
|
// skew is of intrinsic type float64
|
||||||
|
ret.skew = packet.decodeDouble();
|
||||||
|
|
||||||
|
// fiducialId is of intrinsic type int32
|
||||||
|
ret.fiducialId = packet.decodeInt();
|
||||||
|
|
||||||
|
// objDetectId is of intrinsic type int32
|
||||||
|
ret.objDetectId = packet.decodeInt();
|
||||||
|
|
||||||
|
// objDetectConf is of intrinsic type float32
|
||||||
|
ret.objDetectConf = packet.decodeFloat();
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
ret.bestCameraToTarget = PacketUtils.unpackTransform3d(packet);
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
ret.altCameraToTarget = PacketUtils.unpackTransform3d(packet);
|
||||||
|
|
||||||
|
// poseAmbiguity is of intrinsic type float64
|
||||||
|
ret.poseAmbiguity = packet.decodeDouble();
|
||||||
|
|
||||||
|
// minAreaRectCorners is a custom VLA!
|
||||||
|
ret.minAreaRectCorners = packet.decodeList(TargetCorner.photonStruct);
|
||||||
|
|
||||||
|
// detectedCorners is a custom VLA!
|
||||||
|
ret.detectedCorners = packet.decodeList(TargetCorner.photonStruct);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for PnpResult
|
||||||
|
*/
|
||||||
|
public class PnpResultSerde implements PacketSerde<PnpResult> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "0d1f2546b00f24718e30f38d206d4491";
|
||||||
|
public static final String MESSAGE_FORMAT = "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 altReprojErr;float64 ambiguity;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, PnpResult value) {
|
||||||
|
// field is shimmed!
|
||||||
|
PacketUtils.packTransform3d(packet, value.best);
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
PacketUtils.packTransform3d(packet, value.alt);
|
||||||
|
|
||||||
|
// field bestReprojErr is of intrinsic type float64
|
||||||
|
packet.encode((double) value.bestReprojErr);
|
||||||
|
|
||||||
|
// field altReprojErr is of intrinsic type float64
|
||||||
|
packet.encode((double) value.altReprojErr);
|
||||||
|
|
||||||
|
// field ambiguity is of intrinsic type float64
|
||||||
|
packet.encode((double) value.ambiguity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PnpResult unpack(Packet packet) {
|
||||||
|
var ret = new PnpResult();
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
ret.best = PacketUtils.unpackTransform3d(packet);
|
||||||
|
|
||||||
|
// field is shimmed!
|
||||||
|
ret.alt = PacketUtils.unpackTransform3d(packet);
|
||||||
|
|
||||||
|
// bestReprojErr is of intrinsic type float64
|
||||||
|
ret.bestReprojErr = packet.decodeDouble();
|
||||||
|
|
||||||
|
// altReprojErr is of intrinsic type float64
|
||||||
|
ret.altReprojErr = packet.decodeDouble();
|
||||||
|
|
||||||
|
// ambiguity is of intrinsic type float64
|
||||||
|
ret.ambiguity = packet.decodeDouble();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||||
|
|
||||||
|
package org.photonvision.struct;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.utils.PacketUtils;
|
||||||
|
|
||||||
|
// Assume that the base class lives here and we can import it
|
||||||
|
import org.photonvision.targeting.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated serialization/deserialization helper for TargetCorner
|
||||||
|
*/
|
||||||
|
public class TargetCornerSerde implements PacketSerde<TargetCorner> {
|
||||||
|
// Message definition md5sum. See photon_packet.adoc for details
|
||||||
|
public static final String MESSAGE_VERSION = "22b1ff7551d10215af6fb3672fe4eda8";
|
||||||
|
public static final String MESSAGE_FORMAT = "float64 x;float64 y;";
|
||||||
|
|
||||||
|
public final String getTypeString() { return MESSAGE_FORMAT; }
|
||||||
|
public final String getInterfaceUUID() { return MESSAGE_VERSION; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxByteSize() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pack(Packet packet, TargetCorner value) {
|
||||||
|
// field x is of intrinsic type float64
|
||||||
|
packet.encode((double) value.x);
|
||||||
|
|
||||||
|
// field y is of intrinsic type float64
|
||||||
|
packet.encode((double) value.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TargetCorner unpack(Packet packet) {
|
||||||
|
var ret = new TargetCorner();
|
||||||
|
|
||||||
|
// x is of intrinsic type float64
|
||||||
|
ret.x = packet.decodeDouble();
|
||||||
|
|
||||||
|
// y is of intrinsic type float64
|
||||||
|
ret.y = packet.decodeDouble();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/MultiTargetPNPResultSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<MultiTargetPNPResult>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const MultiTargetPNPResult& value) {
|
||||||
|
packet.Pack<photon::PnpResult>(value.estimatedPose);
|
||||||
|
packet.Pack<std::vector<int16_t>>(value.fiducialIDsUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiTargetPNPResult StructType::Unpack(Packet& packet) {
|
||||||
|
return MultiTargetPNPResult{MultiTargetPNPResult_PhotonStruct{
|
||||||
|
.estimatedPose = packet.Unpack<photon::PnpResult>(),
|
||||||
|
.fiducialIDsUsed = packet.Unpack<std::vector<int16_t>>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/PhotonPipelineMetadataSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<PhotonPipelineMetadata>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const PhotonPipelineMetadata& value) {
|
||||||
|
packet.Pack<int64_t>(value.sequenceID);
|
||||||
|
packet.Pack<int64_t>(value.captureTimestampMicros);
|
||||||
|
packet.Pack<int64_t>(value.publishTimestampMicros);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotonPipelineMetadata StructType::Unpack(Packet& packet) {
|
||||||
|
return PhotonPipelineMetadata{PhotonPipelineMetadata_PhotonStruct{
|
||||||
|
.sequenceID = packet.Unpack<int64_t>(),
|
||||||
|
.captureTimestampMicros = packet.Unpack<int64_t>(),
|
||||||
|
.publishTimestampMicros = packet.Unpack<int64_t>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/PhotonPipelineResultSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<PhotonPipelineResult>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const PhotonPipelineResult& value) {
|
||||||
|
packet.Pack<photon::PhotonPipelineMetadata>(value.metadata);
|
||||||
|
packet.Pack<std::vector<photon::PhotonTrackedTarget>>(value.targets);
|
||||||
|
packet.Pack<std::optional<photon::MultiTargetPNPResult>>(
|
||||||
|
value.multitagResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotonPipelineResult StructType::Unpack(Packet& packet) {
|
||||||
|
return PhotonPipelineResult{PhotonPipelineResult_PhotonStruct{
|
||||||
|
.metadata = packet.Unpack<photon::PhotonPipelineMetadata>(),
|
||||||
|
.targets = packet.Unpack<std::vector<photon::PhotonTrackedTarget>>(),
|
||||||
|
.multitagResult =
|
||||||
|
packet.Unpack<std::optional<photon::MultiTargetPNPResult>>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/PhotonTrackedTargetSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<PhotonTrackedTarget>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const PhotonTrackedTarget& value) {
|
||||||
|
packet.Pack<double>(value.yaw);
|
||||||
|
packet.Pack<double>(value.pitch);
|
||||||
|
packet.Pack<double>(value.area);
|
||||||
|
packet.Pack<double>(value.skew);
|
||||||
|
packet.Pack<int32_t>(value.fiducialId);
|
||||||
|
packet.Pack<int32_t>(value.objDetectId);
|
||||||
|
packet.Pack<float>(value.objDetectConf);
|
||||||
|
packet.Pack<frc::Transform3d>(value.bestCameraToTarget);
|
||||||
|
packet.Pack<frc::Transform3d>(value.altCameraToTarget);
|
||||||
|
packet.Pack<double>(value.poseAmbiguity);
|
||||||
|
packet.Pack<std::vector<photon::TargetCorner>>(value.minAreaRectCorners);
|
||||||
|
packet.Pack<std::vector<photon::TargetCorner>>(value.detectedCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotonTrackedTarget StructType::Unpack(Packet& packet) {
|
||||||
|
return PhotonTrackedTarget{PhotonTrackedTarget_PhotonStruct{
|
||||||
|
.yaw = packet.Unpack<double>(),
|
||||||
|
.pitch = packet.Unpack<double>(),
|
||||||
|
.area = packet.Unpack<double>(),
|
||||||
|
.skew = packet.Unpack<double>(),
|
||||||
|
.fiducialId = packet.Unpack<int32_t>(),
|
||||||
|
.objDetectId = packet.Unpack<int32_t>(),
|
||||||
|
.objDetectConf = packet.Unpack<float>(),
|
||||||
|
.bestCameraToTarget = packet.Unpack<frc::Transform3d>(),
|
||||||
|
.altCameraToTarget = packet.Unpack<frc::Transform3d>(),
|
||||||
|
.poseAmbiguity = packet.Unpack<double>(),
|
||||||
|
.minAreaRectCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
|
||||||
|
.detectedCorners = packet.Unpack<std::vector<photon::TargetCorner>>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/PnpResultSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<PnpResult>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const PnpResult& value) {
|
||||||
|
packet.Pack<frc::Transform3d>(value.best);
|
||||||
|
packet.Pack<frc::Transform3d>(value.alt);
|
||||||
|
packet.Pack<double>(value.bestReprojErr);
|
||||||
|
packet.Pack<double>(value.altReprojErr);
|
||||||
|
packet.Pack<double>(value.ambiguity);
|
||||||
|
}
|
||||||
|
|
||||||
|
PnpResult StructType::Unpack(Packet& packet) {
|
||||||
|
return PnpResult{PnpResult_PhotonStruct{
|
||||||
|
.best = packet.Unpack<frc::Transform3d>(),
|
||||||
|
.alt = packet.Unpack<frc::Transform3d>(),
|
||||||
|
.bestReprojErr = packet.Unpack<double>(),
|
||||||
|
.altReprojErr = packet.Unpack<double>(),
|
||||||
|
.ambiguity = packet.Unpack<double>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include "photon/serde/TargetCornerSerde.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
using StructType = SerdeType<TargetCorner>;
|
||||||
|
|
||||||
|
void StructType::Pack(Packet& packet, const TargetCorner& value) {
|
||||||
|
packet.Pack<double>(value.x);
|
||||||
|
packet.Pack<double>(value.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetCorner StructType::Unpack(Packet& packet) {
|
||||||
|
return TargetCorner{TargetCorner_PhotonStruct{
|
||||||
|
.x = packet.Unpack<double>(),
|
||||||
|
.y = packet.Unpack<double>(),
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<MultiTargetPNPResult> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "ffc1cb847deb6e796a583a5b1885496b";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "PnpResult estimatedPose;int16[?] fiducialIDsUsed;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::MultiTargetPNPResult Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet,
|
||||||
|
const photon::MultiTargetPNPResult& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::MultiTargetPNPResult>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/PhotonPipelineMetadata.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<PhotonPipelineMetadata> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "2a7039527bda14d13028a1b9282d40a2";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "int64 sequenceID;int64 captureTimestampMicros;int64 "
|
||||||
|
"publishTimestampMicros;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::PhotonPipelineMetadata Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet,
|
||||||
|
const photon::PhotonPipelineMetadata& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::PhotonPipelineMetadata>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/PhotonPipelineResult.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
|
#include "photon/targeting/PhotonPipelineMetadata.h"
|
||||||
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
#include <optional>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<PhotonPipelineResult> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "cb3e1605048ba49325888eb797399fe2";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "PhotonPipelineMetadata metadata;PhotonTrackedTarget[?] "
|
||||||
|
"targets;MultiTargetPNPResult? multitagResult;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::PhotonPipelineResult Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet,
|
||||||
|
const photon::PhotonPipelineResult& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::PhotonPipelineResult>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include "photon/targeting/TargetCorner.h"
|
||||||
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<PhotonTrackedTarget> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "8fdada56b9162f2e32bd24f0055d7b60";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "float64 yaw;float64 pitch;float64 area;float64 skew;int32 "
|
||||||
|
"fiducialId;int32 objDetectId;float32 objDetectConf;Transform3d "
|
||||||
|
"bestCameraToTarget;Transform3d altCameraToTarget;float64 "
|
||||||
|
"poseAmbiguity;TargetCorner[?] minAreaRectCorners;TargetCorner[?] "
|
||||||
|
"detectedCorners;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::PhotonTrackedTarget Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet,
|
||||||
|
const photon::PhotonTrackedTarget& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::PhotonTrackedTarget>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<PnpResult> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "0d1f2546b00f24718e30f38d206d4491";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "Transform3d best;Transform3d alt;float64 bestReprojErr;float64 "
|
||||||
|
"altReprojErr;float64 ambiguity;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::PnpResult Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet, const photon::PnpResult& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::PnpResult>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
#include <wpi/SymbolExports.h>
|
||||||
|
|
||||||
|
// Include myself
|
||||||
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
|
#include "photon/targeting/TargetCorner.h"
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct WPILIB_DLLEXPORT SerdeType<TargetCorner> {
|
||||||
|
static constexpr std::string_view GetSchemaHash() {
|
||||||
|
return "22b1ff7551d10215af6fb3672fe4eda8";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view GetSchema() {
|
||||||
|
return "float64 x;float64 y;";
|
||||||
|
}
|
||||||
|
|
||||||
|
static photon::TargetCorner Unpack(photon::Packet& packet);
|
||||||
|
static void Pack(photon::Packet& packet, const photon::TargetCorner& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(photon::PhotonStructSerializable<photon::TargetCorner>);
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "photon/targeting/PnpResult.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct MultiTargetPNPResult_PhotonStruct {
|
||||||
|
photon::PnpResult estimatedPose;
|
||||||
|
std::vector<int16_t> fiducialIDsUsed;
|
||||||
|
|
||||||
|
friend bool operator==(MultiTargetPNPResult_PhotonStruct const&,
|
||||||
|
MultiTargetPNPResult_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -15,11 +15,23 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#pragma once
|
||||||
#include "photon/targeting/MultiTargetPNPResult.h"
|
|
||||||
|
|
||||||
// TODO
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
TEST(MultiTargetPNPResultTest, Equality) {}
|
// NOT MODIFY
|
||||||
|
|
||||||
// TODO
|
// Includes for dependant types
|
||||||
TEST(MultiTargetPNPResultTest, Inequality) {}
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct PhotonPipelineMetadata_PhotonStruct {
|
||||||
|
int64_t sequenceID;
|
||||||
|
int64_t captureTimestampMicros;
|
||||||
|
int64_t publishTimestampMicros;
|
||||||
|
|
||||||
|
friend bool operator==(PhotonPipelineMetadata_PhotonStruct const&,
|
||||||
|
PhotonPipelineMetadata_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "photon/targeting/MultiTargetPNPResult.h"
|
||||||
|
#include "photon/targeting/PhotonPipelineMetadata.h"
|
||||||
|
#include "photon/targeting/PhotonTrackedTarget.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct PhotonPipelineResult_PhotonStruct {
|
||||||
|
photon::PhotonPipelineMetadata metadata;
|
||||||
|
std::vector<photon::PhotonTrackedTarget> targets;
|
||||||
|
std::optional<photon::MultiTargetPNPResult> multitagResult;
|
||||||
|
|
||||||
|
friend bool operator==(PhotonPipelineResult_PhotonStruct const&,
|
||||||
|
PhotonPipelineResult_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
|
||||||
|
#include "photon/targeting/TargetCorner.h"
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct PhotonTrackedTarget_PhotonStruct {
|
||||||
|
double yaw;
|
||||||
|
double pitch;
|
||||||
|
double area;
|
||||||
|
double skew;
|
||||||
|
int32_t fiducialId;
|
||||||
|
int32_t objDetectId;
|
||||||
|
float objDetectConf;
|
||||||
|
frc::Transform3d bestCameraToTarget;
|
||||||
|
frc::Transform3d altCameraToTarget;
|
||||||
|
double poseAmbiguity;
|
||||||
|
std::vector<photon::TargetCorner> minAreaRectCorners;
|
||||||
|
std::vector<photon::TargetCorner> detectedCorners;
|
||||||
|
|
||||||
|
friend bool operator==(PhotonTrackedTarget_PhotonStruct const&,
|
||||||
|
PhotonTrackedTarget_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
|
// NOT MODIFY
|
||||||
|
|
||||||
|
// Includes for dependant types
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct PnpResult_PhotonStruct {
|
||||||
|
frc::Transform3d best;
|
||||||
|
frc::Transform3d alt;
|
||||||
|
double bestReprojErr;
|
||||||
|
double altReprojErr;
|
||||||
|
double ambiguity;
|
||||||
|
|
||||||
|
friend bool operator==(PnpResult_PhotonStruct const&,
|
||||||
|
PnpResult_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -15,11 +15,22 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#pragma once
|
||||||
#include "photon/targeting/PhotonTrackedTarget.h"
|
|
||||||
|
|
||||||
// TODO
|
// THIS std::FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO
|
||||||
TEST(PhotonTrackedTargetTest, Equality) {}
|
// NOT MODIFY
|
||||||
|
|
||||||
// TODO
|
// Includes for dependant types
|
||||||
TEST(PhotonTrackedTargetTest, Inequality) {}
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace photon {
|
||||||
|
|
||||||
|
struct TargetCorner_PhotonStruct {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
|
||||||
|
friend bool operator==(TargetCorner_PhotonStruct const&,
|
||||||
|
TargetCorner_PhotonStruct const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace photon
|
||||||
@@ -17,22 +17,25 @@
|
|||||||
|
|
||||||
package org.photonvision.common.dataflow.structures;
|
package org.photonvision.common.dataflow.structures;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
/** A packet that holds byte-packed data to be sent over NetworkTables. */
|
/** A packet that holds byte-packed data to be sent over NetworkTables. */
|
||||||
public class Packet {
|
public class Packet {
|
||||||
// Size of the packet.
|
|
||||||
int size;
|
|
||||||
// Data stored in the packet.
|
// Data stored in the packet.
|
||||||
byte[] packetData;
|
byte[] packetData;
|
||||||
// Read and write positions.
|
// Read and write positions.
|
||||||
int readPos, writePos;
|
int readPos, writePos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an empty packet.
|
* Constructs an empty packet. This buffer will dynamically expand if we need more data space.
|
||||||
*
|
*
|
||||||
* @param size The size of the packet buffer.
|
* @param size The size of the packet buffer.
|
||||||
*/
|
*/
|
||||||
public Packet(int size) {
|
public Packet(int size) {
|
||||||
this.size = size;
|
|
||||||
packetData = new byte[size];
|
packetData = new byte[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,27 +46,34 @@ public class Packet {
|
|||||||
*/
|
*/
|
||||||
public Packet(byte[] data) {
|
public Packet(byte[] data) {
|
||||||
packetData = data;
|
packetData = data;
|
||||||
size = packetData.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clears the packet and resets the read and write positions. */
|
/** Clears the packet and resets the read and write positions. */
|
||||||
public void clear() {
|
public void clear() {
|
||||||
packetData = new byte[size];
|
packetData = new byte[packetData.length];
|
||||||
readPos = 0;
|
readPos = 0;
|
||||||
writePos = 0;
|
writePos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNumBytesWritten() {
|
||||||
|
return writePos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumBytesRead() {
|
||||||
|
return readPos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return size;
|
return packetData.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the packet data.
|
* Returns a copy of only the packet data we've actually written to so far.
|
||||||
*
|
*
|
||||||
* @return The packet data.
|
* @return The packet data.
|
||||||
*/
|
*/
|
||||||
public byte[] getData() {
|
public byte[] getWrittenDataCopy() {
|
||||||
return packetData;
|
return Arrays.copyOfRange(packetData, 0, writePos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +83,64 @@ public class Packet {
|
|||||||
*/
|
*/
|
||||||
public void setData(byte[] data) {
|
public void setData(byte[] data) {
|
||||||
packetData = data;
|
packetData = data;
|
||||||
size = data.length;
|
}
|
||||||
|
|
||||||
|
// Logic taken from ArraysSupport, licensed under GPL V2
|
||||||
|
public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
|
||||||
|
|
||||||
|
// Logic taken from ArraysSupport, licensed under GPL V2
|
||||||
|
private static int newLength(int oldLength, int minGrowth, int prefGrowth) {
|
||||||
|
// preconditions not checked because of inlining
|
||||||
|
// assert oldLength >= 0
|
||||||
|
// assert minGrowth > 0
|
||||||
|
|
||||||
|
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
|
||||||
|
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
|
||||||
|
return prefLength;
|
||||||
|
} else {
|
||||||
|
// put code cold in a separate method
|
||||||
|
return hugeLength(oldLength, minGrowth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic taken from ArraysSupport, licensed under GPL V2
|
||||||
|
private static int hugeLength(int oldLength, int minGrowth) {
|
||||||
|
int minLength = oldLength + minGrowth;
|
||||||
|
if (minLength < 0) { // overflow
|
||||||
|
throw new OutOfMemoryError(
|
||||||
|
"Required array length " + oldLength + " + " + minGrowth + " is too large");
|
||||||
|
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
|
||||||
|
return SOFT_MAX_ARRAY_LENGTH;
|
||||||
|
} else {
|
||||||
|
return minLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the capacity to ensure that it can hold at least the number of elements specified by
|
||||||
|
* the minimum capacity argument.
|
||||||
|
*
|
||||||
|
* <p>This logic is copied from ArrayList, which is licensed GPL V2
|
||||||
|
*
|
||||||
|
* @param minCapacity the desired minimum capacity
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private void ensureCapacity(int bytesToAdd) {
|
||||||
|
int minCapacity = writePos + bytesToAdd;
|
||||||
|
int oldCapacity = packetData.length;
|
||||||
|
if (minCapacity <= oldCapacity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oldCapacity > 0) {
|
||||||
|
int newCapacity =
|
||||||
|
Packet.newLength(
|
||||||
|
oldCapacity,
|
||||||
|
minCapacity - oldCapacity, /* minimum growth */
|
||||||
|
oldCapacity >> 1 /* preferred growth */);
|
||||||
|
packetData = Arrays.copyOf(packetData, newCapacity);
|
||||||
|
} else {
|
||||||
|
packetData = new byte[Math.max(256, minCapacity)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +149,7 @@ public class Packet {
|
|||||||
* @param src The byte to encode.
|
* @param src The byte to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(byte src) {
|
public void encode(byte src) {
|
||||||
|
ensureCapacity(1);
|
||||||
packetData[writePos++] = src;
|
packetData[writePos++] = src;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +159,7 @@ public class Packet {
|
|||||||
* @param src The short to encode.
|
* @param src The short to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(short src) {
|
public void encode(short src) {
|
||||||
|
ensureCapacity(2);
|
||||||
packetData[writePos++] = (byte) (src >>> 8);
|
packetData[writePos++] = (byte) (src >>> 8);
|
||||||
packetData[writePos++] = (byte) src;
|
packetData[writePos++] = (byte) src;
|
||||||
}
|
}
|
||||||
@@ -101,6 +170,7 @@ public class Packet {
|
|||||||
* @param src The integer to encode.
|
* @param src The integer to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(int src) {
|
public void encode(int src) {
|
||||||
|
ensureCapacity(4);
|
||||||
packetData[writePos++] = (byte) (src >>> 24);
|
packetData[writePos++] = (byte) (src >>> 24);
|
||||||
packetData[writePos++] = (byte) (src >>> 16);
|
packetData[writePos++] = (byte) (src >>> 16);
|
||||||
packetData[writePos++] = (byte) (src >>> 8);
|
packetData[writePos++] = (byte) (src >>> 8);
|
||||||
@@ -113,6 +183,7 @@ public class Packet {
|
|||||||
* @param src The float to encode.
|
* @param src The float to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(float src) {
|
public void encode(float src) {
|
||||||
|
ensureCapacity(4);
|
||||||
int data = Float.floatToIntBits(src);
|
int data = Float.floatToIntBits(src);
|
||||||
packetData[writePos++] = (byte) ((data >> 24) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 24) & 0xff);
|
||||||
packetData[writePos++] = (byte) ((data >> 16) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 16) & 0xff);
|
||||||
@@ -126,6 +197,7 @@ public class Packet {
|
|||||||
* @param data The double to encode.
|
* @param data The double to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(long data) {
|
public void encode(long data) {
|
||||||
|
ensureCapacity(8);
|
||||||
packetData[writePos++] = (byte) ((data >> 56) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 56) & 0xff);
|
||||||
packetData[writePos++] = (byte) ((data >> 48) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 48) & 0xff);
|
||||||
packetData[writePos++] = (byte) ((data >> 40) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 40) & 0xff);
|
||||||
@@ -142,6 +214,7 @@ public class Packet {
|
|||||||
* @param src The double to encode.
|
* @param src The double to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(double src) {
|
public void encode(double src) {
|
||||||
|
ensureCapacity(8);
|
||||||
long data = Double.doubleToRawLongBits(src);
|
long data = Double.doubleToRawLongBits(src);
|
||||||
packetData[writePos++] = (byte) ((data >> 56) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 56) & 0xff);
|
||||||
packetData[writePos++] = (byte) ((data >> 48) & 0xff);
|
packetData[writePos++] = (byte) ((data >> 48) & 0xff);
|
||||||
@@ -159,9 +232,56 @@ public class Packet {
|
|||||||
* @param src The boolean to encode.
|
* @param src The boolean to encode.
|
||||||
*/
|
*/
|
||||||
public void encode(boolean src) {
|
public void encode(boolean src) {
|
||||||
|
ensureCapacity(1);
|
||||||
packetData[writePos++] = src ? (byte) 1 : (byte) 0;
|
packetData[writePos++] = src ? (byte) 1 : (byte) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void encode(List<Short> data) {
|
||||||
|
byte size = (byte) data.size();
|
||||||
|
if (data.size() > Byte.MAX_VALUE) {
|
||||||
|
throw new RuntimeException("Array too long! Got " + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// length byte
|
||||||
|
encode(size);
|
||||||
|
|
||||||
|
for (var f : data) {
|
||||||
|
encode(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PhotonStructSerializable<T>> void encode(T data) {
|
||||||
|
data.getSerde().pack(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a list of serializable structs. Lists are stored as [uint8 length, [length many] data
|
||||||
|
* structs]
|
||||||
|
*
|
||||||
|
* @param <T> the class this list will be packing
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
public <T extends PhotonStructSerializable<T>> void encodeList(List<T> data) {
|
||||||
|
byte size = (byte) data.size();
|
||||||
|
if (data.size() > Byte.MAX_VALUE) {
|
||||||
|
throw new RuntimeException("Array too long! Got " + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// length byte
|
||||||
|
encode(size);
|
||||||
|
|
||||||
|
for (var f : data) {
|
||||||
|
f.getSerde().pack(this, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PhotonStructSerializable<T>> void encodeOptional(Optional<T> data) {
|
||||||
|
encode(data.isPresent());
|
||||||
|
if (data.isPresent()) {
|
||||||
|
data.get().getSerde().pack(this, data.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a decoded byte from the packet.
|
* Returns a decoded byte from the packet.
|
||||||
*
|
*
|
||||||
@@ -275,4 +395,49 @@ public class Packet {
|
|||||||
}
|
}
|
||||||
return (short) ((0xff & packetData[readPos++]) << 8 | (0xff & packetData[readPos++]));
|
return (short) ((0xff & packetData[readPos++]) << 8 | (0xff & packetData[readPos++]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a list of serializable structs. Lists are stored as [uint8 length, [length many] data
|
||||||
|
* structs]. Because java sucks, we need to take the serde ref directly
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* @param serde
|
||||||
|
*/
|
||||||
|
public <T extends PhotonStructSerializable<T>> List<T> decodeList(PacketSerde<T> serde) {
|
||||||
|
byte length = decodeByte();
|
||||||
|
|
||||||
|
var ret = new ArrayList<T>();
|
||||||
|
ret.ensureCapacity(length);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
ret.add(serde.unpack(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PhotonStructSerializable<T>> Optional<T> decodeOptional(PacketSerde<T> serde) {
|
||||||
|
var present = decodeBoolean();
|
||||||
|
if (present) {
|
||||||
|
return Optional.of(serde.unpack(this));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Short> decodeShortList() {
|
||||||
|
byte length = decodeByte();
|
||||||
|
|
||||||
|
var ret = new ArrayList<Short>();
|
||||||
|
ret.ensureCapacity(length);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
ret.add(decodeShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PhotonStructSerializable<T>> T decode(PhotonStructSerializable<T> t) {
|
||||||
|
return t.getSerde().unpack(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,8 @@ public interface PacketSerde<T> {
|
|||||||
void pack(Packet packet, T value);
|
void pack(Packet packet, T value);
|
||||||
|
|
||||||
T unpack(Packet packet);
|
T unpack(Packet packet);
|
||||||
|
|
||||||
|
String getTypeString();
|
||||||
|
|
||||||
|
String getInterfaceUUID();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ public class NTTopicSet {
|
|||||||
.getRawTopic("rawBytes")
|
.getRawTopic("rawBytes")
|
||||||
.publish("rawBytes", PubSubOption.periodic(0.01), PubSubOption.sendAll(true));
|
.publish("rawBytes", PubSubOption.periodic(0.01), PubSubOption.sendAll(true));
|
||||||
|
|
||||||
resultPublisher = new PacketPublisher<>(rawBytesEntry, PhotonPipelineResult.serde);
|
resultPublisher =
|
||||||
|
new PacketPublisher<PhotonPipelineResult>(rawBytesEntry, PhotonPipelineResult.photonStruct);
|
||||||
protoResultPublisher =
|
protoResultPublisher =
|
||||||
subTable
|
subTable
|
||||||
.getProtobufTopic("result_proto", PhotonPipelineResult.proto)
|
.getProtobufTopic("result_proto", PhotonPipelineResult.proto)
|
||||||
|
|||||||
@@ -17,27 +17,44 @@
|
|||||||
|
|
||||||
package org.photonvision.common.networktables;
|
package org.photonvision.common.networktables;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import edu.wpi.first.networktables.RawPublisher;
|
import edu.wpi.first.networktables.RawPublisher;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
import org.photonvision.common.dataflow.structures.Packet;
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
|
||||||
public class PacketPublisher<T> implements AutoCloseable {
|
public class PacketPublisher<T> implements AutoCloseable {
|
||||||
public final RawPublisher publisher;
|
public final RawPublisher publisher;
|
||||||
private final PacketSerde<T> serde;
|
private final PacketSerde<T> photonStruct;
|
||||||
|
|
||||||
public PacketPublisher(RawPublisher publisher, PacketSerde<T> serde) {
|
public PacketPublisher(RawPublisher publisher, PacketSerde<T> photonStruct) {
|
||||||
this.publisher = publisher;
|
this.publisher = publisher;
|
||||||
this.serde = serde;
|
this.photonStruct = photonStruct;
|
||||||
|
|
||||||
|
var mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
this.publisher
|
||||||
|
.getTopic()
|
||||||
|
.setProperty("message_format", mapper.writeValueAsString(photonStruct.getTypeString()));
|
||||||
|
this.publisher
|
||||||
|
.getTopic()
|
||||||
|
.setProperty("message_uuid", mapper.writeValueAsString(photonStruct.getInterfaceUUID()));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(T value, int byteSize) {
|
public void set(T value, int byteSize) {
|
||||||
var packet = new Packet(byteSize);
|
var packet = new Packet(byteSize);
|
||||||
serde.pack(packet, value);
|
photonStruct.pack(packet, value);
|
||||||
publisher.set(packet.getData());
|
// todo: trim to only the bytes we need to send
|
||||||
|
publisher.set(packet.getWrittenDataCopy());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(T value) {
|
public void set(T value) {
|
||||||
set(value, serde.getMaxByteSize());
|
set(value, photonStruct.getMaxByteSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ public class PacketSubscriber<T> implements AutoCloseable {
|
|||||||
subscriber.close();
|
subscriber.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - i can see an argument for moving this logic all here instead of keeping in photoncamera
|
||||||
|
public String getInterfaceUUID() {
|
||||||
|
return subscriber.getTopic().getProperty("message_uuid");
|
||||||
|
}
|
||||||
|
|
||||||
public List<PacketResult<T>> getAllChanges() {
|
public List<PacketResult<T>> getAllChanges() {
|
||||||
List<PacketResult<T>> ret = new ArrayList<>();
|
List<PacketResult<T>> ret = new ArrayList<>();
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import edu.wpi.first.math.numbers.*;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.ejml.simple.SimpleMatrix;
|
import org.ejml.simple.SimpleMatrix;
|
||||||
import org.opencv.calib3d.Calib3d;
|
import org.opencv.calib3d.Calib3d;
|
||||||
import org.opencv.core.Core;
|
import org.opencv.core.Core;
|
||||||
@@ -46,7 +47,7 @@ import org.opencv.core.Point3;
|
|||||||
import org.opencv.core.Rect;
|
import org.opencv.core.Rect;
|
||||||
import org.opencv.core.RotatedRect;
|
import org.opencv.core.RotatedRect;
|
||||||
import org.opencv.imgproc.Imgproc;
|
import org.opencv.imgproc.Imgproc;
|
||||||
import org.photonvision.targeting.PNPResult;
|
import org.photonvision.targeting.PnpResult;
|
||||||
import org.photonvision.targeting.TargetCorner;
|
import org.photonvision.targeting.TargetCorner;
|
||||||
|
|
||||||
public final class OpenCVHelp {
|
public final class OpenCVHelp {
|
||||||
@@ -402,7 +403,7 @@ public final class OpenCVHelp {
|
|||||||
* @return The resulting transformation that maps the camera pose to the target pose and the
|
* @return The resulting transformation that maps the camera pose to the target pose and the
|
||||||
* ambiguity if an alternate solution is available.
|
* ambiguity if an alternate solution is available.
|
||||||
*/
|
*/
|
||||||
public static PNPResult solvePNP_SQUARE(
|
public static Optional<PnpResult> solvePNP_SQUARE(
|
||||||
Matrix<N3, N3> cameraMatrix,
|
Matrix<N3, N3> cameraMatrix,
|
||||||
Matrix<N8, N1> distCoeffs,
|
Matrix<N8, N1> distCoeffs,
|
||||||
List<Translation3d> modelTrls,
|
List<Translation3d> modelTrls,
|
||||||
@@ -467,14 +468,15 @@ public final class OpenCVHelp {
|
|||||||
// check if solvePnP failed with NaN results and retrying failed
|
// check if solvePnP failed with NaN results and retrying failed
|
||||||
if (Double.isNaN(errors[0])) throw new Exception("SolvePNP_SQUARE NaN result");
|
if (Double.isNaN(errors[0])) throw new Exception("SolvePNP_SQUARE NaN result");
|
||||||
|
|
||||||
if (alt != null) return new PNPResult(best, alt, errors[0] / errors[1], errors[0], errors[1]);
|
if (alt != null)
|
||||||
else return new PNPResult(best, errors[0]);
|
return Optional.of(new PnpResult(best, alt, errors[0] / errors[1], errors[0], errors[1]));
|
||||||
|
else return Optional.empty();
|
||||||
}
|
}
|
||||||
// solvePnP failed
|
// solvePnP failed
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
System.err.println("SolvePNP_SQUARE failed!");
|
System.err.println("SolvePNP_SQUARE failed!");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return new PNPResult();
|
return Optional.empty();
|
||||||
} finally {
|
} finally {
|
||||||
// release our Mats from native memory
|
// release our Mats from native memory
|
||||||
objectMat.release();
|
objectMat.release();
|
||||||
@@ -509,7 +511,7 @@ public final class OpenCVHelp {
|
|||||||
* model points are supplied relative to the origin, this transformation brings the camera to
|
* model points are supplied relative to the origin, this transformation brings the camera to
|
||||||
* the origin.
|
* the origin.
|
||||||
*/
|
*/
|
||||||
public static PNPResult solvePNP_SQPNP(
|
public static Optional<PnpResult> solvePNP_SQPNP(
|
||||||
Matrix<N3, N3> cameraMatrix,
|
Matrix<N3, N3> cameraMatrix,
|
||||||
Matrix<N8, N1> distCoeffs,
|
Matrix<N8, N1> distCoeffs,
|
||||||
List<Translation3d> objectTrls,
|
List<Translation3d> objectTrls,
|
||||||
@@ -558,11 +560,11 @@ public final class OpenCVHelp {
|
|||||||
// check if solvePnP failed with NaN results
|
// check if solvePnP failed with NaN results
|
||||||
if (Double.isNaN(error[0])) throw new Exception("SolvePNP_SQPNP NaN result");
|
if (Double.isNaN(error[0])) throw new Exception("SolvePNP_SQPNP NaN result");
|
||||||
|
|
||||||
return new PNPResult(best, error[0]);
|
return Optional.of(new PnpResult(best, error[0]));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("SolvePNP_SQPNP failed!");
|
System.err.println("SolvePNP_SQPNP failed!");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return new PNPResult();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ import edu.wpi.first.math.numbers.*;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
import org.photonvision.targeting.PNPResult;
|
|
||||||
import org.photonvision.targeting.PhotonTrackedTarget;
|
import org.photonvision.targeting.PhotonTrackedTarget;
|
||||||
|
import org.photonvision.targeting.PnpResult;
|
||||||
import org.photonvision.targeting.TargetCorner;
|
import org.photonvision.targeting.TargetCorner;
|
||||||
|
|
||||||
public class VisionEstimation {
|
public class VisionEstimation {
|
||||||
@@ -64,9 +65,9 @@ public class VisionEstimation {
|
|||||||
* @param visTags The visible tags reported by PV. Non-tag targets are automatically excluded.
|
* @param visTags The visible tags reported by PV. Non-tag targets are automatically excluded.
|
||||||
* @param tagLayout The known tag layout on the field
|
* @param tagLayout The known tag layout on the field
|
||||||
* @return The transformation that maps the field origin to the camera pose. Ensure the {@link
|
* @return The transformation that maps the field origin to the camera pose. Ensure the {@link
|
||||||
* PNPResult} are present before utilizing them.
|
* PnpResult} are present before utilizing them.
|
||||||
*/
|
*/
|
||||||
public static PNPResult estimateCamPosePNP(
|
public static Optional<PnpResult> estimateCamPosePNP(
|
||||||
Matrix<N3, N3> cameraMatrix,
|
Matrix<N3, N3> cameraMatrix,
|
||||||
Matrix<N8, N1> distCoeffs,
|
Matrix<N8, N1> distCoeffs,
|
||||||
List<PhotonTrackedTarget> visTags,
|
List<PhotonTrackedTarget> visTags,
|
||||||
@@ -76,7 +77,7 @@ public class VisionEstimation {
|
|||||||
|| visTags == null
|
|| visTags == null
|
||||||
|| tagLayout.getTags().isEmpty()
|
|| tagLayout.getTags().isEmpty()
|
||||||
|| visTags.isEmpty()) {
|
|| visTags.isEmpty()) {
|
||||||
return new PNPResult();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
var corners = new ArrayList<TargetCorner>();
|
var corners = new ArrayList<TargetCorner>();
|
||||||
@@ -93,7 +94,7 @@ public class VisionEstimation {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (knownTags.isEmpty() || corners.isEmpty() || corners.size() % 4 != 0) {
|
if (knownTags.isEmpty() || corners.isEmpty() || corners.size() % 4 != 0) {
|
||||||
return new PNPResult();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Point[] points = OpenCVHelp.cornersToPoints(corners);
|
Point[] points = OpenCVHelp.cornersToPoints(corners);
|
||||||
|
|
||||||
@@ -101,32 +102,34 @@ public class VisionEstimation {
|
|||||||
if (knownTags.size() == 1) {
|
if (knownTags.size() == 1) {
|
||||||
var camToTag =
|
var camToTag =
|
||||||
OpenCVHelp.solvePNP_SQUARE(cameraMatrix, distCoeffs, tagModel.vertices, points);
|
OpenCVHelp.solvePNP_SQUARE(cameraMatrix, distCoeffs, tagModel.vertices, points);
|
||||||
if (!camToTag.isPresent) return new PNPResult();
|
if (!camToTag.isPresent()) return Optional.empty();
|
||||||
var bestPose = knownTags.get(0).pose.transformBy(camToTag.best.inverse());
|
var bestPose = knownTags.get(0).pose.transformBy(camToTag.get().best.inverse());
|
||||||
var altPose = new Pose3d();
|
var altPose = new Pose3d();
|
||||||
if (camToTag.ambiguity != 0)
|
if (camToTag.get().ambiguity != 0)
|
||||||
altPose = knownTags.get(0).pose.transformBy(camToTag.alt.inverse());
|
altPose = knownTags.get(0).pose.transformBy(camToTag.get().alt.inverse());
|
||||||
|
|
||||||
var o = new Pose3d();
|
var o = new Pose3d();
|
||||||
return new PNPResult(
|
return Optional.of(
|
||||||
new Transform3d(o, bestPose),
|
new PnpResult(
|
||||||
new Transform3d(o, altPose),
|
new Transform3d(o, bestPose),
|
||||||
camToTag.ambiguity,
|
new Transform3d(o, altPose),
|
||||||
camToTag.bestReprojErr,
|
camToTag.get().ambiguity,
|
||||||
camToTag.altReprojErr);
|
camToTag.get().bestReprojErr,
|
||||||
|
camToTag.get().altReprojErr));
|
||||||
}
|
}
|
||||||
// multi-tag pnp
|
// multi-tag pnp
|
||||||
else {
|
else {
|
||||||
var objectTrls = new ArrayList<Translation3d>();
|
var objectTrls = new ArrayList<Translation3d>();
|
||||||
for (var tag : knownTags) objectTrls.addAll(tagModel.getFieldVertices(tag.pose));
|
for (var tag : knownTags) objectTrls.addAll(tagModel.getFieldVertices(tag.pose));
|
||||||
var camToOrigin = OpenCVHelp.solvePNP_SQPNP(cameraMatrix, distCoeffs, objectTrls, points);
|
var camToOrigin = OpenCVHelp.solvePNP_SQPNP(cameraMatrix, distCoeffs, objectTrls, points);
|
||||||
if (!camToOrigin.isPresent) return new PNPResult();
|
if (camToOrigin.isEmpty()) return Optional.empty();
|
||||||
return new PNPResult(
|
return Optional.of(
|
||||||
camToOrigin.best.inverse(),
|
new PnpResult(
|
||||||
camToOrigin.alt.inverse(),
|
camToOrigin.get().best.inverse(),
|
||||||
camToOrigin.ambiguity,
|
camToOrigin.get().alt.inverse(),
|
||||||
camToOrigin.bestReprojErr,
|
camToOrigin.get().ambiguity,
|
||||||
camToOrigin.altReprojErr);
|
camToOrigin.get().bestReprojErr,
|
||||||
|
camToOrigin.get().altReprojErr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,22 +18,23 @@
|
|||||||
package org.photonvision.targeting;
|
package org.photonvision.targeting;
|
||||||
|
|
||||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.MultiTargetPNPResultSerde;
|
||||||
import org.photonvision.targeting.proto.MultiTargetPNPResultProto;
|
import org.photonvision.targeting.proto.MultiTargetPNPResultProto;
|
||||||
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
public class MultiTargetPNPResult implements ProtobufSerializable {
|
public class MultiTargetPNPResult
|
||||||
|
implements ProtobufSerializable, PhotonStructSerializable<MultiTargetPNPResult> {
|
||||||
// Seeing 32 apriltags at once seems like a sane limit
|
// Seeing 32 apriltags at once seems like a sane limit
|
||||||
private static final int MAX_IDS = 32;
|
private static final int MAX_IDS = 32;
|
||||||
|
|
||||||
public PNPResult estimatedPose = new PNPResult();
|
public PnpResult estimatedPose = new PnpResult();
|
||||||
public List<Integer> fiducialIDsUsed = List.of();
|
public List<Short> fiducialIDsUsed = List.of();
|
||||||
|
|
||||||
public MultiTargetPNPResult() {}
|
public MultiTargetPNPResult() {}
|
||||||
|
|
||||||
public MultiTargetPNPResult(PNPResult results, List<Integer> ids) {
|
public MultiTargetPNPResult(PnpResult results, List<Short> ids) {
|
||||||
estimatedPose = results;
|
estimatedPose = results;
|
||||||
fiducialIDsUsed = ids;
|
fiducialIDsUsed = ids;
|
||||||
}
|
}
|
||||||
@@ -71,39 +72,13 @@ public class MultiTargetPNPResult implements ProtobufSerializable {
|
|||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class APacketSerde implements PacketSerde<MultiTargetPNPResult> {
|
|
||||||
@Override
|
|
||||||
public int getMaxByteSize() {
|
|
||||||
// PNPResult + MAX_IDS possible targets (arbitrary upper limit that should never be hit,
|
|
||||||
// ideally)
|
|
||||||
return PNPResult.serde.getMaxByteSize() + (Short.BYTES * MAX_IDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pack(Packet packet, MultiTargetPNPResult result) {
|
|
||||||
PNPResult.serde.pack(packet, result.estimatedPose);
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_IDS; i++) {
|
|
||||||
if (i < result.fiducialIDsUsed.size()) {
|
|
||||||
packet.encode((short) result.fiducialIDsUsed.get(i).byteValue());
|
|
||||||
} else {
|
|
||||||
packet.encode((short) -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MultiTargetPNPResult unpack(Packet packet) {
|
|
||||||
var results = PNPResult.serde.unpack(packet);
|
|
||||||
var ids = new ArrayList<Integer>(MAX_IDS);
|
|
||||||
for (int i = 0; i < MAX_IDS; i++) {
|
|
||||||
int targetId = packet.decodeShort();
|
|
||||||
if (targetId > -1) ids.add(targetId);
|
|
||||||
}
|
|
||||||
return new MultiTargetPNPResult(results, ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final APacketSerde serde = new APacketSerde();
|
|
||||||
public static final MultiTargetPNPResultProto proto = new MultiTargetPNPResultProto();
|
public static final MultiTargetPNPResultProto proto = new MultiTargetPNPResultProto();
|
||||||
|
|
||||||
|
// tODO!
|
||||||
|
public static final MultiTargetPNPResultSerde photonStruct = new MultiTargetPNPResultSerde();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<MultiTargetPNPResult> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.targeting;
|
||||||
|
|
||||||
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.PhotonPipelineMetadataSerde;
|
||||||
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
|
public class PhotonPipelineMetadata implements PhotonStructSerializable<PhotonPipelineMetadata> {
|
||||||
|
// Mirror of the heartbeat entry -- monotonically increasing
|
||||||
|
public long sequenceID;
|
||||||
|
|
||||||
|
// Image capture and NT publish timestamp, in microseconds and in the
|
||||||
|
// coprocessor timebase. As
|
||||||
|
// reported by WPIUtilJNI::now.
|
||||||
|
public long captureTimestampMicros;
|
||||||
|
public long publishTimestampMicros;
|
||||||
|
|
||||||
|
public PhotonPipelineMetadata(
|
||||||
|
long captureTimestampMicros, long publishTimestampMicros, long sequenceID) {
|
||||||
|
this.captureTimestampMicros = captureTimestampMicros;
|
||||||
|
this.publishTimestampMicros = publishTimestampMicros;
|
||||||
|
this.sequenceID = sequenceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhotonPipelineMetadata() {
|
||||||
|
this(-1, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the time between image capture and publish to NT */
|
||||||
|
public double getLatencyMillis() {
|
||||||
|
return (publishTimestampMicros - captureTimestampMicros) / 1e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The time that this image was captured, in the coprocessor's time base. */
|
||||||
|
public long getCaptureTimestampMicros() {
|
||||||
|
return captureTimestampMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The time that this result was published to NT, in the coprocessor's time base. */
|
||||||
|
public long getPublishTimestampMicros() {
|
||||||
|
return publishTimestampMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of non-empty frames processed by this camera since boot. Useful to checking if a
|
||||||
|
* camera is alive.
|
||||||
|
*/
|
||||||
|
public long getSequenceID() {
|
||||||
|
return sequenceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PhotonPipelineMetadata [sequenceID="
|
||||||
|
+ sequenceID
|
||||||
|
+ ", captureTimestampMicros="
|
||||||
|
+ captureTimestampMicros
|
||||||
|
+ ", publishTimestampMicros="
|
||||||
|
+ publishTimestampMicros
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + (int) (sequenceID ^ (sequenceID >>> 32));
|
||||||
|
result = prime * result + (int) (captureTimestampMicros ^ (captureTimestampMicros >>> 32));
|
||||||
|
result = prime * result + (int) (publishTimestampMicros ^ (publishTimestampMicros >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
PhotonPipelineMetadata other = (PhotonPipelineMetadata) obj;
|
||||||
|
if (sequenceID != other.sequenceID) return false;
|
||||||
|
if (captureTimestampMicros != other.captureTimestampMicros) return false;
|
||||||
|
if (publishTimestampMicros != other.publishTimestampMicros) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final PhotonPipelineMetadataSerde photonStruct = new PhotonPipelineMetadataSerde();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<PhotonPipelineMetadata> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,33 +20,33 @@ package org.photonvision.targeting;
|
|||||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
import java.util.Optional;
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.PhotonPipelineResultSerde;
|
||||||
import org.photonvision.targeting.proto.PhotonPipelineResultProto;
|
import org.photonvision.targeting.proto.PhotonPipelineResultProto;
|
||||||
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
/** Represents a pipeline result from a PhotonCamera. */
|
/** Represents a pipeline result from a PhotonCamera. */
|
||||||
public class PhotonPipelineResult implements ProtobufSerializable {
|
public class PhotonPipelineResult
|
||||||
|
implements ProtobufSerializable, PhotonStructSerializable<PhotonPipelineResult> {
|
||||||
private static boolean HAS_WARNED = false;
|
private static boolean HAS_WARNED = false;
|
||||||
|
|
||||||
// Image capture and NT publish timestamp, in microseconds and in the coprocessor timebase. As
|
// Frame capture metadata
|
||||||
// reported by WPIUtilJNI::now.
|
public PhotonPipelineMetadata metadata;
|
||||||
private long captureTimestampMicros = -1;
|
|
||||||
private long publishTimestampMicros = -1;
|
|
||||||
|
|
||||||
// Mirror of the heartbeat entry -- monotonically increasing
|
|
||||||
private long sequenceID = -1;
|
|
||||||
|
|
||||||
// Targets to store.
|
// Targets to store.
|
||||||
public final List<PhotonTrackedTarget> targets = new ArrayList<>();
|
public List<PhotonTrackedTarget> targets = new ArrayList<>();
|
||||||
|
|
||||||
// Multi-tag result
|
// Multi-tag result
|
||||||
private MultiTargetPNPResult multiTagResult = new MultiTargetPNPResult();
|
public Optional<MultiTargetPNPResult> multitagResult;
|
||||||
|
|
||||||
// Since we don't trust NT time sync, keep track of when we got this packet into robot code
|
// HACK: Since we don't trust NT time sync, keep track of when we got this packet into robot code
|
||||||
private long ntRecieveTimestampMicros;
|
public long ntReceiveTimestampMicros = -1;
|
||||||
|
|
||||||
/** Constructs an empty pipeline result. */
|
/** Constructs an empty pipeline result. */
|
||||||
public PhotonPipelineResult() {}
|
public PhotonPipelineResult() {
|
||||||
|
this(new PhotonPipelineMetadata(), List.of(), Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a pipeline result.
|
* Constructs a pipeline result.
|
||||||
@@ -63,10 +63,10 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
long captureTimestamp,
|
long captureTimestamp,
|
||||||
long publishTimestamp,
|
long publishTimestamp,
|
||||||
List<PhotonTrackedTarget> targets) {
|
List<PhotonTrackedTarget> targets) {
|
||||||
this.captureTimestampMicros = captureTimestamp;
|
this(
|
||||||
this.publishTimestampMicros = publishTimestamp;
|
new PhotonPipelineMetadata(captureTimestamp, publishTimestamp, sequenceID),
|
||||||
this.sequenceID = sequenceID;
|
targets,
|
||||||
this.targets.addAll(targets);
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,12 +85,20 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
long captureTimestamp,
|
long captureTimestamp,
|
||||||
long publishTimestamp,
|
long publishTimestamp,
|
||||||
List<PhotonTrackedTarget> targets,
|
List<PhotonTrackedTarget> targets,
|
||||||
MultiTargetPNPResult result) {
|
Optional<MultiTargetPNPResult> result) {
|
||||||
this.captureTimestampMicros = captureTimestamp;
|
this(
|
||||||
this.publishTimestampMicros = publishTimestamp;
|
new PhotonPipelineMetadata(captureTimestamp, publishTimestamp, sequenceID),
|
||||||
this.sequenceID = sequenceID;
|
targets,
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhotonPipelineResult(
|
||||||
|
PhotonPipelineMetadata metadata,
|
||||||
|
List<PhotonTrackedTarget> targets,
|
||||||
|
Optional<MultiTargetPNPResult> result) {
|
||||||
|
this.metadata = metadata;
|
||||||
this.targets.addAll(targets);
|
this.targets.addAll(targets);
|
||||||
this.multiTagResult = result;
|
this.multitagResult = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,10 +107,11 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
* @return The size of the packet needed to store this pipeline result.
|
* @return The size of the packet needed to store this pipeline result.
|
||||||
*/
|
*/
|
||||||
public int getPacketSize() {
|
public int getPacketSize() {
|
||||||
return Double.BYTES // latency
|
throw new RuntimeException("TODO");
|
||||||
+ 1 // target count
|
// return Double.BYTES // latency
|
||||||
+ targets.size() * PhotonTrackedTarget.serde.getMaxByteSize()
|
// + 1 // target count
|
||||||
+ MultiTargetPNPResult.serde.getMaxByteSize();
|
// + targets.size() * PhotonTrackedTarget.serde.getMaxByteSize()
|
||||||
|
// + MultiTargetPNPResult.serde.getMaxByteSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,50 +133,6 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
return hasTargets() ? targets.get(0) : null;
|
return hasTargets() ? targets.get(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the time between image capture and publish to NT */
|
|
||||||
public double getLatencyMillis() {
|
|
||||||
return (publishTimestampMicros - captureTimestampMicros) / 1e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the estimated time the frame was taken, in the recieved system's time base. This is
|
|
||||||
* calculated as (NT recieve time (robot base) - (publish timestamp, coproc timebase - capture
|
|
||||||
* timestamp, coproc timebase))
|
|
||||||
*
|
|
||||||
* @return The timestamp in seconds
|
|
||||||
*/
|
|
||||||
public double getTimestampSeconds() {
|
|
||||||
return (ntRecieveTimestampMicros - (publishTimestampMicros - captureTimestampMicros)) / 1e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The time that this image was captured, in the coprocessor's time base. */
|
|
||||||
public long getCaptureTimestampMicros() {
|
|
||||||
return captureTimestampMicros;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The time that this result was published to NT, in the coprocessor's time base. */
|
|
||||||
public long getPublishTimestampMicros() {
|
|
||||||
return publishTimestampMicros;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of non-empty frames processed by this camera since boot. Useful to checking if a
|
|
||||||
* camera is alive.
|
|
||||||
*/
|
|
||||||
public long getSequenceID() {
|
|
||||||
return sequenceID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The time that the robot recieved this result, in the FPGA timebase. */
|
|
||||||
public long getNtRecieveTimestampMicros() {
|
|
||||||
return ntRecieveTimestampMicros;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the FPGA timestamp this result was recieved by robot code */
|
|
||||||
public void setRecieveTimestampMicros(long timestampMicros) {
|
|
||||||
this.ntRecieveTimestampMicros = timestampMicros;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the pipeline has targets.
|
* Returns whether the pipeline has targets.
|
||||||
*
|
*
|
||||||
@@ -192,22 +157,54 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
* Return the latest multi-target result. Be sure to check
|
* Return the latest multi-target result. Be sure to check
|
||||||
* getMultiTagResult().estimatedPose.isPresent before using the pose estimate!
|
* getMultiTagResult().estimatedPose.isPresent before using the pose estimate!
|
||||||
*/
|
*/
|
||||||
public MultiTargetPNPResult getMultiTagResult() {
|
public Optional<MultiTargetPNPResult> getMultiTagResult() {
|
||||||
return multiTagResult;
|
return multitagResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the estimated time the frame was taken, in the Received system's time base. This is
|
||||||
|
* calculated as (NT Receive time (robot base) - (publish timestamp, coproc timebase - capture
|
||||||
|
* timestamp, coproc timebase))
|
||||||
|
*
|
||||||
|
* @return The timestamp in seconds
|
||||||
|
*/
|
||||||
|
public double getTimestampSeconds() {
|
||||||
|
return (ntReceiveTimestampMicros
|
||||||
|
- (metadata.publishTimestampMicros - metadata.captureTimestampMicros))
|
||||||
|
/ 1e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The time that the robot Received this result, in the FPGA timebase. */
|
||||||
|
public long getNtReceiveTimestampMicros() {
|
||||||
|
return ntReceiveTimestampMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the FPGA timestamp this result was Received by robot code */
|
||||||
|
public void setReceiveTimestampMicros(long timestampMicros) {
|
||||||
|
this.ntReceiveTimestampMicros = timestampMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PhotonPipelineResult [metadata="
|
||||||
|
+ metadata
|
||||||
|
+ ", targets="
|
||||||
|
+ targets
|
||||||
|
+ ", multitagResult="
|
||||||
|
+ multitagResult
|
||||||
|
+ ", ntReceiveTimestampMicros="
|
||||||
|
+ ntReceiveTimestampMicros
|
||||||
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + (int) (captureTimestampMicros ^ (captureTimestampMicros >>> 32));
|
result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
|
||||||
long temp;
|
|
||||||
temp = Double.doubleToLongBits(publishTimestampMicros);
|
|
||||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
|
||||||
result = prime * result + (int) (sequenceID ^ (sequenceID >>> 32));
|
|
||||||
result = prime * result + ((targets == null) ? 0 : targets.hashCode());
|
result = prime * result + ((targets == null) ? 0 : targets.hashCode());
|
||||||
result = prime * result + ((multiTagResult == null) ? 0 : multiTagResult.hashCode());
|
result = prime * result + ((multitagResult == null) ? 0 : multitagResult.hashCode());
|
||||||
result = prime * result + (int) (ntRecieveTimestampMicros ^ (ntRecieveTimestampMicros >>> 32));
|
result = prime * result + (int) (ntReceiveTimestampMicros ^ (ntReceiveTimestampMicros >>> 32));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,70 +214,24 @@ public class PhotonPipelineResult implements ProtobufSerializable {
|
|||||||
if (obj == null) return false;
|
if (obj == null) return false;
|
||||||
if (getClass() != obj.getClass()) return false;
|
if (getClass() != obj.getClass()) return false;
|
||||||
PhotonPipelineResult other = (PhotonPipelineResult) obj;
|
PhotonPipelineResult other = (PhotonPipelineResult) obj;
|
||||||
if (captureTimestampMicros != other.captureTimestampMicros) return false;
|
if (metadata == null) {
|
||||||
if (Double.doubleToLongBits(publishTimestampMicros)
|
if (other.metadata != null) return false;
|
||||||
!= Double.doubleToLongBits(other.publishTimestampMicros)) return false;
|
} else if (!metadata.equals(other.metadata)) return false;
|
||||||
if (sequenceID != other.sequenceID) return false;
|
|
||||||
if (targets == null) {
|
if (targets == null) {
|
||||||
if (other.targets != null) return false;
|
if (other.targets != null) return false;
|
||||||
} else if (!targets.equals(other.targets)) return false;
|
} else if (!targets.equals(other.targets)) return false;
|
||||||
if (multiTagResult == null) {
|
if (multitagResult == null) {
|
||||||
if (other.multiTagResult != null) return false;
|
if (other.multitagResult != null) return false;
|
||||||
} else if (!multiTagResult.equals(other.multiTagResult)) return false;
|
} else if (!multitagResult.equals(other.multitagResult)) return false;
|
||||||
if (ntRecieveTimestampMicros != other.ntRecieveTimestampMicros) return false;
|
if (ntReceiveTimestampMicros != other.ntReceiveTimestampMicros) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static final PhotonPipelineResultSerde photonStruct = new PhotonPipelineResultSerde();
|
||||||
public String toString() {
|
|
||||||
return "PhotonPipelineResult [captureTimestamp="
|
|
||||||
+ captureTimestampMicros
|
|
||||||
+ ", publishTimestamp="
|
|
||||||
+ publishTimestampMicros
|
|
||||||
+ ", sequenceID="
|
|
||||||
+ sequenceID
|
|
||||||
+ ", targets="
|
|
||||||
+ targets
|
|
||||||
+ ", multiTagResult="
|
|
||||||
+ multiTagResult
|
|
||||||
+ ", ntRecieveTimestamp="
|
|
||||||
+ ntRecieveTimestampMicros
|
|
||||||
+ "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class APacketSerde implements PacketSerde<PhotonPipelineResult> {
|
|
||||||
@Override
|
|
||||||
public int getMaxByteSize() {
|
|
||||||
// This uses dynamic packets so it doesn't matter
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pack(Packet packet, PhotonPipelineResult value) {
|
|
||||||
packet.encode(value.sequenceID);
|
|
||||||
packet.encode(value.captureTimestampMicros);
|
|
||||||
packet.encode(value.publishTimestampMicros);
|
|
||||||
packet.encode((byte) value.targets.size());
|
|
||||||
for (var target : value.targets) PhotonTrackedTarget.serde.pack(packet, target);
|
|
||||||
MultiTargetPNPResult.serde.pack(packet, value.multiTagResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PhotonPipelineResult unpack(Packet packet) {
|
|
||||||
var seq = packet.decodeLong();
|
|
||||||
var cap = packet.decodeLong();
|
|
||||||
var pub = packet.decodeLong();
|
|
||||||
var len = packet.decodeByte();
|
|
||||||
var targets = new ArrayList<PhotonTrackedTarget>(len);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
targets.add(PhotonTrackedTarget.serde.unpack(packet));
|
|
||||||
}
|
|
||||||
var result = MultiTargetPNPResult.serde.unpack(packet);
|
|
||||||
|
|
||||||
return new PhotonPipelineResult(seq, cap, pub, targets, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final APacketSerde serde = new APacketSerde();
|
|
||||||
public static final PhotonPipelineResultProto proto = new PhotonPipelineResultProto();
|
public static final PhotonPipelineResultProto proto = new PhotonPipelineResultProto();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<PhotonPipelineResult> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,32 +19,32 @@ package org.photonvision.targeting;
|
|||||||
|
|
||||||
import edu.wpi.first.math.geometry.Transform3d;
|
import edu.wpi.first.math.geometry.Transform3d;
|
||||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.PhotonTrackedTargetSerde;
|
||||||
import org.photonvision.targeting.proto.PhotonTrackedTargetProto;
|
import org.photonvision.targeting.proto.PhotonTrackedTargetProto;
|
||||||
import org.photonvision.utils.PacketUtils;
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
public class PhotonTrackedTarget implements ProtobufSerializable {
|
public class PhotonTrackedTarget
|
||||||
|
implements ProtobufSerializable, PhotonStructSerializable<PhotonTrackedTarget> {
|
||||||
private static final int MAX_CORNERS = 8;
|
private static final int MAX_CORNERS = 8;
|
||||||
|
|
||||||
private final double yaw;
|
public double yaw;
|
||||||
private final double pitch;
|
public double pitch;
|
||||||
private final double area;
|
public double area;
|
||||||
private final double skew;
|
public double skew;
|
||||||
private final int fiducialId;
|
public int fiducialId;
|
||||||
private final int classId;
|
public int objDetectId;
|
||||||
private final float objDetectConf;
|
public float objDetectConf;
|
||||||
private final Transform3d bestCameraToTarget;
|
public Transform3d bestCameraToTarget;
|
||||||
private final Transform3d altCameraToTarget;
|
public Transform3d altCameraToTarget;
|
||||||
private final double poseAmbiguity;
|
public double poseAmbiguity;
|
||||||
|
|
||||||
// Corners from the min-area rectangle bounding the target
|
// Corners from the min-area rectangle bounding the target
|
||||||
private final List<TargetCorner> minAreaRectCorners;
|
public List<TargetCorner> minAreaRectCorners;
|
||||||
|
|
||||||
// Corners from whatever corner detection method was used
|
// Corners from whatever corner detection method was used
|
||||||
private final List<TargetCorner> detectedCorners;
|
public List<TargetCorner> detectedCorners;
|
||||||
|
|
||||||
/** Construct a tracked target, given exactly 4 corners */
|
/** Construct a tracked target, given exactly 4 corners */
|
||||||
public PhotonTrackedTarget(
|
public PhotonTrackedTarget(
|
||||||
@@ -71,7 +71,7 @@ public class PhotonTrackedTarget implements ProtobufSerializable {
|
|||||||
this.area = area;
|
this.area = area;
|
||||||
this.skew = skew;
|
this.skew = skew;
|
||||||
this.fiducialId = fiducialId;
|
this.fiducialId = fiducialId;
|
||||||
this.classId = classId;
|
this.objDetectId = classId;
|
||||||
this.objDetectConf = objDetectConf;
|
this.objDetectConf = objDetectConf;
|
||||||
this.bestCameraToTarget = pose;
|
this.bestCameraToTarget = pose;
|
||||||
this.altCameraToTarget = altPose;
|
this.altCameraToTarget = altPose;
|
||||||
@@ -80,6 +80,10 @@ public class PhotonTrackedTarget implements ProtobufSerializable {
|
|||||||
this.poseAmbiguity = ambiguity;
|
this.poseAmbiguity = ambiguity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PhotonTrackedTarget() {
|
||||||
|
// TODO Auto-generated constructor stub
|
||||||
|
}
|
||||||
|
|
||||||
public double getYaw() {
|
public double getYaw() {
|
||||||
return yaw;
|
return yaw;
|
||||||
}
|
}
|
||||||
@@ -103,7 +107,7 @@ public class PhotonTrackedTarget implements ProtobufSerializable {
|
|||||||
|
|
||||||
/** Get the object detection class ID number, or -1 if not set. */
|
/** Get the object detection class ID number, or -1 if not set. */
|
||||||
public int getDetectedObjectClassID() {
|
public int getDetectedObjectClassID() {
|
||||||
return classId;
|
return objDetectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,75 +239,11 @@ public class PhotonTrackedTarget implements ProtobufSerializable {
|
|||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class APacketSerde implements PacketSerde<PhotonTrackedTarget> {
|
|
||||||
@Override
|
|
||||||
public int getMaxByteSize() {
|
|
||||||
return Double.BYTES * (5 + 7 + 2 * 4 + 1 + 1 + 4 + 7 + 2 * MAX_CORNERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pack(Packet packet, PhotonTrackedTarget value) {
|
|
||||||
packet.encode(value.yaw);
|
|
||||||
packet.encode(value.pitch);
|
|
||||||
packet.encode(value.area);
|
|
||||||
packet.encode(value.skew);
|
|
||||||
packet.encode(value.fiducialId);
|
|
||||||
packet.encode(value.classId);
|
|
||||||
packet.encode(value.objDetectConf);
|
|
||||||
PacketUtils.packTransform3d(packet, value.bestCameraToTarget);
|
|
||||||
PacketUtils.packTransform3d(packet, value.altCameraToTarget);
|
|
||||||
packet.encode(value.poseAmbiguity);
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
TargetCorner.serde.pack(packet, value.minAreaRectCorners.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.encode((byte) Math.min(value.detectedCorners.size(), Byte.MAX_VALUE));
|
|
||||||
for (TargetCorner targetCorner : value.detectedCorners) {
|
|
||||||
TargetCorner.serde.pack(packet, targetCorner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PhotonTrackedTarget unpack(Packet packet) {
|
|
||||||
var yaw = packet.decodeDouble();
|
|
||||||
var pitch = packet.decodeDouble();
|
|
||||||
var area = packet.decodeDouble();
|
|
||||||
var skew = packet.decodeDouble();
|
|
||||||
var fiducialId = packet.decodeInt();
|
|
||||||
var classId = packet.decodeInt();
|
|
||||||
var objDetectConf = packet.decodeFloat();
|
|
||||||
Transform3d best = PacketUtils.unpackTransform3d(packet);
|
|
||||||
Transform3d alt = PacketUtils.unpackTransform3d(packet);
|
|
||||||
double ambiguity = packet.decodeDouble();
|
|
||||||
|
|
||||||
var minAreaRectCorners = new ArrayList<TargetCorner>(4);
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
minAreaRectCorners.add(TargetCorner.serde.unpack(packet));
|
|
||||||
}
|
|
||||||
|
|
||||||
var len = packet.decodeByte();
|
|
||||||
var detectedCorners = new ArrayList<TargetCorner>(len);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
detectedCorners.add(TargetCorner.serde.unpack(packet));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PhotonTrackedTarget(
|
|
||||||
yaw,
|
|
||||||
pitch,
|
|
||||||
area,
|
|
||||||
skew,
|
|
||||||
fiducialId,
|
|
||||||
classId,
|
|
||||||
objDetectConf,
|
|
||||||
best,
|
|
||||||
alt,
|
|
||||||
ambiguity,
|
|
||||||
minAreaRectCorners,
|
|
||||||
detectedCorners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final APacketSerde serde = new APacketSerde();
|
|
||||||
public static final PhotonTrackedTargetProto proto = new PhotonTrackedTargetProto();
|
public static final PhotonTrackedTargetProto proto = new PhotonTrackedTargetProto();
|
||||||
|
public static final PhotonTrackedTargetSerde photonStruct = new PhotonTrackedTargetSerde();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<PhotonTrackedTarget> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ package org.photonvision.targeting;
|
|||||||
|
|
||||||
import edu.wpi.first.math.geometry.Transform3d;
|
import edu.wpi.first.math.geometry.Transform3d;
|
||||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.PnpResultSerde;
|
||||||
import org.photonvision.targeting.proto.PNPResultProto;
|
import org.photonvision.targeting.proto.PNPResultProto;
|
||||||
import org.photonvision.utils.PacketUtils;
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The best estimated transformation from solvePnP, and possibly an alternate transformation
|
* The best estimated transformation from solvePnP, and possibly an alternate transformation
|
||||||
@@ -33,37 +33,30 @@ import org.photonvision.utils.PacketUtils;
|
|||||||
* <p>Note that the coordinate frame of these transforms depends on the implementing solvePnP
|
* <p>Note that the coordinate frame of these transforms depends on the implementing solvePnP
|
||||||
* method.
|
* method.
|
||||||
*/
|
*/
|
||||||
public class PNPResult implements ProtobufSerializable {
|
public class PnpResult implements ProtobufSerializable, PhotonStructSerializable<PnpResult> {
|
||||||
/**
|
|
||||||
* If this result is valid. A false value indicates there was an error in estimation, and this
|
|
||||||
* result should not be used.
|
|
||||||
*/
|
|
||||||
public final boolean isPresent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The best-fit transform. The coordinate frame of this transform depends on the method which gave
|
* The best-fit transform. The coordinate frame of this transform depends on the method which gave
|
||||||
* this result.
|
* this result.
|
||||||
*/
|
*/
|
||||||
public final Transform3d best;
|
public Transform3d best;
|
||||||
|
|
||||||
/** Reprojection error of the best solution, in pixels */
|
/** Reprojection error of the best solution, in pixels */
|
||||||
public final double bestReprojErr;
|
public double bestReprojErr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternate, ambiguous solution from solvepnp. If no alternate solution is found, this is equal
|
* Alternate, ambiguous solution from solvepnp. If no alternate solution is found, this is equal
|
||||||
* to the best solution.
|
* to the best solution.
|
||||||
*/
|
*/
|
||||||
public final Transform3d alt;
|
public Transform3d alt;
|
||||||
|
|
||||||
/** If no alternate solution is found, this is bestReprojErr */
|
/** If no alternate solution is found, this is bestReprojErr */
|
||||||
public final double altReprojErr;
|
public double altReprojErr;
|
||||||
|
|
||||||
/** If no alternate solution is found, this is 0 */
|
/** If no alternate solution is found, this is 0 */
|
||||||
public final double ambiguity;
|
public double ambiguity;
|
||||||
|
|
||||||
/** An empty (invalid) result. */
|
/** An empty (invalid) result. */
|
||||||
public PNPResult() {
|
public PnpResult() {
|
||||||
this.isPresent = false;
|
|
||||||
this.best = new Transform3d();
|
this.best = new Transform3d();
|
||||||
this.alt = new Transform3d();
|
this.alt = new Transform3d();
|
||||||
this.ambiguity = 0;
|
this.ambiguity = 0;
|
||||||
@@ -71,17 +64,16 @@ public class PNPResult implements ProtobufSerializable {
|
|||||||
this.altReprojErr = 0;
|
this.altReprojErr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PNPResult(Transform3d best, double bestReprojErr) {
|
public PnpResult(Transform3d best, double bestReprojErr) {
|
||||||
this(best, best, 0, bestReprojErr, bestReprojErr);
|
this(best, best, 0, bestReprojErr, bestReprojErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PNPResult(
|
public PnpResult(
|
||||||
Transform3d best,
|
Transform3d best,
|
||||||
Transform3d alt,
|
Transform3d alt,
|
||||||
double ambiguity,
|
double ambiguity,
|
||||||
double bestReprojErr,
|
double bestReprojErr,
|
||||||
double altReprojErr) {
|
double altReprojErr) {
|
||||||
this.isPresent = true;
|
|
||||||
this.best = best;
|
this.best = best;
|
||||||
this.alt = alt;
|
this.alt = alt;
|
||||||
this.ambiguity = ambiguity;
|
this.ambiguity = ambiguity;
|
||||||
@@ -93,7 +85,6 @@ public class PNPResult implements ProtobufSerializable {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + (isPresent ? 1231 : 1237);
|
|
||||||
result = prime * result + ((best == null) ? 0 : best.hashCode());
|
result = prime * result + ((best == null) ? 0 : best.hashCode());
|
||||||
long temp;
|
long temp;
|
||||||
temp = Double.doubleToLongBits(bestReprojErr);
|
temp = Double.doubleToLongBits(bestReprojErr);
|
||||||
@@ -111,8 +102,7 @@ public class PNPResult implements ProtobufSerializable {
|
|||||||
if (this == obj) return true;
|
if (this == obj) return true;
|
||||||
if (obj == null) return false;
|
if (obj == null) return false;
|
||||||
if (getClass() != obj.getClass()) return false;
|
if (getClass() != obj.getClass()) return false;
|
||||||
PNPResult other = (PNPResult) obj;
|
PnpResult other = (PnpResult) obj;
|
||||||
if (isPresent != other.isPresent) return false;
|
|
||||||
if (best == null) {
|
if (best == null) {
|
||||||
if (other.best != null) return false;
|
if (other.best != null) return false;
|
||||||
} else if (!best.equals(other.best)) return false;
|
} else if (!best.equals(other.best)) return false;
|
||||||
@@ -130,9 +120,7 @@ public class PNPResult implements ProtobufSerializable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PNPResult [isPresent="
|
return "PnpResult [best="
|
||||||
+ isPresent
|
|
||||||
+ ", best="
|
|
||||||
+ best
|
+ best
|
||||||
+ ", bestReprojErr="
|
+ ", bestReprojErr="
|
||||||
+ bestReprojErr
|
+ bestReprojErr
|
||||||
@@ -145,42 +133,11 @@ public class PNPResult implements ProtobufSerializable {
|
|||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class APacketSerde implements PacketSerde<PNPResult> {
|
|
||||||
@Override
|
|
||||||
public int getMaxByteSize() {
|
|
||||||
return 1 + (Double.BYTES * 7 * 2) + (Double.BYTES * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pack(Packet packet, PNPResult value) {
|
|
||||||
packet.encode(value.isPresent);
|
|
||||||
|
|
||||||
if (value.isPresent) {
|
|
||||||
PacketUtils.packTransform3d(packet, value.best);
|
|
||||||
PacketUtils.packTransform3d(packet, value.alt);
|
|
||||||
packet.encode(value.bestReprojErr);
|
|
||||||
packet.encode(value.altReprojErr);
|
|
||||||
packet.encode(value.ambiguity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PNPResult unpack(Packet packet) {
|
|
||||||
var present = packet.decodeBoolean();
|
|
||||||
|
|
||||||
if (!present) {
|
|
||||||
return new PNPResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
var best = PacketUtils.unpackTransform3d(packet);
|
|
||||||
var alt = PacketUtils.unpackTransform3d(packet);
|
|
||||||
var bestEr = packet.decodeDouble();
|
|
||||||
var altEr = packet.decodeDouble();
|
|
||||||
var ambiguity = packet.decodeDouble();
|
|
||||||
return new PNPResult(best, alt, ambiguity, bestEr, altEr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final APacketSerde serde = new APacketSerde();
|
|
||||||
public static final PNPResultProto proto = new PNPResultProto();
|
public static final PNPResultProto proto = new PNPResultProto();
|
||||||
|
public static final PnpResultSerde photonStruct = new PnpResultSerde();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<PnpResult> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,23 +19,28 @@ package org.photonvision.targeting;
|
|||||||
|
|
||||||
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
import edu.wpi.first.util.protobuf.ProtobufSerializable;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.photonvision.common.dataflow.structures.Packet;
|
|
||||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||||
|
import org.photonvision.struct.TargetCornerSerde;
|
||||||
import org.photonvision.targeting.proto.TargetCornerProto;
|
import org.photonvision.targeting.proto.TargetCornerProto;
|
||||||
|
import org.photonvision.targeting.serde.PhotonStructSerializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a point in an image at the corner of the minimum-area bounding rectangle, in pixels.
|
* Represents a point in an image at the corner of the minimum-area bounding rectangle, in pixels.
|
||||||
* Origin at the top left, plus-x to the right, plus-y down.
|
* Origin at the top left, plus-x to the right, plus-y down.
|
||||||
*/
|
*/
|
||||||
public class TargetCorner implements ProtobufSerializable {
|
public class TargetCorner implements ProtobufSerializable, PhotonStructSerializable<TargetCorner> {
|
||||||
public final double x;
|
public double x;
|
||||||
public final double y;
|
public double y;
|
||||||
|
|
||||||
public TargetCorner(double cx, double cy) {
|
public TargetCorner(double cx, double cy) {
|
||||||
this.x = cx;
|
this.x = cx;
|
||||||
this.y = cy;
|
this.y = cy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TargetCorner() {
|
||||||
|
this(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@@ -54,24 +59,11 @@ public class TargetCorner implements ProtobufSerializable {
|
|||||||
return "(" + x + "," + y + ')';
|
return "(" + x + "," + y + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class APacketSerde implements PacketSerde<TargetCorner> {
|
|
||||||
@Override
|
|
||||||
public int getMaxByteSize() {
|
|
||||||
return Double.BYTES * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pack(Packet packet, TargetCorner corner) {
|
|
||||||
packet.encode(corner.x);
|
|
||||||
packet.encode(corner.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TargetCorner unpack(Packet packet) {
|
|
||||||
return new TargetCorner(packet.decodeDouble(), packet.decodeDouble());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final APacketSerde serde = new APacketSerde();
|
|
||||||
public static final TargetCornerProto proto = new TargetCornerProto();
|
public static final TargetCornerProto proto = new TargetCornerProto();
|
||||||
|
public static final TargetCornerSerde photonStruct = new TargetCornerSerde();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketSerde<TargetCorner> getSerde() {
|
||||||
|
return photonStruct;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import edu.wpi.first.util.protobuf.Protobuf;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import org.photonvision.proto.Photon.ProtobufMultiTargetPNPResult;
|
import org.photonvision.proto.Photon.ProtobufMultiTargetPNPResult;
|
||||||
import org.photonvision.targeting.MultiTargetPNPResult;
|
import org.photonvision.targeting.MultiTargetPNPResult;
|
||||||
import org.photonvision.targeting.PNPResult;
|
import org.photonvision.targeting.PnpResult;
|
||||||
import us.hebi.quickbuf.Descriptors.Descriptor;
|
import us.hebi.quickbuf.Descriptors.Descriptor;
|
||||||
import us.hebi.quickbuf.RepeatedInt;
|
import us.hebi.quickbuf.RepeatedInt;
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ public class MultiTargetPNPResultProto
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Protobuf<?, ?>[] getNested() {
|
public Protobuf<?, ?>[] getNested() {
|
||||||
return new Protobuf<?, ?>[] {PNPResult.proto};
|
return new Protobuf<?, ?>[] {PnpResult.proto};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,17 +49,17 @@ public class MultiTargetPNPResultProto
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultiTargetPNPResult unpack(ProtobufMultiTargetPNPResult msg) {
|
public MultiTargetPNPResult unpack(ProtobufMultiTargetPNPResult msg) {
|
||||||
ArrayList<Integer> fidIdsUsed = new ArrayList<>(msg.getFiducialIdsUsed().length());
|
ArrayList<Short> fidIdsUsed = new ArrayList<>(msg.getFiducialIdsUsed().length());
|
||||||
for (var packedFidId : msg.getFiducialIdsUsed()) {
|
for (var packedFidId : msg.getFiducialIdsUsed()) {
|
||||||
fidIdsUsed.add(packedFidId);
|
fidIdsUsed.add(packedFidId.shortValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MultiTargetPNPResult(PNPResult.proto.unpack(msg.getEstimatedPose()), fidIdsUsed);
|
return new MultiTargetPNPResult(PnpResult.proto.unpack(msg.getEstimatedPose()), fidIdsUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pack(ProtobufMultiTargetPNPResult msg, MultiTargetPNPResult value) {
|
public void pack(ProtobufMultiTargetPNPResult msg, MultiTargetPNPResult value) {
|
||||||
PNPResult.proto.pack(msg.getMutableEstimatedPose(), value.estimatedPose);
|
PnpResult.proto.pack(msg.getMutableEstimatedPose(), value.estimatedPose);
|
||||||
|
|
||||||
RepeatedInt idsUsed = msg.getMutableFiducialIdsUsed().reserve(value.fiducialIDsUsed.size());
|
RepeatedInt idsUsed = msg.getMutableFiducialIdsUsed().reserve(value.fiducialIDsUsed.size());
|
||||||
for (int i = 0; i < value.fiducialIDsUsed.size(); i++) {
|
for (int i = 0; i < value.fiducialIDsUsed.size(); i++) {
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ package org.photonvision.targeting.proto;
|
|||||||
import edu.wpi.first.math.geometry.Transform3d;
|
import edu.wpi.first.math.geometry.Transform3d;
|
||||||
import edu.wpi.first.util.protobuf.Protobuf;
|
import edu.wpi.first.util.protobuf.Protobuf;
|
||||||
import org.photonvision.proto.Photon.ProtobufPNPResult;
|
import org.photonvision.proto.Photon.ProtobufPNPResult;
|
||||||
import org.photonvision.targeting.PNPResult;
|
import org.photonvision.targeting.PnpResult;
|
||||||
import us.hebi.quickbuf.Descriptors.Descriptor;
|
import us.hebi.quickbuf.Descriptors.Descriptor;
|
||||||
|
|
||||||
public class PNPResultProto implements Protobuf<PNPResult, ProtobufPNPResult> {
|
public class PNPResultProto implements Protobuf<PnpResult, ProtobufPNPResult> {
|
||||||
@Override
|
@Override
|
||||||
public Class<PNPResult> getTypeClass() {
|
public Class<PnpResult> getTypeClass() {
|
||||||
return PNPResult.class;
|
return PnpResult.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,12 +45,8 @@ public class PNPResultProto implements Protobuf<PNPResult, ProtobufPNPResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PNPResult unpack(ProtobufPNPResult msg) {
|
public PnpResult unpack(ProtobufPNPResult msg) {
|
||||||
if (!msg.getIsPresent()) {
|
return new PnpResult(
|
||||||
return new PNPResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PNPResult(
|
|
||||||
Transform3d.proto.unpack(msg.getBest()),
|
Transform3d.proto.unpack(msg.getBest()),
|
||||||
Transform3d.proto.unpack(msg.getAlt()),
|
Transform3d.proto.unpack(msg.getAlt()),
|
||||||
msg.getAmbiguity(),
|
msg.getAmbiguity(),
|
||||||
@@ -59,12 +55,11 @@ public class PNPResultProto implements Protobuf<PNPResult, ProtobufPNPResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pack(ProtobufPNPResult msg, PNPResult value) {
|
public void pack(ProtobufPNPResult msg, PnpResult value) {
|
||||||
Transform3d.proto.pack(msg.getMutableBest(), value.best);
|
Transform3d.proto.pack(msg.getMutableBest(), value.best);
|
||||||
Transform3d.proto.pack(msg.getMutableAlt(), value.alt);
|
Transform3d.proto.pack(msg.getMutableAlt(), value.alt);
|
||||||
msg.setAmbiguity(value.ambiguity)
|
msg.setAmbiguity(value.ambiguity)
|
||||||
.setBestReprojErr(value.bestReprojErr)
|
.setBestReprojErr(value.bestReprojErr)
|
||||||
.setAltReprojErr(value.altReprojErr)
|
.setAltReprojErr(value.altReprojErr);
|
||||||
.setIsPresent(value.isPresent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user