From 95eedb2111e0671019382e39a7e2106c3c9e927d Mon Sep 17 00:00:00 2001 From: jasondaming Date: Wed, 8 Oct 2025 16:41:08 -0500 Subject: [PATCH 1/6] Create comprehensive AprilTagFieldLayout documentation article Creates new article documenting AprilTagFieldLayout usage including loading layouts, getting tag poses, using setOrigin(), and common pitfalls. Covers both premade field layouts and custom layouts. Fixes #2253 --- .../apriltag/apriltag-field-layout.rst | 198 ++++++++++++++++++ .../vision-processing/apriltag/index.rst | 1 + 2 files changed, 199 insertions(+) create mode 100644 source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst new file mode 100644 index 0000000000..14c0f3e7dd --- /dev/null +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -0,0 +1,198 @@ +# AprilTag Field Layouts + +The ``AprilTagFieldLayout`` class helps robots understand where AprilTags are located on the field. This is essential for using vision measurements with :doc:`pose estimators ` to determine your robot's position. + +## What is AprilTagFieldLayout? + +``AprilTagFieldLayout`` stores the 3D positions of all AprilTags for a specific game year. Each tag has: + +- A unique **ID number** (e.g., tag 1, tag 2, etc.) +- A **3D pose** (position and rotation) on the field +- The tag's physical **size** + +When your vision system detects a tag, you can look up its field position using the tag's ID, then calculate where your robot must be to see that tag from that angle. + +## Loading a Field Layout + +WPILib provides official field layouts for each game year as JSON files. The easiest way to load a layout is: + +.. tab-set-code:: + + ```java + import edu.wpi.first.apriltag.AprilTagFieldLayout; + import edu.wpi.first.apriltag.AprilTagFields; + + // Load the official field layout for the current year + AprilTagFieldLayout fieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2024Crescendo); + ``` + + ```c++ + #include + #include + + // Load the official field layout for the current year + frc::AprilTagFieldLayout fieldLayout = frc::LoadAprilTagLayoutField(frc::AprilTagField::k2024Crescendo); + ``` + + ```python + from wpilib import AprilTagFieldLayout + from wpilib import AprilTagField + + # Load the official field layout for the current year + field_layout = AprilTagFieldLayout.loadField(AprilTagField.k2024Crescendo) + ``` + +## Setting the Origin + +One of the most common sources of confusion is the **origin location**. FRC fields are symmetric, with identical tag layouts on both the red and blue alliance sides. The field layout needs to know which alliance you're on to give you correct positions. + +.. important:: Always call ``setOrigin()`` with your alliance color before using vision measurements! Forgetting this will cause pose estimates to be wildly incorrect. + +.. tab-set-code:: + + ```java + import edu.wpi.first.wpilibj.DriverStation; + import edu.wpi.first.apriltag.AprilTagFieldLayout.OriginPosition; + + // In robotInit() or robotPeriodic(): + var alliance = DriverStation.getAlliance(); + if (alliance.isPresent()) { + fieldLayout.setOrigin(alliance.get() == DriverStation.Alliance.Blue ? + OriginPosition.kBlueAllianceWallRightSide : + OriginPosition.kRedAllianceWallRightSide); + } + ``` + + ```c++ + #include + + // In RobotInit() or RobotPeriodic(): + auto alliance = frc::DriverStation::GetAlliance(); + if (alliance) { + fieldLayout.SetOrigin(alliance.value() == frc::DriverStation::Alliance::kBlue ? + frc::AprilTagFieldLayout::OriginPosition::kBlueAllianceWallRightSide : + frc::AprilTagFieldLayout::OriginPosition::kRedAllianceWallRightSide); + } + ``` + + ```python + from wpilib import DriverStation + from wpilib import AprilTagFieldLayout + + # In robotInit() or robotPeriodic(): + alliance = DriverStation.getAlliance() + if alliance is not None: + origin = (AprilTagFieldLayout.OriginPosition.kBlueAllianceWallRightSide + if alliance == DriverStation.Alliance.kBlue + else AprilTagFieldLayout.OriginPosition.kRedAllianceWallRightSide) + field_layout.setOrigin(origin) + ``` + +## Using the Field Layout + +Once loaded and configured, you can: + +### Get a Tag's Position + +.. tab-set-code:: + + ```java + // Get the pose of tag 5 + Optional tagPose = fieldLayout.getTagPose(5); + if (tagPose.isPresent()) { + Pose3d pose = tagPose.get(); + // Use the pose... + } + ``` + + ```c++ + // Get the pose of tag 5 + std::optional tagPose = fieldLayout.GetTagPose(5); + if (tagPose) { + frc::Pose3d pose = tagPose.value(); + // Use the pose... + } + ``` + + ```python + # Get the pose of tag 5 + tag_pose = field_layout.getTagPose(5) + if tag_pose is not None: + # Use the pose... + pass + ``` + +### Pass it to Vision Libraries + +Most vision processing libraries (PhotonVision, Limelight) need the field layout to calculate robot poses: + +.. tab-set-code:: + + ```java + // PhotonVision example + PhotonPoseEstimator poseEstimator = new PhotonPoseEstimator( + fieldLayout, + PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR, + camera, + robotToCam + ); + ``` + + ```c++ + // PhotonVision example + photon::PhotonPoseEstimator poseEstimator{ + fieldLayout, + photon::PoseStrategy::MULTI_TAG_PNP_ON_COPROCESSOR, + camera, + robotToCam + }; + ``` + + ```python + # PhotonVision example + from photonlibpy.photonPoseEstimator import PhotonPoseEstimator, PoseStrategy + + pose_estimator = PhotonPoseEstimator( + field_layout, + PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR, + camera, + robot_to_cam + ) + ``` + +## Loading Custom Layouts + +For testing or custom applications, you can load field layouts from a JSON file: + +.. tab-set-code:: + + ```java + // Load from a custom JSON file + AprilTagFieldLayout customLayout = new AprilTagFieldLayout("path/to/layout.json"); + ``` + + ```c++ + // Load from a custom JSON file + frc::AprilTagFieldLayout customLayout{"path/to/layout.json"}; + ``` + + ```python + # Load from a custom JSON file + custom_layout = AprilTagFieldLayout("path/to/layout.json") + ``` + +The JSON format matches the official field layouts. You can find examples in the `WPILib repository `_. + +## Common Pitfalls + +1. **Forgetting to set origin**: This is the #1 cause of incorrect pose estimates. Always call ``setOrigin()`` based on your alliance! +2. **Using wrong year's layout**: Make sure you're loading the layout for the current game year +3. **Not handling optional values**: Tag pose lookups return ``Optional`` / ``std::optional`` / ``None`` - always check before using! +4. **Coordinate system confusion**: The field layout uses field-relative coordinates (blue alliance origin), not robot-relative + +## See Also + +- :doc:`Pose Estimators ` - How to use vision measurements with pose estimation +- :doc:`AprilTag Introduction ` - Understanding AprilTag detection +- `AprilTagFieldLayout API Docs (Java) `_ +- `AprilTagFieldLayout API Docs (C++) `_ diff --git a/source/docs/software/vision-processing/apriltag/index.rst b/source/docs/software/vision-processing/apriltag/index.rst index 06f7e9506b..d8c74aea4a 100644 --- a/source/docs/software/vision-processing/apriltag/index.rst +++ b/source/docs/software/vision-processing/apriltag/index.rst @@ -4,3 +4,4 @@ :maxdepth: 2 apriltag-intro + apriltag-field-layout From f47eef139a0dd0051af609cd2a1cf4187fec58ea Mon Sep 17 00:00:00 2001 From: jasondaming Date: Thu, 9 Oct 2025 21:24:23 -0500 Subject: [PATCH 2/6] Address review feedback on PR #3138 - Clarified field symmetry description (rotational vs mirror) - Added else case to setOrigin() examples to always explicitly set origin - Defaults to blue alliance when not connected to FMS --- .../apriltag/apriltag-field-layout.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst index 14c0f3e7dd..44fadb5fb0 100644 --- a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -44,7 +44,7 @@ WPILib provides official field layouts for each game year as JSON files. The eas ## Setting the Origin -One of the most common sources of confusion is the **origin location**. FRC fields are symmetric, with identical tag layouts on both the red and blue alliance sides. The field layout needs to know which alliance you're on to give you correct positions. +One of the most common sources of confusion is the **origin location**. FRC fields have tag layouts for both alliances. Some years the field has rotational symmetry (where the field can be rotated 180 degrees and tags are in the same positions), while other years have mirror symmetry. The field layout needs to know which alliance you're on to give you correct positions. .. important:: Always call ``setOrigin()`` with your alliance color before using vision measurements! Forgetting this will cause pose estimates to be wildly incorrect. @@ -60,6 +60,9 @@ One of the most common sources of confusion is the **origin location**. FRC fiel fieldLayout.setOrigin(alliance.get() == DriverStation.Alliance.Blue ? OriginPosition.kBlueAllianceWallRightSide : OriginPosition.kRedAllianceWallRightSide); + } else { + // Default to blue alliance if not connected to FMS + fieldLayout.setOrigin(OriginPosition.kBlueAllianceWallRightSide); } ``` @@ -72,6 +75,9 @@ One of the most common sources of confusion is the **origin location**. FRC fiel fieldLayout.SetOrigin(alliance.value() == frc::DriverStation::Alliance::kBlue ? frc::AprilTagFieldLayout::OriginPosition::kBlueAllianceWallRightSide : frc::AprilTagFieldLayout::OriginPosition::kRedAllianceWallRightSide); + } else { + // Default to blue alliance if not connected to FMS + fieldLayout.SetOrigin(frc::AprilTagFieldLayout::OriginPosition::kBlueAllianceWallRightSide); } ``` @@ -86,6 +92,9 @@ One of the most common sources of confusion is the **origin location**. FRC fiel if alliance == DriverStation.Alliance.kBlue else AprilTagFieldLayout.OriginPosition.kRedAllianceWallRightSide) field_layout.setOrigin(origin) + else: + # Default to blue alliance if not connected to FMS + field_layout.setOrigin(AprilTagFieldLayout.OriginPosition.kBlueAllianceWallRightSide) ``` ## Using the Field Layout From 14cd4a65ed9485772477889f320cc4a55eba2d8e Mon Sep 17 00:00:00 2001 From: jasondaming Date: Mon, 13 Oct 2025 20:24:16 -0500 Subject: [PATCH 3/6] Fix deprecated C++ function LoadAprilTagLayoutField MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace deprecated frc::LoadAprilTagLayoutField() with frc::AprilTagFieldLayout::LoadField() per the deprecation warning in the WPILib headers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../vision-processing/apriltag/apriltag-field-layout.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst index 44fadb5fb0..fc8c7518f0 100644 --- a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -31,7 +31,7 @@ WPILib provides official field layouts for each game year as JSON files. The eas #include // Load the official field layout for the current year - frc::AprilTagFieldLayout fieldLayout = frc::LoadAprilTagLayoutField(frc::AprilTagField::k2024Crescendo); + frc::AprilTagFieldLayout fieldLayout = frc::AprilTagFieldLayout::LoadField(frc::AprilTagField::k2024Crescendo); ``` ```python From b282fa13c698937e73735c957e8865270493fc0d Mon Sep 17 00:00:00 2001 From: jasondaming Date: Mon, 13 Oct 2025 20:26:20 -0500 Subject: [PATCH 4/6] Address review comments on AprilTagFieldLayout documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Removed robotInit() from setOrigin() examples (alliance color not available during robotInit) - Added note and link to alliancecolor.rst for getting alliance color safely - Updated comments to suggest robotPeriodic(), autonomousInit(), or teleopInit() - Expanded custom layouts section with: - JSON format specification and example - Deploy directory instructions - Multiple methods for creating layouts (manual, WPICalibrator, programmatic) - Code examples using Filesystem.getDeployDirectory() - Added links to coordinate-system.rst in Common Pitfalls and See Also sections 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../apriltag/apriltag-field-layout.rst | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst index fc8c7518f0..5047500f3c 100644 --- a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -48,13 +48,15 @@ One of the most common sources of confusion is the **origin location**. FRC fiel .. important:: Always call ``setOrigin()`` with your alliance color before using vision measurements! Forgetting this will cause pose estimates to be wildly incorrect. +.. note:: Alliance color is not available during ``robotInit()``. Call ``setOrigin()`` in a periodic method (like ``robotPeriodic()``) or in ``autonomousInit()``/``teleopInit()``. See :doc:`/docs/software/basic-programming/alliancecolor` for more information about getting alliance color safely. + .. tab-set-code:: ```java import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.apriltag.AprilTagFieldLayout.OriginPosition; - // In robotInit() or robotPeriodic(): + // In robotPeriodic(), autonomousInit(), or teleopInit(): var alliance = DriverStation.getAlliance(); if (alliance.isPresent()) { fieldLayout.setOrigin(alliance.get() == DriverStation.Alliance.Blue ? @@ -69,7 +71,7 @@ One of the most common sources of confusion is the **origin location**. FRC fiel ```c++ #include - // In RobotInit() or RobotPeriodic(): + // In RobotPeriodic(), AutonomousInit(), or TeleopInit(): auto alliance = frc::DriverStation::GetAlliance(); if (alliance) { fieldLayout.SetOrigin(alliance.value() == frc::DriverStation::Alliance::kBlue ? @@ -85,7 +87,7 @@ One of the most common sources of confusion is the **origin location**. FRC fiel from wpilib import DriverStation from wpilib import AprilTagFieldLayout - # In robotInit() or robotPeriodic(): + # In robotPeriodic(), autonomousInit(), or teleopInit(): alliance = DriverStation.getAlliance() if alliance is not None: origin = (AprilTagFieldLayout.OriginPosition.kBlueAllianceWallRightSide @@ -171,36 +173,87 @@ Most vision processing libraries (PhotonVision, Limelight) need the field layout ## Loading Custom Layouts -For testing or custom applications, you can load field layouts from a JSON file: +For testing or custom applications, you can create and load custom field layouts from JSON files. + +### Creating Custom Layout Files + +Custom layout files should be placed in your project's ``src/main/deploy`` directory (Java/C++) or ``deploy`` directory (Python). This ensures they are deployed to the roboRIO with your robot code. + +You can create custom layouts by: + +1. **Manually editing JSON**: Copy an existing layout from the `WPILib repository `_ and modify the tag positions +2. **Using WPICalibrator**: The `WPILibPi Raspberry Pi image `_ includes tools for measuring tag positions +3. **Programmatically**: Create layouts in code for testing + +### JSON Format + +The JSON file should contain: + +- ``field``: Object with ``length`` and ``width`` in meters +- ``tags``: Array of tag objects, each with: + + - ``ID``: Integer tag ID + - ``pose``: Object with ``translation`` (x, y, z in meters) and ``rotation`` (quaternion w, x, y, z) + +Example: + +.. code-block:: json + + { + "field": { + "length": 16.54, + "width": 8.21 + }, + "tags": [ + { + "ID": 1, + "pose": { + "translation": { "x": 1.0, "y": 2.0, "z": 0.5 }, + "rotation": { "quaternion": { "W": 1.0, "X": 0.0, "Y": 0.0, "Z": 0.0 } } + } + } + ] + } + +### Loading Custom Layouts .. tab-set-code:: ```java - // Load from a custom JSON file - AprilTagFieldLayout customLayout = new AprilTagFieldLayout("path/to/layout.json"); + import edu.wpi.first.wpilibj.Filesystem; + + // Load from deploy directory + String path = Filesystem.getDeployDirectory().toPath().resolve("custom_layout.json").toString(); + AprilTagFieldLayout customLayout = new AprilTagFieldLayout(path); ``` ```c++ - // Load from a custom JSON file - frc::AprilTagFieldLayout customLayout{"path/to/layout.json"}; + #include + + // Load from deploy directory + std::string path = frc::filesystem::GetDeployDirectory() + "/custom_layout.json"; + frc::AprilTagFieldLayout customLayout{path}; ``` ```python - # Load from a custom JSON file - custom_layout = AprilTagFieldLayout("path/to/layout.json") - ``` + import wpilib -The JSON format matches the official field layouts. You can find examples in the `WPILib repository `_. + # Load from deploy directory + path = wpilib.deployDirectory + "/custom_layout.json" + custom_layout = AprilTagFieldLayout(path) + ``` ## Common Pitfalls 1. **Forgetting to set origin**: This is the #1 cause of incorrect pose estimates. Always call ``setOrigin()`` based on your alliance! 2. **Using wrong year's layout**: Make sure you're loading the layout for the current game year 3. **Not handling optional values**: Tag pose lookups return ``Optional`` / ``std::optional`` / ``None`` - always check before using! -4. **Coordinate system confusion**: The field layout uses field-relative coordinates (blue alliance origin), not robot-relative +4. **Coordinate system confusion**: The field layout uses field-relative coordinates. See :doc:`/docs/software/basic-programming/coordinate-system` for details on the FRC coordinate system. ## See Also +- :doc:`/docs/software/basic-programming/coordinate-system` - Understanding the FRC coordinate system +- :doc:`/docs/software/basic-programming/alliancecolor` - Getting alliance color safely - :doc:`Pose Estimators ` - How to use vision measurements with pose estimation - :doc:`AprilTag Introduction ` - Understanding AprilTag detection - `AprilTagFieldLayout API Docs (Java) `_ From 780ed373d299f9e67a6b8c472014a720a314c620 Mon Sep 17 00:00:00 2001 From: jasondaming Date: Mon, 13 Oct 2025 20:28:16 -0500 Subject: [PATCH 5/6] Update examples to use 2025 Reefscape field layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed from k2024Crescendo to k2025ReefscapeWelded to reflect the current year (2025). Added note explaining the two 2025 field layout options (Welded vs AndyMark) so teams can choose the correct one for their practice field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../vision-processing/apriltag/apriltag-field-layout.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst index 5047500f3c..a1107ac246 100644 --- a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -23,7 +23,7 @@ WPILib provides official field layouts for each game year as JSON files. The eas import edu.wpi.first.apriltag.AprilTagFields; // Load the official field layout for the current year - AprilTagFieldLayout fieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2024Crescendo); + AprilTagFieldLayout fieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2025ReefscapeWelded); ``` ```c++ @@ -31,7 +31,7 @@ WPILib provides official field layouts for each game year as JSON files. The eas #include // Load the official field layout for the current year - frc::AprilTagFieldLayout fieldLayout = frc::AprilTagFieldLayout::LoadField(frc::AprilTagField::k2024Crescendo); + frc::AprilTagFieldLayout fieldLayout = frc::AprilTagFieldLayout::LoadField(frc::AprilTagField::k2025ReefscapeWelded); ``` ```python @@ -39,9 +39,11 @@ WPILib provides official field layouts for each game year as JSON files. The eas from wpilib import AprilTagField # Load the official field layout for the current year - field_layout = AprilTagFieldLayout.loadField(AprilTagField.k2024Crescendo) + field_layout = AprilTagFieldLayout.loadField(AprilTagField.k2025ReefscapeWelded) ``` +.. note:: For 2025 Reefscape, there are two field layout options: ``k2025ReefscapeWelded`` (for welded tube fields) and ``k2025ReefscapeAndyMark`` (for AndyMark tube fields). Use the one that matches your practice field or the official field you're competing on. + ## Setting the Origin One of the most common sources of confusion is the **origin location**. FRC fields have tag layouts for both alliances. Some years the field has rotational symmetry (where the field can be rotated 180 degrees and tags are in the same positions), while other years have mirror symmetry. The field layout needs to know which alliance you're on to give you correct positions. From 455b1d76882eb6d5b625e298e2661552731ee066 Mon Sep 17 00:00:00 2001 From: jasondaming Date: Mon, 13 Oct 2025 21:30:39 -0500 Subject: [PATCH 6/6] Fix duplicate section label causing build failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed '### Loading Custom Layouts' to '### Loading from Deploy Directory' to avoid duplicate labels with the parent section '## Loading Custom Layouts'. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../vision-processing/apriltag/apriltag-field-layout.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst index a1107ac246..6e5974925e 100644 --- a/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst +++ b/source/docs/software/vision-processing/apriltag/apriltag-field-layout.rst @@ -217,7 +217,7 @@ Example: ] } -### Loading Custom Layouts +### Loading from Deploy Directory .. tab-set-code::