Skip to content

Vision Subsystem#4

Open
StewardHail3433 wants to merge 18 commits intomainfrom
visionSubsystem
Open

Vision Subsystem#4
StewardHail3433 wants to merge 18 commits intomainfrom
visionSubsystem

Conversation

@StewardHail3433
Copy link
Copy Markdown
Collaborator

The Vision Subsystem.
Includes:

  • Vision Interface
  • multiple cameras
  • PhotonVision and sim implementations
  • validation checks of pose . A measurement is valid
    • 2+ april tag visible to the camera at the time
    • 1 april tag that with a distance less than the maximum
    • the measurement matches april tags with another measurement from a different camera
  • add measurement to drive pose estimation

Copy link
Copy Markdown
Collaborator

@danielcastellarin danielcastellarin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like see full docstrings for your functions, but for now let's just address the individual comments I made

Comment on lines +60 to +63
inputs.targetIds = new int[multitagResult.fiducialIDsUsed.size()];
for (int j = 0; j < inputs.targetIds.length; j++) {
inputs.targetIds[j] = multitagResult.fiducialIDsUsed.get(j).intValue();
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do this in one line. Potential learning opportunity

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learned about java streams and was able to do it with this line in the update method:
inputs.targetIds = multitagResult.fiducialIDsUsed.stream().mapToInt(Short::toUnsignedInt).toArray();

Comment on lines +99 to +121
measurement.robotPose =
cameraPose.transformBy(
(new Transform2d(
robotToCamera.getTranslation().toTranslation2d(),
robotToCamera.getRotation().toRotation2d()))
.inverse());
measurement.targetIds = inputs.targetIds;
measurement.timestamp = result.getTimestampSeconds();

// robot to camera + camera to target = robot to target
measurement.robotToBestTargetDistanceInMeters =
robotToCamera
.plus(result.getBestTarget().getBestCameraToTarget())
.getTranslation()
.getDistance(Translation3d.kZero);

inputs.cameraDistanceToTargetMeters =
result
.getBestTarget()
.getBestCameraToTarget()
.getTranslation()
.getDistance(Translation3d.kZero);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels repetitive from the previous else-if; let's see if we can consolidate them?

Comment on lines +127 to +168
if (VISION_LOGGING_DEBUG) {
for (int cameraIndex = 0; cameraIndex < cameras.size(); cameraIndex++) {
for (int i = 0; i < cameras.get(cameraIndex).getVisionPoseMeasurements().length; i++) {
VisionPoseMeasurement poseMeasurement =
cameras.get(cameraIndex).getVisionPoseMeasurements()[i];
boolean isValid = validPoseMeasurements.contains(poseMeasurement);
String reason = "";

if (poseMeasurement.targetIds.length >= 2) {
reason = "MultiTag with IDs:" + Arrays.toString(poseMeasurement.targetIds);
} else if (poseMeasurement.robotToBestTargetDistanceInMeters != -1
&& poseMeasurement.robotToBestTargetDistanceInMeters
<= MAXIMUM_SINGLE_TAG_DISTANCE_METERS) {
reason =
"Single tag with distance of : "
+ poseMeasurement.robotToBestTargetDistanceInMeters;
} else if (isValid) {
reason = "Cross Tag Check, Same AprilTag as different camera";

} else {
reason =
"Failed to have Single Tag with a match and is greater than min valid distance: "
+ poseMeasurement.robotToBestTargetDistanceInMeters;
}

Logger.recordOutput(
"Vision/"
+ cameras.get(cameraIndex).getName()
+ "/PoseMeasurements/"
+ String.valueOf(i)
+ "/reason",
reason);
Logger.recordOutput(
"Vision/"
+ cameras.get(cameraIndex).getName()
+ "/PoseMeasurements/"
+ String.valueOf(i)
+ "/isValid",
isValid);
}
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to consolidate the logging parts from earlier in this function into here?

}

// add valid pose measurment to consumer
for (int i = 0; i < validPoseMeasurements.size(); i++) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change this to a "for each" loop instead of a "for i". You don't really need the index for anything here

Comment on lines +184 to +197
double currentTime = Timer.getFPGATimestamp();
for (int cameraIndex = 0; cameraIndex < cameraTagPoses.size(); cameraIndex++) {
// the time between one publish and the next
double oneMeasureDurationSeconds = 1.0 / cameras.get(cameraIndex).getCameraSettings().fps;

double measureLifespan = oneMeasureDurationSeconds * 5; // found 5 from testing in sim

Map<Integer, List<VisionPoseMeasurement>> tagMap = cameraTagPoses.get(cameraIndex);

for (List<VisionPoseMeasurement> posesAtSeenTag : tagMap.values()) {
posesAtSeenTag.removeIf(
measurement -> currentTime - measurement.timestamp > measureLifespan);
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder for Dan to look at this because I sleep

Logger.processInputs("Vision/" + camera.getName(), inputs);
camera.update(inputs);

if (visionMeasurementConsumer.isPresent()) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do anything in updateCamera if the consumer doesn't exist?

Copy link
Copy Markdown
Collaborator Author

@StewardHail3433 StewardHail3433 Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it depends on weather we want to use the camera with anything else that is not related to updating pose estimation. My thought was that information could still be gather weather or not we have a consumer.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it stands, the only other functionality photonvision enables is object detection/tracking. I think we can assume we won't be utilizing that (and if some reason we do need it, we can modify the code at that time)

Comment on lines +214 to +230
for (int i = 0; i < poseMeasurements.length; i++) {
for (int j = 0; j < poseMeasurements[i].targetIds.length; j++) {
if (cameraTagPoses.get(cameraIndex).containsKey(poseMeasurements[i].targetIds[j])) {
cameraTagPoses
.get(cameraIndex)
.get(poseMeasurements[i].targetIds[j])
.add(poseMeasurements[i]);
} else {
// make new key if first time tag is being seen
List<VisionPoseMeasurement> poseMeasurementList =
new ArrayList<VisionPoseMeasurement>();
poseMeasurementList.add(poseMeasurements[i]);
cameraTagPoses
.get(cameraIndex)
.put(poseMeasurements[i].targetIds[j], poseMeasurementList);
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder for dan to reread

Comment on lines +33 to +38
public static class VisionPoseMeasurement {
public double timestamp = -1.0;
public Pose2d robotPose = new Pose2d();
public int[] targetIds = new int[0];
public double robotToBestTargetDistanceInMeters = -1.0;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again: why does this class have to be static?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants