Skip to content

Commit a56f642

Browse files
committed
pose estimation cleaning and documentation
1 parent ad634fe commit a56f642

File tree

4 files changed

+59
-202
lines changed

4 files changed

+59
-202
lines changed

src/main/java/frc/robot/Constants.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import edu.wpi.first.apriltag.AprilTagFieldLayout;
44
import edu.wpi.first.apriltag.AprilTagFields;
5+
import edu.wpi.first.math.VecBuilder;
6+
import edu.wpi.first.math.Vector;
57
import edu.wpi.first.math.geometry.Rotation3d;
68
import edu.wpi.first.math.geometry.Transform3d;
79
import edu.wpi.first.math.geometry.Translation2d;
810
import edu.wpi.first.math.geometry.Translation3d;
11+
import edu.wpi.first.math.numbers.N3;
912
import edu.wpi.first.math.util.Units;
1013

1114
public class Constants {
@@ -124,8 +127,13 @@ public static final class DriveConstants {
124127
*/
125128
public static AprilTagFieldLayout kFieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2025Reefscape);
126129

127-
public static final class RobotConstants {
128-
130+
public static final class VisionConstants {
131+
/**
132+
* Standard deviations of the pose estimate (x position in meters, y position in
133+
* meters, and heading in radians). Increase these numbers to trust your state
134+
* estimate less.
135+
*/
136+
public static final Vector<N3> kStateStdDevs = VecBuilder.fill(0.05, 0.05, Units.degreesToRadians(5));
129137
/**
130138
* The {@code Transform3d} expressing the pose of the first camera relative to
131139
* the pose of the robot.

src/main/java/frc/robot/Robot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
package frc.robot;
66

77
import static frc.robot.Constants.*;
8-
import static frc.robot.Constants.RobotConstants.*;
8+
import static frc.robot.Constants.VisionConstants.*;
99
import static frc.robot.subsystems.PoseEstimationSubsystem.*;
1010

1111
import java.util.Map;

src/main/java/frc/robot/subsystems/PoseEstimationSubsystem.java

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package frc.robot.subsystems;
22

33
import static frc.robot.Constants.*;
4+
import static frc.robot.Constants.VisionConstants.*;
45

56
import java.util.HashMap;
67
import java.util.Map;
@@ -35,13 +36,6 @@ public class PoseEstimationSubsystem extends SubsystemBase {
3536
private final Map<PhotonCamera, PhotonPoseEstimator> m_cameras = new HashMap<PhotonCamera, PhotonPoseEstimator>();
3637
private final DriveSubsystem m_driveSubsystem;
3738

38-
/**
39-
* Standard deviations of the pose estimate (x position in meters, y position in
40-
* meters, and heading in radians). Increase these numbers to trust your state
41-
* estimate less.
42-
*/
43-
private static final Vector<N3> stateStdDevs = VecBuilder.fill(0.05, 0.05, Units.degreesToRadians(5));
44-
4539
/**
4640
* Standard deviations of the vision pose measurement (x position in meters, y
4741
* position in meters, and heading in radians). Smaller values will cause
@@ -72,7 +66,7 @@ public PoseEstimationSubsystem(DriveSubsystem driveSubsystem) {
7266
driveSubsystem.getHeading(),
7367
driveSubsystem.getModulePositions(),
7468
new Pose2d(),
75-
stateStdDevs,
69+
kStateStdDevs,
7670
visionMeasurementStdDevs);
7771
m_detectedPosePublisher = NetworkTableInstance.getDefault()
7872
.getStructTopic("/SmartDashboard/Pose@PhotonPoseEstimator", Pose2d.struct)
@@ -107,7 +101,7 @@ public void periodic() {
107101
for (var camEntry : m_cameras.entrySet()) {
108102
var camera = camEntry.getKey();
109103
var poseEstimator = camEntry.getValue();
110-
for (var result : camera.getAllUnreadResults()) { // for every result r
104+
for (var result : camera.getAllUnreadResults()) { // for every result
111105
if (firstCamera || result.getTargets().size() > 1) { // TODO: use both camera results equally, don't
112106
// prioritize cam1
113107
Optional<EstimatedRobotPose> pose = poseEstimator.update(result);
@@ -149,67 +143,54 @@ public Pose2d targetOdometryCentricPose(Pose2d fieldCentricPose) {
149143
}
150144

151145
/**
152-
* Finds the {@code Pose2d} of the {@code AprilTag} that is closest to the
153-
* robot.
154-
* Returns null if there is no such {@code AprilTag}).
155-
*
156-
* @param angleOfCoverageInDegrees the angular coverage (in degrees) within
157-
* which {@code AprilTag}s are considered (maximum: 180)
158-
* @param distanceThresholdInMeters the maximum distance (in meters) within
159-
* which {@code AprilTag}s are considered
160-
* @return the {@code Pose2d} of the {@code AprilTag} that is closest to the
161-
* robot ({@code null} if no such {@code AprilTag})
162-
*/
163-
public Pose2d closestTagPose(double angleOfCoverageInDegrees, double distanceThresholdInMeters) {
164-
var i = closestTagID(getEstimatedPose(), angleOfCoverageInDegrees, distanceThresholdInMeters);
165-
return i == null ? null : kFieldLayout.getTagPose(i).get().toPose2d();
166-
}
167-
168-
/**
169-
* Finds the {@code Pose2d} of the {@code AprilTag} that is closest (in terms of
170-
* angular displacement) to the robot when the specified {@code Translation2d}
171-
* is applied to the robot.
146+
* Finds the pose of the {@code AprilTag} that is closest to the robot, either
147+
* in terms of distance or angle (based on user selection).
172148
*
173-
* @param translation a {@code Translation2d}
149+
* @param angleOfCoverage the angular coverage (in degrees) within
150+
* which AprilTags are considered (maximum: 180)
174151
* @param distanceThresholdInMeters the maximum distance (in meters) within
175-
* which {@code AprilTag}s are considered
176-
* @return the {@code Pose2d} of the {@code AprilTag} that is closest (in terms
177-
* of angular displacement) to the robot when the specified
178-
* {@code Translation2d} is applied to the robot
152+
* which AprilTags are considered
153+
* @param isClosestByDistance true for distance-based tag filtering.
154+
* false for angle-based tag filtering.
155+
* @return the {@code Pose2d} of the {@code AprilTag} closest to the robot.
156+
* Returns null if there is no such AprilTag.
157+
* @apiNote
179158
*/
180-
public Pose2d closestTagPose(Translation2d translation, double distanceThresholdInMeters) {
181-
var pose = getEstimatedPose();
182-
if (translation.getNorm() > 0)
183-
pose = new Pose2d(pose.getTranslation(), translation.getAngle());
184-
var i = closestTagID(pose, distanceThresholdInMeters);
185-
return i == null ? null : kFieldLayout.getTagPose(i).get().toPose2d();
159+
public Pose2d closestTagPose(Optional<Double> angleOfCoverage, double distanceThresholdInMeters,
160+
boolean isClosestByDistance) {
161+
var id = (isClosestByDistance)
162+
? distanceClosestTagID( // returns ID of tag closest in terms of distance
163+
getEstimatedPose(), distanceThresholdInMeters, angleOfCoverage)
164+
: angleClosestTagID( // returns ID of tag closest in terms of angular displacement
165+
getEstimatedPose(), distanceThresholdInMeters, angleOfCoverage);
166+
return id == null ? null : kFieldLayout.getTagPose(id).get().toPose2d();
186167
}
187168

188169
/**
189170
* Determines the ID of the {@code AprilTag} that is closest to the specified
190171
* {@code Pose2d} ({@code null} if no such {@code AprilTag}).
191172
*
192173
* @param pose a {@code Pose2d}
193-
* @param angleOfCoverageInDegrees the angular coverage (in degrees) within
174+
* @param angleOfCoverage the angular coverage (in degrees) within
194175
* which {@code AprilTag}s are considered (maximum: 180)
195176
* @param distanceThresholdInMeters the maximum distance (in meters) within
196177
* which {@code AprilTag}s are considered
197178
* @return the ID of the {@code AprilTag} that is closest to the specified
198179
* {@code Pose2d} ({@code null} if no such {@code AprilTag})
199180
*/
200-
public static Integer closestTagID(Pose2d pose, double angleOfCoverageInDegrees, double distanceThresholdInMeters) {
201-
var s = kFieldLayout.getTags().stream()
202-
// consider only the tags facing toward the robot
203-
.filter(
204-
tag -> Math.abs(
205-
tag.pose.getTranslation().toTranslation2d().minus(pose.getTranslation()).getAngle()
206-
.minus(tag.pose.toPose2d().getRotation()).getDegrees()) > 90)
207-
.filter( // consider only the tags within the angle of coverage
208-
tag -> Math.abs(
209-
angularDisplacement(pose, tag.pose.toPose2d()).getDegrees()) < angleOfCoverageInDegrees)
210-
.map(tag -> Map.entry(tag.ID, Math.abs(translationalDisplacement(pose, tag.pose.toPose2d())))) // distance
211-
.filter(tag -> tag.getValue() < distanceThresholdInMeters); // only tags sufficently close
181+
public static Integer distanceClosestTagID(Pose2d pose, double distanceThresholdInMeters,
182+
Optional<Double> angleOfCoverage) {
183+
var s = kFieldLayout.getTags().stream();
184+
if (angleOfCoverage.isPresent()) {
185+
s.filter(
186+
tag -> (translationalDisplacement(pose, tag.pose.toPose2d()) < distanceThresholdInMeters
187+
&& translationalDisplacement(pose, tag.pose.toPose2d()) > 0));
188+
} else {
189+
s.filter(tag -> Math.abs(angularDisplacement(pose, tag.pose.toPose2d()).getDegrees()) < 90);
190+
}
212191
Optional<Entry<Integer, Double>> closest = s
192+
.map(tag -> Map.entry(tag.ID, Math.abs(translationalDisplacement(pose, tag.pose.toPose2d()))))
193+
.filter(tag -> tag.getValue() < distanceThresholdInMeters)
213194
.reduce((tag1, tag2) -> tag1.getValue() < tag2.getValue() ? tag1 : tag2);
214195
if (closest.isPresent()) {
215196
return closest.get().getKey();
@@ -225,19 +206,25 @@ public static Integer closestTagID(Pose2d pose, double angleOfCoverageInDegrees,
225206
* @param pose a {@code Pose2d}
226207
* @param distanceThresholdInMeters the maximum distance (in meters) within
227208
* which {@code AprilTag}s are considered
209+
* @param angleOfCoverage The angle of coverage (in degrees) TODO
228210
* @return the ID of the {@code AprilTag} that is closest (in terms of angular
229211
* displacement) to the specified {@code Pose2d} ({@code null} if no
230212
* such {@code AprilTag})
231213
*/
232-
public static Integer closestTagID(Pose2d pose, double distanceThresholdInMeters) {
233-
var s = kFieldLayout.getTags().stream()
234-
// consider only the tags facing toward the robot
235-
.filter(
236-
tag -> (translationalDisplacement(pose, tag.pose.toPose2d()) < distanceThresholdInMeters
237-
&& translationalDisplacement(pose, tag.pose.toPose2d()) > 0))
238-
// only tags sufficently close
239-
.map(tag -> Map.entry(tag.ID, Math.abs(angularDisplacement(pose, tag.pose.toPose2d()).getDegrees())));
214+
public static Integer angleClosestTagID(Pose2d pose, double distanceThresholdInMeters,
215+
Optional<Double> angleOfCoverage) {
216+
var s = kFieldLayout.getTags().stream();
217+
if (angleOfCoverage.isPresent()) {
218+
s.filter(
219+
tag -> Math
220+
.abs(angularDisplacement(pose, tag.pose.toPose2d()).getDegrees()) < angleOfCoverage
221+
.get());
222+
} else {
223+
s.filter(tag -> Math.abs(angularDisplacement(pose, tag.pose.toPose2d()).getDegrees()) < 90);
224+
}
240225
Optional<Entry<Integer, Double>> closest = s
226+
.map(tag -> Map.entry(tag.ID, Math.abs(translationalDisplacement(pose, tag.pose.toPose2d()))))
227+
.filter(tag -> tag.getValue() < distanceThresholdInMeters)
241228
.reduce((tag1, tag2) -> tag1.getValue() < tag2.getValue() ? tag1 : tag2);
242229
if (closest.isPresent()) {
243230
return closest.get().getKey();

src/main/java/frc/robot/subsystems/VisionSimulator.java

Lines changed: 0 additions & 138 deletions
This file was deleted.

0 commit comments

Comments
 (0)