mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e613e75db6 | ||
|
|
eca3cea82d | ||
|
|
cbbfbda59d | ||
|
|
a3e1dda3aa | ||
|
|
939283df0e | ||
|
|
43338a4e96 | ||
|
|
bcea6fcc8d | ||
|
|
90773e0e4a | ||
|
|
57f02f31a5 | ||
|
|
580bbb4a4d | ||
|
|
4a0c15b61b | ||
|
|
a1df37e20f | ||
|
|
644c162834 | ||
|
|
5f591a51c4 | ||
|
|
d59be893ae | ||
|
|
f13a507a71 |
283
.github/workflows/build.yml
vendored
283
.github/workflows/build.yml
vendored
@@ -2,123 +2,14 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches:
|
||||||
|
- master
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-client:
|
|
||||||
name: "PhotonClient Build"
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: photon-client
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: Build Production Client
|
|
||||||
run: npm run build
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: built-client
|
|
||||||
path: photon-client/dist/
|
|
||||||
build-examples:
|
|
||||||
name: "Build Examples"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Fetch tags
|
|
||||||
run: git fetch --tags --force
|
|
||||||
- name: Install Java 17
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
# Need to publish to maven local first, so that C++ sim can pick it up
|
|
||||||
# Still haven't figured out how to make the vendordep file be copied before trying to build examples
|
|
||||||
- name: Publish photonlib to maven local
|
|
||||||
run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew publishtomavenlocal -x check
|
|
||||||
- name: Build Java examples
|
|
||||||
working-directory: photonlib-java-examples
|
|
||||||
run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew copyPhotonlib -x check
|
|
||||||
./gradlew build -x check --max-workers 2
|
|
||||||
- name: Build C++ examples
|
|
||||||
working-directory: photonlib-cpp-examples
|
|
||||||
run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew copyPhotonlib -x check
|
|
||||||
./gradlew build -x check --max-workers 2
|
|
||||||
build-gradle:
|
|
||||||
name: "Gradle Build"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
# Checkout code.
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Fetch tags
|
|
||||||
run: git fetch --tags --force
|
|
||||||
- name: Install Java 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
- name: Install mrcal deps
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
|
|
||||||
- name: Gradle Build
|
|
||||||
run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew build -x check --max-workers 2
|
|
||||||
- name: Gradle Tests
|
|
||||||
run: ./gradlew testHeadless -i --max-workers 1 --stacktrace
|
|
||||||
- name: Gradle Coverage
|
|
||||||
run: ./gradlew jacocoTestReport --max-workers 1
|
|
||||||
- name: Publish Coverage Report
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
file: ./photon-server/build/reports/jacoco/test/jacocoTestReport.xml
|
|
||||||
- name: Publish Core Coverage Report
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
file: ./photon-core/build/reports/jacoco/test/jacocoTestReport.xml
|
|
||||||
build-offline-docs:
|
|
||||||
name: "Build Offline Docs"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: 'PhotonVision/photonvision-docs.git'
|
|
||||||
ref: master
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.9'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install sphinx sphinx_rtd_theme sphinx-tabs sphinxext-opengraph doc8
|
|
||||||
pip install -r requirements.txt
|
|
||||||
- name: Build the docs
|
|
||||||
run: |
|
|
||||||
make html
|
|
||||||
- uses: actions/upload-artifact@master
|
|
||||||
with:
|
|
||||||
name: built-docs
|
|
||||||
path: build/html
|
|
||||||
build-photonlib-host:
|
build-photonlib-host:
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 12
|
MACOSX_DEPLOYMENT_TARGET: 12
|
||||||
@@ -188,173 +79,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
build-package:
|
|
||||||
needs: [build-client, build-gradle, build-offline-docs]
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
artifact-name: Win64
|
|
||||||
architecture: x64
|
|
||||||
arch-override: none
|
|
||||||
- os: macos-latest
|
|
||||||
artifact-name: macOS
|
|
||||||
architecture: x64
|
|
||||||
arch-override: none
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: Linux
|
|
||||||
architecture: x64
|
|
||||||
arch-override: none
|
|
||||||
- os: macos-latest
|
|
||||||
artifact-name: macOSArm
|
|
||||||
architecture: x64
|
|
||||||
arch-override: macarm64
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm32
|
|
||||||
architecture: x64
|
|
||||||
arch-override: linuxarm32
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
architecture: x64
|
|
||||||
arch-override: linuxarm64
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
name: "Build fat JAR - ${{ matrix.artifact-name }}"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Java 17
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
- run: |
|
|
||||||
rm -rf photon-server/src/main/resources/web/*
|
|
||||||
mkdir -p photon-server/src/main/resources/web/docs
|
|
||||||
if: ${{ (matrix.os) != 'windows-latest' }}
|
|
||||||
- run: |
|
|
||||||
del photon-server\src\main\resources\web\*.*
|
|
||||||
mkdir photon-server\src\main\resources\web\docs
|
|
||||||
if: ${{ (matrix.os) == 'windows-latest' }}
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: built-client
|
|
||||||
path: photon-server/src/main/resources/web/
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: built-docs
|
|
||||||
path: photon-server/src/main/resources/web/docs
|
|
||||||
- run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew photon-server:shadowJar --max-workers 2 -PArchOverride=${{ matrix.arch-override }}
|
|
||||||
if: ${{ (matrix.arch-override != 'none') }}
|
|
||||||
- run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew photon-server:shadowJar --max-workers 2
|
|
||||||
if: ${{ (matrix.arch-override == 'none') }}
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: jar-${{ matrix.artifact-name }}
|
|
||||||
path: photon-server/build/libs
|
|
||||||
build-image:
|
|
||||||
needs: [build-package]
|
|
||||||
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: RaspberryPi
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.4/photonvision_raspi.img.xz
|
|
||||||
cpu: cortex-a7
|
|
||||||
image_additional_mb: 0
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight2
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.4/photonvision_limelight.img.xz
|
|
||||||
cpu: cortex-a7
|
|
||||||
image_additional_mb: 0
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: limelight3
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.5/photonvision_limelight3.img.xz
|
|
||||||
cpu: cortex-a7
|
|
||||||
image_additional_mb: 0
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.4/photonvision_opi5.img.xz
|
|
||||||
cpu: cortex-a8
|
|
||||||
image_additional_mb: 4096
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
name: "Build image - ${{ matrix.image_url }}"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: jar-${{ matrix.artifact-name }}
|
|
||||||
- uses: pguyot/arm-runner-action@v2
|
|
||||||
name: Generate image
|
|
||||||
id: generate_image
|
|
||||||
with:
|
|
||||||
base_image: ${{ matrix.image_url }}
|
|
||||||
image_additional_mb: ${{ matrix.image_additional_mb }}
|
|
||||||
optimize_image: yes
|
|
||||||
cpu: ${{ matrix.cpu }}
|
|
||||||
# We do _not_ wanna copy photon into the image. Bind mount instead
|
|
||||||
bind_mount_repository: true
|
|
||||||
commands: |
|
|
||||||
chmod +x scripts/armrunner.sh
|
|
||||||
./scripts/armrunner.sh
|
|
||||||
- name: Compress image
|
|
||||||
run: |
|
|
||||||
new_jar=$(realpath $(find . -name photonvision\*-linuxarm64.jar))
|
|
||||||
new_image_name=$(basename "${new_jar/.jar/_${{ matrix.image_suffix }}.img}")
|
|
||||||
mv ${{ steps.generate_image.outputs.image }} $new_image_name
|
|
||||||
sudo xz -T 0 -v $new_image_name
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
name: Upload image
|
|
||||||
with:
|
|
||||||
name: image-${{ matrix.image_suffix }}
|
|
||||||
path: photonvision*.xz
|
|
||||||
release:
|
|
||||||
needs: [build-package, build-image]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
# Download literally every single artifact. This also downloads client and docs,
|
|
||||||
# but the filtering below won't pick these up (I hope)
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
- run: find
|
|
||||||
# Push to dev release
|
|
||||||
- uses: pyTooling/Actions/releaser@r0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: 'Dev'
|
|
||||||
rm: true
|
|
||||||
files: |
|
|
||||||
**/*.xz
|
|
||||||
**/*.jar
|
|
||||||
**/photonlib*.json
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
# Upload all jars and xz archives
|
|
||||||
- uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
**/*.xz
|
|
||||||
**/*.jar
|
|
||||||
**/photonlib*.json
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|||||||
92
.github/workflows/documentation.yml
vendored
92
.github/workflows/documentation.yml
vendored
@@ -1,92 +0,0 @@
|
|||||||
name: Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# For now, run on all commits to master
|
|
||||||
branches: [ master ]
|
|
||||||
# and also all tags starting with v
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-client:
|
|
||||||
name: "PhotonClient Build"
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: photon-client
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: Build Production Client
|
|
||||||
run: npm run build-demo
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: built-client
|
|
||||||
path: photon-client/dist/
|
|
||||||
|
|
||||||
run_docs:
|
|
||||||
runs-on: "ubuntu-22.04"
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Fetch tags
|
|
||||||
run: git fetch --tags --force
|
|
||||||
- name: Install Java 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
|
|
||||||
- name: Build javadocs/doxygen
|
|
||||||
run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew docs:generateJavaDocs docs:doxygen
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: built-docs
|
|
||||||
path: docs/build/docs
|
|
||||||
|
|
||||||
release:
|
|
||||||
needs: [build-client, run_docs]
|
|
||||||
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
|
|
||||||
# Download literally every single artifact.
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- run: find .
|
|
||||||
|
|
||||||
- name: Setup Pages
|
|
||||||
uses: actions/configure-pages@v4
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
# Upload entire repository
|
|
||||||
path: '.'
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
88
.github/workflows/lint-format.yml
vendored
88
.github/workflows/lint-format.yml
vendored
@@ -1,88 +0,0 @@
|
|||||||
name: Lint and Format
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
wpiformat:
|
|
||||||
name: "wpiformat"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Fetch all history and metadata
|
|
||||||
run: |
|
|
||||||
git fetch --prune --unshallow
|
|
||||||
git checkout -b pr
|
|
||||||
git branch -f master origin/master
|
|
||||||
- name: Set up Python 3.8
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: Install wpiformat
|
|
||||||
run: pip3 install wpiformat
|
|
||||||
- name: Run
|
|
||||||
run: wpiformat
|
|
||||||
- name: Check output
|
|
||||||
run: git --no-pager diff --exit-code HEAD
|
|
||||||
- name: Generate diff
|
|
||||||
run: git diff HEAD > wpiformat-fixes.patch
|
|
||||||
if: ${{ failure() }}
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: wpiformat fixes
|
|
||||||
path: wpiformat-fixes.patch
|
|
||||||
if: ${{ failure() }}
|
|
||||||
javaformat:
|
|
||||||
name: "Java Formatting"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: temurin
|
|
||||||
- run: |
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew spotlessCheck
|
|
||||||
|
|
||||||
client-lint-format:
|
|
||||||
name: "PhotonClient Lint and Formatting"
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: photon-client
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: Check Linting
|
|
||||||
run: npm run lint-ci
|
|
||||||
- name: Check Formatting
|
|
||||||
run: npm run format-ci
|
|
||||||
server-index:
|
|
||||||
name: "Check server index.html not changed"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Fetch all history and metadata
|
|
||||||
run: |
|
|
||||||
git fetch --prune --unshallow
|
|
||||||
git checkout -b pr
|
|
||||||
git branch -f master origin/master
|
|
||||||
- name: Check index.html not changed
|
|
||||||
run: git --no-pager diff --exit-code origin/master photon-server/src/main/resources/web/index.html
|
|
||||||
60
.github/workflows/python.yml
vendored
60
.github/workflows/python.yml
vendored
@@ -1,60 +0,0 @@
|
|||||||
name: Build and Distribute PhotonLibPy
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
buildAndDeploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.11
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install setuptools wheel pytest
|
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
working-directory: ./photon-lib/py
|
|
||||||
run: |
|
|
||||||
python setup.py sdist bdist_wheel
|
|
||||||
|
|
||||||
- name: Run Unit Tests
|
|
||||||
working-directory: ./photon-lib/py
|
|
||||||
run: |
|
|
||||||
pip install --no-cache-dir dist/*.whl
|
|
||||||
pytest
|
|
||||||
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@master
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: ./photon-lib/py/dist/
|
|
||||||
|
|
||||||
- name: Publish package distributions to TestPyPI
|
|
||||||
# Only upload on tags
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages_dir: ./photon-lib/py/dist/
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
||||||
13
build.gradle
13
build.gradle
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.diffplug.spotless" version "6.24.0"
|
id "com.diffplug.spotless" version "6.24.0"
|
||||||
id "edu.wpi.first.NativeUtils" version "2024.6.1" apply false
|
id "edu.wpi.first.NativeUtils" version "2024.6.1" apply false
|
||||||
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
|
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
|
||||||
id "edu.wpi.first.GradleRIO" version "2024.1.1"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
id 'edu.wpi.first.WpilibTools' version '1.3.0'
|
id 'edu.wpi.first.WpilibTools' version '1.3.0'
|
||||||
id 'com.google.protobuf' version '0.9.4' apply false
|
id 'com.google.protobuf' version '0.9.4' apply false
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,9 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url = "https://maven.photonvision.org/repository/internal/" }
|
maven { url = "https://maven.photonvision.org/releases" }
|
||||||
maven { url = "https://maven.photonvision.org/repository/snapshots/" }
|
maven { url = "https://maven.photonvision.org/snapshots" }
|
||||||
|
maven { url = "https://jogamp.org/deployment/maven/" }
|
||||||
}
|
}
|
||||||
wpilibRepositories.addAllReleaseRepositories(it)
|
wpilibRepositories.addAllReleaseRepositories(it)
|
||||||
wpilibRepositories.addAllDevelopmentRepositories(it)
|
wpilibRepositories.addAllDevelopmentRepositories(it)
|
||||||
@@ -24,7 +25,7 @@ allprojects {
|
|||||||
apply from: "versioningHelper.gradle"
|
apply from: "versioningHelper.gradle"
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
wpilibVersion = "2024.1.1"
|
wpilibVersion = "2024.2.1"
|
||||||
wpimathVersion = wpilibVersion
|
wpimathVersion = wpilibVersion
|
||||||
openCVversion = "4.8.0-2"
|
openCVversion = "4.8.0-2"
|
||||||
joglVersion = "2.4.0-rc-20200307"
|
joglVersion = "2.4.0-rc-20200307"
|
||||||
@@ -50,6 +51,10 @@ ext {
|
|||||||
println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName)
|
println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName)
|
||||||
println("Using Wpilib: " + wpilibVersion)
|
println("Using Wpilib: " + wpilibVersion)
|
||||||
println("Using OpenCV: " + openCVversion)
|
println("Using OpenCV: " + openCVversion)
|
||||||
|
|
||||||
|
|
||||||
|
photonMavenURL = 'https://maven.photonvision.org/' + (isDev ? 'snapshots' : 'releases');
|
||||||
|
println("Publishing Photonlib to " + photonMavenURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
/>
|
/>
|
||||||
<pv-number-input
|
<pv-number-input
|
||||||
v-model="patternWidth"
|
v-model="patternWidth"
|
||||||
label="Board Width (in)"
|
label="Board Width (squares)"
|
||||||
tooltip="Width of the board in dots or chessboard squares"
|
tooltip="Width of the board in dots or chessboard squares"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
|
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
|
||||||
@@ -319,7 +319,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
/>
|
/>
|
||||||
<pv-number-input
|
<pv-number-input
|
||||||
v-model="patternHeight"
|
v-model="patternHeight"
|
||||||
label="Board Height (in)"
|
label="Board Height (squares)"
|
||||||
tooltip="Height of the board in dots or chessboard squares"
|
tooltip="Height of the board in dots or chessboard squares"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
|
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const changeCurrentCameraIndex = (index: number) => {
|
|||||||
break;
|
break;
|
||||||
case PipelineType.ObjectDetection:
|
case PipelineType.ObjectDetection:
|
||||||
pipelineType.value = WebsocketPipelineType.ObjectDetection;
|
pipelineType.value = WebsocketPipelineType.ObjectDetection;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,6 +125,18 @@ const cancelPipelineNameEdit = () => {
|
|||||||
const showPipelineCreationDialog = ref(false);
|
const showPipelineCreationDialog = ref(false);
|
||||||
const newPipelineName = ref("");
|
const newPipelineName = ref("");
|
||||||
const newPipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().currentWebsocketPipelineType);
|
const newPipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().currentWebsocketPipelineType);
|
||||||
|
const validNewPipelineTypes = computed(() => {
|
||||||
|
const pipelineTypes = [
|
||||||
|
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||||
|
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||||
|
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
||||||
|
{ name: "Aruco", value: WebsocketPipelineType.Aruco }
|
||||||
|
];
|
||||||
|
if (useSettingsStore().general.rknnSupported) {
|
||||||
|
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
||||||
|
}
|
||||||
|
return pipelineTypes;
|
||||||
|
});
|
||||||
const showCreatePipelineDialog = () => {
|
const showCreatePipelineDialog = () => {
|
||||||
newPipelineName.value = "";
|
newPipelineName.value = "";
|
||||||
newPipelineType.value = useCameraSettingsStore().currentWebsocketPipelineType;
|
newPipelineType.value = useCameraSettingsStore().currentWebsocketPipelineType;
|
||||||
@@ -359,13 +372,7 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
|
|||||||
:select-cols="12 - 3"
|
:select-cols="12 - 3"
|
||||||
label="Tracking Type"
|
label="Tracking Type"
|
||||||
tooltip="Pipeline type, which changes the type of processing that will happen on input frames"
|
tooltip="Pipeline type, which changes the type of processing that will happen on input frames"
|
||||||
:items="[
|
:items="validNewPipelineTypes"
|
||||||
{ name: 'Reflective', value: WebsocketPipelineType.Reflective },
|
|
||||||
{ name: 'Colored Shape', value: WebsocketPipelineType.ColoredShape },
|
|
||||||
{ name: 'AprilTag', value: WebsocketPipelineType.AprilTag },
|
|
||||||
{ name: 'Aruco', value: WebsocketPipelineType.Aruco },
|
|
||||||
{ name: 'Object Detection', value: WebsocketPipelineType.ObjectDetection }
|
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider />
|
<v-divider />
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ const currentPipelineSettings = computed<ActivePipelineSettings>(
|
|||||||
() => useCameraSettingsStore().currentPipelineSettings
|
() => useCameraSettingsStore().currentPipelineSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ const currentPipelineSettings = computed<ActivePipelineSettings>(
|
|||||||
() => useCameraSettingsStore().currentPipelineSettings
|
() => useCameraSettingsStore().currentPipelineSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -49,13 +49,12 @@ const contourRadius = computed<[number, number]>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -63,13 +63,12 @@ const handleStreamResolutionChange = (value: number) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { PipelineType } from "@/types/PipelineTypes";
|
import { type ActivePipelineSettings, PipelineType } from "@/types/PipelineTypes";
|
||||||
import PvSlider from "@/components/common/pv-slider.vue";
|
import PvSlider from "@/components/common/pv-slider.vue";
|
||||||
import { computed, getCurrentInstance } from "vue";
|
import { computed, getCurrentInstance } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
|
|
||||||
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
|
||||||
// Defer reference to store access method
|
// Defer reference to store access method
|
||||||
const currentPipelineSettings = useCameraSettingsStore().currentPipelineSettings;
|
const currentPipelineSettings = computed<ActivePipelineSettings>(
|
||||||
|
() => useCameraSettingsStore().currentPipelineSettings
|
||||||
|
);
|
||||||
|
|
||||||
const interactiveCols = computed(
|
// TODO fix pv-range-slider so that store access doesn't need to be deferred
|
||||||
() =>
|
const contourArea = computed<[number, number]>({
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.contourArea) as [number, number],
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourArea = v)
|
||||||
)
|
});
|
||||||
? 9
|
const contourRatio = computed<[number, number]>({
|
||||||
: 8;
|
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.contourRatio) as [number, number],
|
||||||
|
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourRatio = v)
|
||||||
|
});
|
||||||
|
|
||||||
|
const interactiveCols = computed(() =>
|
||||||
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
|
? 9
|
||||||
|
: 8
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -31,5 +42,42 @@ const interactiveCols = computed(
|
|||||||
:step="0.01"
|
:step="0.01"
|
||||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ confidence: value }, false)"
|
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ confidence: value }, false)"
|
||||||
/>
|
/>
|
||||||
|
<pv-range-slider
|
||||||
|
v-model="contourArea"
|
||||||
|
label="Area"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:slider-cols="interactiveCols"
|
||||||
|
:step="0.01"
|
||||||
|
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)"
|
||||||
|
/>
|
||||||
|
<pv-range-slider
|
||||||
|
v-model="contourRatio"
|
||||||
|
label="Ratio (W/H)"
|
||||||
|
tooltip="Min and max ratio between the width and height of a contour's bounding rectangle"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:slider-cols="interactiveCols"
|
||||||
|
:step="0.01"
|
||||||
|
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)"
|
||||||
|
/>
|
||||||
|
<pv-select
|
||||||
|
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
||||||
|
label="Target Orientation"
|
||||||
|
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
||||||
|
:items="['Portrait', 'Landscape']"
|
||||||
|
:select-cols="interactiveCols"
|
||||||
|
@input="
|
||||||
|
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<pv-select
|
||||||
|
v-model="currentPipelineSettings.contourSortMode"
|
||||||
|
label="Target Sort"
|
||||||
|
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||||
|
:select-cols="interactiveCols"
|
||||||
|
:items="['Largest', 'Smallest', 'Highest', 'Lowest', 'Rightmost', 'Leftmost', 'Centermost']"
|
||||||
|
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,13 +46,12 @@ const currentPipelineSettings = computed<ActivePipelineSettings>(
|
|||||||
() => useCameraSettingsStore().currentPipelineSettings
|
() => useCameraSettingsStore().currentPipelineSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import PvSlider from "@/components/common/pv-slider.vue";
|
|||||||
import { computed, getCurrentInstance } from "vue";
|
import { computed, getCurrentInstance } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -124,13 +124,12 @@ onBeforeUnmount(() => {
|
|||||||
cameraStream.removeEventListener("click", handleStreamClick);
|
cameraStream.removeEventListener("click", handleStreamClick);
|
||||||
});
|
});
|
||||||
|
|
||||||
const interactiveCols = computed(
|
const interactiveCols = computed(() =>
|
||||||
() =>
|
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
? 9
|
||||||
)
|
: 8
|
||||||
? 9
|
);
|
||||||
: 8;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import PvInput from "@/components/common/pv-input.vue";
|
|||||||
import PvRadio from "@/components/common/pv-radio.vue";
|
import PvRadio from "@/components/common/pv-radio.vue";
|
||||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import { NetworkConnectionType, type NetworkSettings } from "@/types/SettingTypes";
|
import { type ConfigurableNetworkSettings, NetworkConnectionType } from "@/types/SettingTypes";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
|
|
||||||
// Copy object to remove reference to store
|
// Copy object to remove reference to store
|
||||||
const tempSettingsStruct = ref<NetworkSettings>(Object.assign({}, useSettingsStore().network));
|
const tempSettingsStruct = ref<ConfigurableNetworkSettings>(Object.assign({}, useSettingsStore().network));
|
||||||
|
|
||||||
const resetTempSettingsStruct = () => {
|
const resetTempSettingsStruct = () => {
|
||||||
tempSettingsStruct.value = Object.assign({}, useSettingsStore().network);
|
tempSettingsStruct.value = Object.assign({}, useSettingsStore().network);
|
||||||
};
|
};
|
||||||
@@ -58,7 +57,6 @@ const settingsHaveChanged = (): boolean => {
|
|||||||
a.runNTServer !== b.runNTServer ||
|
a.runNTServer !== b.runNTServer ||
|
||||||
a.shouldManage !== b.shouldManage ||
|
a.shouldManage !== b.shouldManage ||
|
||||||
a.shouldPublishProto !== b.shouldPublishProto ||
|
a.shouldPublishProto !== b.shouldPublishProto ||
|
||||||
a.canManage !== b.canManage ||
|
|
||||||
a.networkManagerIface !== b.networkManagerIface ||
|
a.networkManagerIface !== b.networkManagerIface ||
|
||||||
a.setStaticCommand !== b.setStaticCommand ||
|
a.setStaticCommand !== b.setStaticCommand ||
|
||||||
a.setDHCPcommand !== b.setDHCPcommand
|
a.setDHCPcommand !== b.setDHCPcommand
|
||||||
@@ -91,7 +89,10 @@ const saveGeneralSettings = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update the local settings cause the backend checked their validity. Assign is to deref value
|
// Update the local settings cause the backend checked their validity. Assign is to deref value
|
||||||
useSettingsStore().network = Object.assign({}, tempSettingsStruct.value);
|
useSettingsStore().network = {
|
||||||
|
...useSettingsStore().network,
|
||||||
|
...Object.assign({}, tempSettingsStruct.value)
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
resetTempSettingsStruct();
|
resetTempSettingsStruct();
|
||||||
@@ -162,42 +163,63 @@ watchEffect(() => {
|
|||||||
The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect.
|
The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect.
|
||||||
</v-banner>
|
</v-banner>
|
||||||
<pv-radio
|
<pv-radio
|
||||||
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-model="tempSettingsStruct.connectionType"
|
v-model="tempSettingsStruct.connectionType"
|
||||||
label="IP Assignment Mode"
|
label="IP Assignment Mode"
|
||||||
tooltip="DHCP will make the radio (router) automatically assign an IP address; this may result in an IP address that changes across reboots. Static IP assignment means that you pick the IP address and it won't change."
|
tooltip="DHCP will make the radio (router) automatically assign an IP address; this may result in an IP address that changes across reboots. Static IP assignment means that you pick the IP address and it won't change."
|
||||||
:input-cols="12 - 4"
|
:input-cols="12 - 4"
|
||||||
:list="['DHCP', 'Static']"
|
:list="['DHCP', 'Static']"
|
||||||
:disabled="!(tempSettingsStruct.shouldManage && tempSettingsStruct.canManage)"
|
:disabled="
|
||||||
|
!tempSettingsStruct.shouldManage ||
|
||||||
|
!useSettingsStore().network.canManage ||
|
||||||
|
useSettingsStore().network.networkingDisabled
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<pv-input
|
<pv-input
|
||||||
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-if="tempSettingsStruct.connectionType === NetworkConnectionType.Static"
|
v-if="tempSettingsStruct.connectionType === NetworkConnectionType.Static"
|
||||||
v-model="tempSettingsStruct.staticIp"
|
v-model="tempSettingsStruct.staticIp"
|
||||||
:input-cols="12 - 4"
|
:input-cols="12 - 4"
|
||||||
label="Static IP"
|
label="Static IP"
|
||||||
:rules="[(v) => isValidIPv4(v) || 'Invalid IPv4 address']"
|
:rules="[(v) => isValidIPv4(v) || 'Invalid IPv4 address']"
|
||||||
:disabled="!(tempSettingsStruct.shouldManage && tempSettingsStruct.canManage)"
|
:disabled="
|
||||||
|
!tempSettingsStruct.shouldManage ||
|
||||||
|
!useSettingsStore().network.canManage ||
|
||||||
|
useSettingsStore().network.networkingDisabled
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<pv-input
|
<pv-input
|
||||||
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-model="tempSettingsStruct.hostname"
|
v-model="tempSettingsStruct.hostname"
|
||||||
label="Hostname"
|
label="Hostname"
|
||||||
:input-cols="12 - 4"
|
:input-cols="12 - 4"
|
||||||
:rules="[(v) => isValidHostname(v) || 'Invalid hostname']"
|
:rules="[(v) => isValidHostname(v) || 'Invalid hostname']"
|
||||||
:disabled="!(tempSettingsStruct.shouldManage && tempSettingsStruct.canManage)"
|
:disabled="
|
||||||
|
!tempSettingsStruct.shouldManage ||
|
||||||
|
!useSettingsStore().network.canManage ||
|
||||||
|
useSettingsStore().network.networkingDisabled
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<v-divider class="pb-3" />
|
<v-divider class="pb-3" />
|
||||||
<span style="font-weight: 700">Advanced Networking</span>
|
<span style="font-weight: 700">Advanced Networking</span>
|
||||||
<pv-switch
|
<pv-switch
|
||||||
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-model="tempSettingsStruct.shouldManage"
|
v-model="tempSettingsStruct.shouldManage"
|
||||||
:disabled="!tempSettingsStruct.canManage"
|
:disabled="!useSettingsStore().network.canManage || useSettingsStore().network.networkingDisabled"
|
||||||
label="Manage Device Networking"
|
label="Manage Device Networking"
|
||||||
tooltip="If enabled, Photon will manage device hostname and network settings."
|
tooltip="If enabled, Photon will manage device hostname and network settings."
|
||||||
:label-cols="4"
|
:label-cols="4"
|
||||||
class="pt-2"
|
class="pt-2"
|
||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
|
v-show="!useSettingsStore().network.networkingDisabled"
|
||||||
v-model="currentNetworkInterfaceIndex"
|
v-model="currentNetworkInterfaceIndex"
|
||||||
label="NetworkManager interface"
|
label="NetworkManager interface"
|
||||||
:disabled="!(tempSettingsStruct.shouldManage && tempSettingsStruct.canManage)"
|
:disabled="
|
||||||
|
!tempSettingsStruct.shouldManage ||
|
||||||
|
!useSettingsStore().network.canManage ||
|
||||||
|
useSettingsStore().network.networkingDisabled
|
||||||
|
"
|
||||||
:select-cols="12 - 4"
|
:select-cols="12 - 4"
|
||||||
tooltip="Name of the interface PhotonVision should manage the IP address of"
|
tooltip="Name of the interface PhotonVision should manage the IP address of"
|
||||||
:items="useSettingsStore().networkInterfaceNames"
|
:items="useSettingsStore().networkInterfaceNames"
|
||||||
@@ -206,7 +228,8 @@ watchEffect(() => {
|
|||||||
v-show="
|
v-show="
|
||||||
!useSettingsStore().networkInterfaceNames.length &&
|
!useSettingsStore().networkInterfaceNames.length &&
|
||||||
tempSettingsStruct.shouldManage &&
|
tempSettingsStruct.shouldManage &&
|
||||||
tempSettingsStruct.canManage
|
useSettingsStore().network.canManage &&
|
||||||
|
!useSettingsStore().network.networkingDisabled
|
||||||
"
|
"
|
||||||
rounded
|
rounded
|
||||||
color="red"
|
color="red"
|
||||||
|
|||||||
@@ -45,9 +45,13 @@ export interface NetworkSettings {
|
|||||||
setStaticCommand?: string;
|
setStaticCommand?: string;
|
||||||
setDHCPcommand?: string;
|
setDHCPcommand?: string;
|
||||||
networkInterfaceNames: NetworkInterfaceType[];
|
networkInterfaceNames: NetworkInterfaceType[];
|
||||||
|
networkingDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfigurableNetworkSettings = Omit<NetworkSettings, "canManage" | "networkInterfaceNames">;
|
export type ConfigurableNetworkSettings = Omit<
|
||||||
|
NetworkSettings,
|
||||||
|
"canManage" | "networkInterfaceNames" | "networkingDisabled"
|
||||||
|
>;
|
||||||
|
|
||||||
export interface LightingSettings {
|
export interface LightingSettings {
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
|||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
additionalData: ['@import "@/assets/styles/variables.scss"', ""].join("\n")
|
additionalData: ["@import \"@/assets/styles/variables.scss\"", ""].join("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ public class ConfigManager {
|
|||||||
private final Thread settingsSaveThread;
|
private final Thread settingsSaveThread;
|
||||||
private long saveRequestTimestamp = -1;
|
private long saveRequestTimestamp = -1;
|
||||||
|
|
||||||
|
// special case flag to disable flushing settings to disk at shutdown. Avoids the jvm shutdown
|
||||||
|
// hook overwriting the settings we just uploaded
|
||||||
|
private boolean flushOnShutdown = true;
|
||||||
|
|
||||||
enum ConfigSaveStrategy {
|
enum ConfigSaveStrategy {
|
||||||
SQL,
|
SQL,
|
||||||
LEGACY,
|
LEGACY,
|
||||||
@@ -303,4 +307,19 @@ public class ConfigManager {
|
|||||||
if (!ret.exists()) ret.mkdirs();
|
if (!ret.exists()) ret.mkdirs();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable flushing settings to disk as part of our JVM exit hook. Used to prevent uploading all
|
||||||
|
* settings from getting its new configs overwritten at program exit and before theyre all loaded.
|
||||||
|
*/
|
||||||
|
public void disableFlushOnShutdown() {
|
||||||
|
this.flushOnShutdown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onJvmExit() {
|
||||||
|
if (flushOnShutdown) {
|
||||||
|
logger.info("Force-flushing settings...");
|
||||||
|
saveToDisk();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.photonvision.PhotonVersion;
|
import org.photonvision.PhotonVersion;
|
||||||
import org.photonvision.common.hardware.Platform;
|
import org.photonvision.common.hardware.Platform;
|
||||||
|
import org.photonvision.common.networking.NetworkManager;
|
||||||
import org.photonvision.common.networking.NetworkUtils;
|
import org.photonvision.common.networking.NetworkUtils;
|
||||||
import org.photonvision.common.util.SerializationUtils;
|
import org.photonvision.common.util.SerializationUtils;
|
||||||
import org.photonvision.jni.RknnDetectorJNI;
|
import org.photonvision.jni.RknnDetectorJNI;
|
||||||
@@ -121,6 +122,7 @@ public class PhotonConfiguration {
|
|||||||
// Hack active interfaces into networkSettings
|
// Hack active interfaces into networkSettings
|
||||||
var netConfigMap = networkConfig.toHashMap();
|
var netConfigMap = networkConfig.toHashMap();
|
||||||
netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllWiredInterfaces());
|
netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllWiredInterfaces());
|
||||||
|
netConfigMap.put("networkingDisabled", NetworkManager.getInstance().networkingIsDisabled);
|
||||||
|
|
||||||
settingsSubmap.put("networkSettings", netConfigMap);
|
settingsSubmap.put("networkSettings", netConfigMap);
|
||||||
|
|
||||||
|
|||||||
@@ -145,8 +145,7 @@ public class HardwareManager {
|
|||||||
logger.info("Shutting down LEDs...");
|
logger.info("Shutting down LEDs...");
|
||||||
if (visionLED != null) visionLED.setState(false);
|
if (visionLED != null) visionLED.setState(false);
|
||||||
|
|
||||||
logger.info("Force-flushing settings...");
|
ConfigManager.getInstance().onJvmExit();
|
||||||
ConfigManager.getInstance().saveToDisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean restartDevice() {
|
public boolean restartDevice() {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.photonvision.common.hardware.metrics.cmds.CmdBase;
|
|||||||
import org.photonvision.common.hardware.metrics.cmds.FileCmds;
|
import org.photonvision.common.hardware.metrics.cmds.FileCmds;
|
||||||
import org.photonvision.common.hardware.metrics.cmds.LinuxCmds;
|
import org.photonvision.common.hardware.metrics.cmds.LinuxCmds;
|
||||||
import org.photonvision.common.hardware.metrics.cmds.PiCmds;
|
import org.photonvision.common.hardware.metrics.cmds.PiCmds;
|
||||||
|
import org.photonvision.common.hardware.metrics.cmds.RK3588Cmds;
|
||||||
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.common.util.ShellExec;
|
import org.photonvision.common.util.ShellExec;
|
||||||
@@ -44,6 +45,8 @@ public class MetricsManager {
|
|||||||
cmds = new FileCmds();
|
cmds = new FileCmds();
|
||||||
} else if (Platform.isRaspberryPi()) {
|
} else if (Platform.isRaspberryPi()) {
|
||||||
cmds = new PiCmds(); // Pi's can use a hardcoded command set
|
cmds = new PiCmds(); // Pi's can use a hardcoded command set
|
||||||
|
} else if (Platform.isRK3588()) {
|
||||||
|
cmds = new RK3588Cmds(); // RK3588 chipset hardcoded command set
|
||||||
} else if (Platform.isLinux()) {
|
} else if (Platform.isLinux()) {
|
||||||
cmds = new LinuxCmds(); // Linux/Unix platforms assume a nominal command set
|
cmds = new LinuxCmds(); // Linux/Unix platforms assume a nominal command set
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import org.photonvision.common.configuration.HardwareConfig;
|
|||||||
public class LinuxCmds extends CmdBase {
|
public class LinuxCmds extends CmdBase {
|
||||||
public void initCmds(HardwareConfig config) {
|
public void initCmds(HardwareConfig config) {
|
||||||
// CPU
|
// CPU
|
||||||
cpuMemoryCommand = "free -m | awk 'FNR == 2 {print $3}'";
|
cpuMemoryCommand = "free -m | awk 'FNR == 2 {print $2}'";
|
||||||
|
|
||||||
// TODO: boards have lots of thermal devices. Hard to pick the CPU
|
// TODO: boards have lots of thermal devices. Hard to pick the CPU
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ public class PiCmds extends LinuxCmds {
|
|||||||
super.initCmds(config);
|
super.initCmds(config);
|
||||||
|
|
||||||
// CPU
|
// CPU
|
||||||
cpuMemoryCommand = "free -m | awk 'FNR == 2 {print $2}'";
|
|
||||||
cpuTemperatureCommand = "sed 's/.\\{3\\}$/.&/' /sys/class/thermal/thermal_zone0/temp";
|
cpuTemperatureCommand = "sed 's/.\\{3\\}$/.&/' /sys/class/thermal/thermal_zone0/temp";
|
||||||
cpuThrottleReasonCmd =
|
cpuThrottleReasonCmd =
|
||||||
"if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; "
|
"if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; "
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.hardware.metrics.cmds;
|
||||||
|
|
||||||
|
import org.photonvision.common.configuration.HardwareConfig;
|
||||||
|
|
||||||
|
public class RK3588Cmds extends LinuxCmds {
|
||||||
|
/** Applies pi-specific commands, ignoring any input configuration */
|
||||||
|
public void initCmds(HardwareConfig config) {
|
||||||
|
super.initCmds(config);
|
||||||
|
|
||||||
|
// CPU Temperature
|
||||||
|
/* The RK3588 chip has 7 thermal zones that can be accessed via:
|
||||||
|
* /sys/class/thermal/thermal_zoneX/temp
|
||||||
|
* where X is an interger from 0 to 6.
|
||||||
|
*
|
||||||
|
* || Zone || Location || Comments ||
|
||||||
|
* | 0 | soc | soc thermal (near the center of the chip) |
|
||||||
|
* | 1 | bigcore0 | CPU Big Core A76_0/1 (CPU4 and CPU5) |
|
||||||
|
* | 2 | bigcore1 | CPU Big Core A76_2/3 (CPU6 and CPU7) |
|
||||||
|
* | 3 | littlecore | CPU Small Core A55_0/1/2/3 (CPU0, CPU1, CPU2, and CPU3) |
|
||||||
|
* | 4 | center | also called PD_CENTER |
|
||||||
|
* | 5 | gpu | GPU |
|
||||||
|
* | 6 | npu | NPU |
|
||||||
|
*
|
||||||
|
* Sources:
|
||||||
|
* - http://forum.armsom.org/t/topic/51/3
|
||||||
|
* - https://lore.kernel.org/lkml/7276280.TLKafQO6qx@archbook/
|
||||||
|
*/
|
||||||
|
cpuTemperatureCommand =
|
||||||
|
"cat /sys/class/thermal/thermal_zone1/temp | awk '{printf \"%.1f\", $1/1000}'";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,16 @@ public class Contour implements Releasable {
|
|||||||
this.mat = mat;
|
this.mat = mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contour(Rect2d box) {
|
||||||
|
// no easy way to convert a Rect2d to Mat, diy it. Order is tl tr br bl
|
||||||
|
this.mat =
|
||||||
|
new MatOfPoint(
|
||||||
|
box.tl(),
|
||||||
|
new Point(box.x + box.width, box.y),
|
||||||
|
box.br(),
|
||||||
|
new Point(box.x, box.y + box.height));
|
||||||
|
}
|
||||||
|
|
||||||
public MatOfPoint2f getMat2f() {
|
public MatOfPoint2f getMat2f() {
|
||||||
if (mat2f == null) {
|
if (mat2f == null) {
|
||||||
mat2f = new MatOfPoint2f(mat.toArray());
|
mat2f = new MatOfPoint2f(mat.toArray());
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ public enum ContourSortMode {
|
|||||||
Comparator.comparingDouble(PotentialTarget::getArea)
|
Comparator.comparingDouble(PotentialTarget::getArea)
|
||||||
.reversed()), // reversed so that zero index has the largest size
|
.reversed()), // reversed so that zero index has the largest size
|
||||||
Smallest(Largest.getComparator().reversed()),
|
Smallest(Largest.getComparator().reversed()),
|
||||||
Highest(Comparator.comparingDouble(rect -> rect.getMinAreaRect().center.y)),
|
Highest(Comparator.comparingDouble(tgt -> tgt.getMinAreaRect().center.y)),
|
||||||
Lowest(Highest.getComparator().reversed()),
|
Lowest(Highest.getComparator().reversed()),
|
||||||
Leftmost(Comparator.comparingDouble(target -> target.getMinAreaRect().center.x * -1)),
|
Leftmost(Comparator.comparingDouble(tgt -> tgt.getMinAreaRect().center.x * -1)),
|
||||||
Rightmost(Leftmost.getComparator().reversed()),
|
Rightmost(Leftmost.getComparator().reversed()),
|
||||||
Centermost(
|
Centermost(
|
||||||
Comparator.comparingDouble(
|
Comparator.comparingDouble(
|
||||||
rect ->
|
tgt ->
|
||||||
(Math.pow(rect.getMinAreaRect().center.y, 2)
|
(Math.pow(tgt.getMinAreaRect().center.y, 2)
|
||||||
+ Math.pow(rect.getMinAreaRect().center.x, 2))));
|
+ Math.pow(tgt.getMinAreaRect().center.x, 2))));
|
||||||
|
|
||||||
private final Comparator<PotentialTarget> m_comparator;
|
private final Comparator<PotentialTarget> m_comparator;
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.util.List;
|
|||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.opencv.core.Mat;
|
import org.opencv.core.Mat;
|
||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
|
import org.opencv.core.Scalar;
|
||||||
import org.opencv.imgproc.Imgproc;
|
import org.opencv.imgproc.Imgproc;
|
||||||
import org.photonvision.common.util.ColorHelper;
|
import org.photonvision.common.util.ColorHelper;
|
||||||
import org.photonvision.vision.frame.FrameDivisor;
|
import org.photonvision.vision.frame.FrameDivisor;
|
||||||
@@ -31,22 +32,44 @@ import org.photonvision.vision.target.TrackedTarget;
|
|||||||
public class DrawCalibrationPipe
|
public class DrawCalibrationPipe
|
||||||
extends MutatingPipe<
|
extends MutatingPipe<
|
||||||
Pair<Mat, List<TrackedTarget>>, DrawCalibrationPipe.DrawCalibrationPipeParams> {
|
Pair<Mat, List<TrackedTarget>>, DrawCalibrationPipe.DrawCalibrationPipeParams> {
|
||||||
|
Scalar[] chessboardColors =
|
||||||
|
new Scalar[] {
|
||||||
|
ColorHelper.colorToScalar(Color.RED, 0.4),
|
||||||
|
ColorHelper.colorToScalar(Color.ORANGE, 0.4),
|
||||||
|
ColorHelper.colorToScalar(Color.GREEN, 0.4),
|
||||||
|
ColorHelper.colorToScalar(Color.BLUE, 0.4),
|
||||||
|
ColorHelper.colorToScalar(Color.MAGENTA, 0.4),
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void process(Pair<Mat, List<TrackedTarget>> in) {
|
protected Void process(Pair<Mat, List<TrackedTarget>> in) {
|
||||||
var image = in.getLeft();
|
var image = in.getLeft();
|
||||||
|
|
||||||
|
var imgSz = image.size();
|
||||||
|
var diag = Math.hypot(imgSz.width, imgSz.height);
|
||||||
|
|
||||||
|
// heuristic: about 4px at a diagonal of 750px, or .5%, 'looks good'. keep it at least 3px at
|
||||||
|
// worst tho
|
||||||
|
int r = (int) Math.max(diag * 4.0 / 750.0, 3);
|
||||||
|
int thickness = (int) Math.max(diag * 1.0 / 600.0, 1);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
for (var target : in.getRight()) {
|
for (var target : in.getRight()) {
|
||||||
for (var c : target.getTargetCorners()) {
|
for (var c : target.getTargetCorners()) {
|
||||||
c =
|
c =
|
||||||
new Point(
|
new Point(
|
||||||
c.x / params.divisor.value.doubleValue(), c.y / params.divisor.value.doubleValue());
|
c.x / params.divisor.value.doubleValue(), c.y / params.divisor.value.doubleValue());
|
||||||
var r = 4;
|
|
||||||
var r2 = r / Math.sqrt(2);
|
var r2 = r / Math.sqrt(2);
|
||||||
var color = ColorHelper.colorToScalar(Color.RED, 0.4);
|
var color = chessboardColors[i % chessboardColors.length];
|
||||||
Imgproc.circle(image, c, r, color, 1);
|
Imgproc.circle(image, c, r, color, thickness);
|
||||||
Imgproc.line(image, new Point(c.x - r2, c.y - r2), new Point(c.x + r2, c.y + r2), color);
|
Imgproc.line(
|
||||||
Imgproc.line(image, new Point(c.x + r2, c.y - r2), new Point(c.x - r2, c.y + r2), color);
|
image, new Point(c.x - r2, c.y - r2), new Point(c.x + r2, c.y + r2), color, thickness);
|
||||||
|
Imgproc.line(
|
||||||
|
image, new Point(c.x + r2, c.y - r2), new Point(c.x - r2, c.y + r2), color, thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.vision.pipe.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||||
|
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||||
|
import org.photonvision.vision.pipe.CVPipe;
|
||||||
|
|
||||||
|
public class FilterObjectDetectionsPipe
|
||||||
|
extends CVPipe<
|
||||||
|
List<NeuralNetworkPipeResult>,
|
||||||
|
List<NeuralNetworkPipeResult>,
|
||||||
|
FilterObjectDetectionsPipe.FilterContoursParams> {
|
||||||
|
List<NeuralNetworkPipeResult> m_filteredContours = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<NeuralNetworkPipeResult> process(List<NeuralNetworkPipeResult> in) {
|
||||||
|
m_filteredContours.clear();
|
||||||
|
for (var contour : in) {
|
||||||
|
filterContour(contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_filteredContours;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterContour(NeuralNetworkPipeResult contour) {
|
||||||
|
var boc = contour.box;
|
||||||
|
|
||||||
|
// Area filtering
|
||||||
|
double areaPercentage = boc.area() / params.getFrameStaticProperties().imageArea * 100.0;
|
||||||
|
double minAreaPercentage = params.getArea().getFirst();
|
||||||
|
double maxAreaPercentage = params.getArea().getSecond();
|
||||||
|
if (areaPercentage < minAreaPercentage || areaPercentage > maxAreaPercentage) return;
|
||||||
|
|
||||||
|
// Aspect ratio filtering; much simpler since always axis-aligned
|
||||||
|
double aspectRatio = boc.width / boc.height;
|
||||||
|
if (aspectRatio < params.getRatio().getFirst() || aspectRatio > params.getRatio().getSecond())
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_filteredContours.add(contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FilterContoursParams {
|
||||||
|
private final DoubleCouple m_area;
|
||||||
|
private final DoubleCouple m_ratio;
|
||||||
|
private final FrameStaticProperties m_frameStaticProperties;
|
||||||
|
public final boolean isLandscape;
|
||||||
|
|
||||||
|
public FilterContoursParams(
|
||||||
|
DoubleCouple area,
|
||||||
|
DoubleCouple ratio,
|
||||||
|
FrameStaticProperties camProperties,
|
||||||
|
boolean isLandscape) {
|
||||||
|
this.m_area = area;
|
||||||
|
this.m_ratio = ratio;
|
||||||
|
this.m_frameStaticProperties = camProperties;
|
||||||
|
this.isLandscape = isLandscape;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleCouple getArea() {
|
||||||
|
return m_area;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleCouple getRatio() {
|
||||||
|
return m_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameStaticProperties getFrameStaticProperties() {
|
||||||
|
return m_frameStaticProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ public class SortContoursPipe
|
|||||||
if (params.getSortMode() != ContourSortMode.Centermost) {
|
if (params.getSortMode() != ContourSortMode.Centermost) {
|
||||||
m_sortedContours.sort(params.getSortMode().getComparator());
|
m_sortedContours.sort(params.getSortMode().getComparator());
|
||||||
} else {
|
} else {
|
||||||
|
// we need knowledge of camera properties to calculate this distance -- do it ourselves
|
||||||
m_sortedContours.sort(Comparator.comparingDouble(this::calcSquareCenterDistance));
|
m_sortedContours.sort(Comparator.comparingDouble(this::calcSquareCenterDistance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,10 +51,10 @@ public class SortContoursPipe
|
|||||||
m_sortedContours.subList(0, Math.min(in.size(), params.getMaxTargets())));
|
m_sortedContours.subList(0, Math.min(in.size(), params.getMaxTargets())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calcSquareCenterDistance(PotentialTarget rect) {
|
private double calcSquareCenterDistance(PotentialTarget tgt) {
|
||||||
return Math.sqrt(
|
return Math.sqrt(
|
||||||
Math.pow(params.getCamProperties().centerX - rect.getMinAreaRect().center.x, 2)
|
Math.pow(params.getCamProperties().centerX - tgt.getMinAreaRect().center.x, 2)
|
||||||
+ Math.pow(params.getCamProperties().centerY - rect.getMinAreaRect().center.y, 2));
|
+ Math.pow(params.getCamProperties().centerY - tgt.getMinAreaRect().center.y, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SortContoursParams {
|
public static class SortContoursParams {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
|||||||
|
|
||||||
public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelineSettings>
|
public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelineSettings>
|
||||||
implements Releasable {
|
implements Releasable {
|
||||||
|
static final int MAX_MULTI_TARGET_RESULTS = 10;
|
||||||
|
|
||||||
protected S settings;
|
protected S settings;
|
||||||
protected FrameStaticProperties frameStaticProperties;
|
protected FrameStaticProperties frameStaticProperties;
|
||||||
protected QuirkyCamera cameraQuirks;
|
protected QuirkyCamera cameraQuirks;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class ColoredShapePipeline
|
|||||||
SortContoursPipe.SortContoursParams sortContoursParams =
|
SortContoursPipe.SortContoursParams sortContoursParams =
|
||||||
new SortContoursPipe.SortContoursParams(
|
new SortContoursPipe.SortContoursParams(
|
||||||
settings.contourSortMode,
|
settings.contourSortMode,
|
||||||
settings.outputShowMultipleTargets ? 5 : 1,
|
settings.outputShowMultipleTargets ? MAX_MULTI_TARGET_RESULTS : 1,
|
||||||
frameStaticProperties); // TODO don't hardcode?
|
frameStaticProperties); // TODO don't hardcode?
|
||||||
sortContoursPipe.setParams(sortContoursParams);
|
sortContoursPipe.setParams(sortContoursParams);
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,26 @@
|
|||||||
|
|
||||||
package org.photonvision.vision.pipeline;
|
package org.photonvision.vision.pipeline;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.photonvision.vision.frame.Frame;
|
import org.photonvision.vision.frame.Frame;
|
||||||
import org.photonvision.vision.frame.FrameThresholdType;
|
import org.photonvision.vision.frame.FrameThresholdType;
|
||||||
|
import org.photonvision.vision.opencv.DualOffsetValues;
|
||||||
import org.photonvision.vision.pipe.CVPipe.CVPipeResult;
|
import org.photonvision.vision.pipe.CVPipe.CVPipeResult;
|
||||||
import org.photonvision.vision.pipe.impl.*;
|
import org.photonvision.vision.pipe.impl.*;
|
||||||
import org.photonvision.vision.pipe.impl.RknnDetectionPipe.RknnDetectionPipeParams;
|
import org.photonvision.vision.pipe.impl.RknnDetectionPipe.RknnDetectionPipeParams;
|
||||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||||
|
import org.photonvision.vision.target.PotentialTarget;
|
||||||
|
import org.photonvision.vision.target.TargetOrientation;
|
||||||
import org.photonvision.vision.target.TrackedTarget;
|
import org.photonvision.vision.target.TrackedTarget;
|
||||||
import org.photonvision.vision.target.TrackedTarget.TargetCalculationParameters;
|
|
||||||
|
|
||||||
public class ObjectDetectionPipeline
|
public class ObjectDetectionPipeline
|
||||||
extends CVPipeline<CVPipelineResult, ObjectDetectionPipelineSettings> {
|
extends CVPipeline<CVPipelineResult, ObjectDetectionPipelineSettings> {
|
||||||
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
|
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
|
||||||
private final RknnDetectionPipe rknnPipe = new RknnDetectionPipe();
|
private final RknnDetectionPipe rknnPipe = new RknnDetectionPipe();
|
||||||
|
private final SortContoursPipe sortContoursPipe = new SortContoursPipe();
|
||||||
|
private final Collect2dTargetsPipe collect2dTargetsPipe = new Collect2dTargetsPipe();
|
||||||
|
private final FilterObjectDetectionsPipe filterContoursPipe = new FilterObjectDetectionsPipe();
|
||||||
|
|
||||||
private static final FrameThresholdType PROCESSING_TYPE = FrameThresholdType.NONE;
|
private static final FrameThresholdType PROCESSING_TYPE = FrameThresholdType.NONE;
|
||||||
|
|
||||||
@@ -52,6 +57,38 @@ public class ObjectDetectionPipeline
|
|||||||
params.confidence = settings.confidence;
|
params.confidence = settings.confidence;
|
||||||
params.nms = settings.nms;
|
params.nms = settings.nms;
|
||||||
rknnPipe.setParams(params);
|
rknnPipe.setParams(params);
|
||||||
|
|
||||||
|
DualOffsetValues dualOffsetValues =
|
||||||
|
new DualOffsetValues(
|
||||||
|
settings.offsetDualPointA,
|
||||||
|
settings.offsetDualPointAArea,
|
||||||
|
settings.offsetDualPointB,
|
||||||
|
settings.offsetDualPointBArea);
|
||||||
|
|
||||||
|
SortContoursPipe.SortContoursParams sortContoursParams =
|
||||||
|
new SortContoursPipe.SortContoursParams(
|
||||||
|
settings.contourSortMode,
|
||||||
|
settings.outputShowMultipleTargets ? MAX_MULTI_TARGET_RESULTS : 1,
|
||||||
|
frameStaticProperties);
|
||||||
|
sortContoursPipe.setParams(sortContoursParams);
|
||||||
|
|
||||||
|
var filterContoursParams =
|
||||||
|
new FilterObjectDetectionsPipe.FilterContoursParams(
|
||||||
|
settings.contourArea,
|
||||||
|
settings.contourRatio,
|
||||||
|
frameStaticProperties,
|
||||||
|
settings.contourTargetOrientation == TargetOrientation.Landscape);
|
||||||
|
filterContoursPipe.setParams(filterContoursParams);
|
||||||
|
|
||||||
|
Collect2dTargetsPipe.Collect2dTargetsParams collect2dTargetsParams =
|
||||||
|
new Collect2dTargetsPipe.Collect2dTargetsParams(
|
||||||
|
settings.offsetRobotOffsetMode,
|
||||||
|
settings.offsetSinglePoint,
|
||||||
|
dualOffsetValues,
|
||||||
|
settings.contourTargetOffsetPointEdge,
|
||||||
|
settings.contourTargetOrientation,
|
||||||
|
frameStaticProperties);
|
||||||
|
collect2dTargetsPipe.setParams(collect2dTargetsParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,31 +97,35 @@ public class ObjectDetectionPipeline
|
|||||||
|
|
||||||
// ***************** change based on backend ***********************
|
// ***************** change based on backend ***********************
|
||||||
|
|
||||||
CVPipeResult<List<NeuralNetworkPipeResult>> ret = rknnPipe.run(input_frame.colorImage);
|
CVPipeResult<List<NeuralNetworkPipeResult>> rknnResult = rknnPipe.run(input_frame.colorImage);
|
||||||
sumPipeNanosElapsed += ret.nanosElapsed;
|
sumPipeNanosElapsed += rknnResult.nanosElapsed;
|
||||||
List<NeuralNetworkPipeResult> targetList;
|
List<NeuralNetworkPipeResult> targetList;
|
||||||
|
|
||||||
targetList = ret.output;
|
|
||||||
var names = rknnPipe.getClassNames();
|
var names = rknnPipe.getClassNames();
|
||||||
|
|
||||||
input_frame.colorImage.getMat().copyTo(input_frame.processedImage.getMat());
|
input_frame.colorImage.getMat().copyTo(input_frame.processedImage.getMat());
|
||||||
|
|
||||||
// ***************** change based on backend ***********************
|
// ***************** change based on backend ***********************
|
||||||
|
|
||||||
List<TrackedTarget> targets = new ArrayList<>();
|
var filterContoursResult = filterContoursPipe.run(rknnResult.output);
|
||||||
|
sumPipeNanosElapsed += filterContoursResult.nanosElapsed;
|
||||||
|
|
||||||
for (var t : targetList) {
|
CVPipeResult<List<PotentialTarget>> sortContoursResult =
|
||||||
targets.add(
|
sortContoursPipe.run(
|
||||||
new TrackedTarget(
|
filterContoursResult.output.stream()
|
||||||
t,
|
.map(shape -> new PotentialTarget(shape))
|
||||||
new TargetCalculationParameters(
|
.collect(Collectors.toList()));
|
||||||
false, null, null, null, null, frameStaticProperties)));
|
sumPipeNanosElapsed += sortContoursResult.nanosElapsed;
|
||||||
}
|
|
||||||
|
CVPipeResult<List<TrackedTarget>> collect2dTargetsResult =
|
||||||
|
collect2dTargetsPipe.run(sortContoursResult.output);
|
||||||
|
sumPipeNanosElapsed += collect2dTargetsResult.nanosElapsed;
|
||||||
|
|
||||||
var fpsResult = calculateFPSPipe.run(null);
|
var fpsResult = calculateFPSPipe.run(null);
|
||||||
var fps = fpsResult.output;
|
var fps = fpsResult.output;
|
||||||
|
|
||||||
return new CVPipelineResult(sumPipeNanosElapsed, fps, targets, input_frame, names);
|
return new CVPipelineResult(
|
||||||
|
sumPipeNanosElapsed, fps, collect2dTargetsResult.output, input_frame, names);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -64,29 +64,6 @@ public class ReflectivePipeline extends CVPipeline<CVPipelineResult, ReflectiveP
|
|||||||
settings.offsetDualPointB,
|
settings.offsetDualPointB,
|
||||||
settings.offsetDualPointBArea);
|
settings.offsetDualPointBArea);
|
||||||
|
|
||||||
// var rotateImageParams = new
|
|
||||||
// RotateImagePipe.RotateImageParams(settings.inputImageRotationMode);
|
|
||||||
// rotateImagePipe.setParams(rotateImageParams);
|
|
||||||
|
|
||||||
// if (cameraQuirks.hasQuirk(CameraQuirk.PiCam) && LibCameraJNI.isSupported()) {
|
|
||||||
// LibCameraJNI.setThresholds(
|
|
||||||
// settings.hsvHue.getFirst() / 180d,
|
|
||||||
// settings.hsvSaturation.getFirst() / 255d,
|
|
||||||
// settings.hsvValue.getFirst() / 255d,
|
|
||||||
// settings.hsvHue.getSecond() / 180d,
|
|
||||||
// settings.hsvSaturation.getSecond() / 255d,
|
|
||||||
// settings.hsvValue.getSecond() / 255d);
|
|
||||||
// // LibCameraJNI.setInvertHue(settings.hueInverted);
|
|
||||||
// LibCameraJNI.setRotation(settings.inputImageRotationMode.value);
|
|
||||||
// // LibCameraJNI.setShouldCopyColor(settings.inputShouldShow);
|
|
||||||
// } else {
|
|
||||||
// var hsvParams =
|
|
||||||
// new HSVPipe.HSVParams(
|
|
||||||
// settings.hsvHue, settings.hsvSaturation, settings.hsvValue,
|
|
||||||
// settings.hueInverted);
|
|
||||||
// hsvPipe.setParams(hsvParams);
|
|
||||||
// }
|
|
||||||
|
|
||||||
var findContoursParams = new FindContoursPipe.FindContoursParams();
|
var findContoursParams = new FindContoursPipe.FindContoursParams();
|
||||||
findContoursPipe.setParams(findContoursParams);
|
findContoursPipe.setParams(findContoursParams);
|
||||||
|
|
||||||
@@ -113,7 +90,7 @@ public class ReflectivePipeline extends CVPipeline<CVPipelineResult, ReflectiveP
|
|||||||
var sortContoursParams =
|
var sortContoursParams =
|
||||||
new SortContoursPipe.SortContoursParams(
|
new SortContoursPipe.SortContoursParams(
|
||||||
settings.contourSortMode,
|
settings.contourSortMode,
|
||||||
settings.outputShowMultipleTargets ? 8 : 1, // TODO don't hardcode?
|
settings.outputShowMultipleTargets ? MAX_MULTI_TARGET_RESULTS : 1,
|
||||||
frameStaticProperties);
|
frameStaticProperties);
|
||||||
sortContoursPipe.setParams(sortContoursParams);
|
sortContoursPipe.setParams(sortContoursParams);
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,7 @@ public class VisionRunner {
|
|||||||
var pipelineResult = pipeline.run(frame, cameraQuirks);
|
var pipelineResult = pipeline.run(frame, cameraQuirks);
|
||||||
pipelineResultConsumer.accept(pipelineResult);
|
pipelineResultConsumer.accept(pipelineResult);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Exception on loop " + loopCount);
|
logger.error("Exception on loop " + loopCount, ex);
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loopCount++;
|
loopCount++;
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import java.util.List;
|
|||||||
import org.opencv.core.RotatedRect;
|
import org.opencv.core.RotatedRect;
|
||||||
import org.photonvision.vision.opencv.CVShape;
|
import org.photonvision.vision.opencv.CVShape;
|
||||||
import org.photonvision.vision.opencv.Contour;
|
import org.photonvision.vision.opencv.Contour;
|
||||||
|
import org.photonvision.vision.opencv.ContourShape;
|
||||||
import org.photonvision.vision.opencv.Releasable;
|
import org.photonvision.vision.opencv.Releasable;
|
||||||
|
import org.photonvision.vision.pipe.impl.NeuralNetworkPipeResult;
|
||||||
|
|
||||||
public class PotentialTarget implements Releasable {
|
public class PotentialTarget implements Releasable {
|
||||||
|
|
||||||
@@ -29,6 +31,10 @@ public class PotentialTarget implements Releasable {
|
|||||||
public final List<Contour> m_subContours;
|
public final List<Contour> m_subContours;
|
||||||
public final CVShape shape;
|
public final CVShape shape;
|
||||||
|
|
||||||
|
// additional metadata about object detections we need to keep around
|
||||||
|
public final double confidence;
|
||||||
|
public final int clsId;
|
||||||
|
|
||||||
public PotentialTarget(Contour inputContour) {
|
public PotentialTarget(Contour inputContour) {
|
||||||
this(inputContour, List.of());
|
this(inputContour, List.of());
|
||||||
}
|
}
|
||||||
@@ -41,12 +47,26 @@ public class PotentialTarget implements Releasable {
|
|||||||
m_mainContour = inputContour;
|
m_mainContour = inputContour;
|
||||||
m_subContours = new ArrayList<>(subContours);
|
m_subContours = new ArrayList<>(subContours);
|
||||||
this.shape = shape;
|
this.shape = shape;
|
||||||
|
this.clsId = -1;
|
||||||
|
this.confidence = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PotentialTarget(Contour inputContour, CVShape shape) {
|
public PotentialTarget(Contour inputContour, CVShape shape) {
|
||||||
this(inputContour, List.of(), shape);
|
this(inputContour, List.of(), shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PotentialTarget(NeuralNetworkPipeResult det) {
|
||||||
|
this.shape = new CVShape(new Contour(det.box), ContourShape.Quadrilateral);
|
||||||
|
this.m_mainContour = this.shape.getContour();
|
||||||
|
m_subContours = List.of();
|
||||||
|
this.clsId = det.classIdx;
|
||||||
|
this.confidence = det.confidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PotentialTarget(CVShape cvShape) {
|
||||||
|
this(cvShape.getContour(), cvShape);
|
||||||
|
}
|
||||||
|
|
||||||
public RotatedRect getMinAreaRect() {
|
public RotatedRect getMinAreaRect() {
|
||||||
return m_mainContour.getMinAreaRect();
|
return m_mainContour.getMinAreaRect();
|
||||||
}
|
}
|
||||||
@@ -61,7 +81,7 @@ public class PotentialTarget implements Releasable {
|
|||||||
for (var sc : m_subContours) {
|
for (var sc : m_subContours) {
|
||||||
sc.release();
|
sc.release();
|
||||||
}
|
}
|
||||||
m_subContours.clear();
|
if (!m_subContours.isEmpty()) m_subContours.clear();
|
||||||
if (shape != null) shape.release();
|
if (shape != null) shape.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.opencv.core.Mat;
|
|||||||
import org.opencv.core.MatOfPoint;
|
import org.opencv.core.MatOfPoint;
|
||||||
import org.opencv.core.MatOfPoint2f;
|
import org.opencv.core.MatOfPoint2f;
|
||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
import org.opencv.core.Rect2d;
|
|
||||||
import org.opencv.core.RotatedRect;
|
import org.opencv.core.RotatedRect;
|
||||||
import org.photonvision.common.util.SerializationUtils;
|
import org.photonvision.common.util.SerializationUtils;
|
||||||
import org.photonvision.common.util.math.MathUtils;
|
import org.photonvision.common.util.math.MathUtils;
|
||||||
@@ -39,7 +38,6 @@ import org.photonvision.vision.opencv.CVShape;
|
|||||||
import org.photonvision.vision.opencv.Contour;
|
import org.photonvision.vision.opencv.Contour;
|
||||||
import org.photonvision.vision.opencv.DualOffsetValues;
|
import org.photonvision.vision.opencv.DualOffsetValues;
|
||||||
import org.photonvision.vision.opencv.Releasable;
|
import org.photonvision.vision.opencv.Releasable;
|
||||||
import org.photonvision.vision.pipe.impl.NeuralNetworkPipeResult;
|
|
||||||
|
|
||||||
public class TrackedTarget implements Releasable {
|
public class TrackedTarget implements Releasable {
|
||||||
public final Contour m_mainContour;
|
public final Contour m_mainContour;
|
||||||
@@ -76,6 +74,9 @@ public class TrackedTarget implements Releasable {
|
|||||||
this.m_subContours = origTarget.m_subContours;
|
this.m_subContours = origTarget.m_subContours;
|
||||||
this.m_shape = shape;
|
this.m_shape = shape;
|
||||||
calculateValues(params);
|
calculateValues(params);
|
||||||
|
|
||||||
|
this.m_classId = origTarget.clsId;
|
||||||
|
this.m_confidence = origTarget.confidence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackedTarget(
|
public TrackedTarget(
|
||||||
@@ -159,47 +160,6 @@ public class TrackedTarget implements Releasable {
|
|||||||
m_robotOffsetPoint = new Point();
|
m_robotOffsetPoint = new Point();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackedTarget(
|
|
||||||
Rect2d box, int class_id, double confidence, TargetCalculationParameters params) {
|
|
||||||
m_targetOffsetPoint = new Point(box.x + box.width / 2.0, box.y + box.height / 2.0);
|
|
||||||
m_robotOffsetPoint = new Point();
|
|
||||||
|
|
||||||
var yawPitch =
|
|
||||||
TargetCalculations.calculateYawPitch(
|
|
||||||
params.cameraCenterPoint.x,
|
|
||||||
box.x + box.width / 2.0,
|
|
||||||
params.horizontalFocalLength,
|
|
||||||
params.cameraCenterPoint.y,
|
|
||||||
box.y + box.height / 2.0,
|
|
||||||
params.verticalFocalLength);
|
|
||||||
m_yaw = yawPitch.getFirst();
|
|
||||||
m_pitch = yawPitch.getSecond();
|
|
||||||
|
|
||||||
Point[] cornerPoints =
|
|
||||||
new Point[] {
|
|
||||||
// Box.x/y is the top-left corner, not the center
|
|
||||||
new Point(box.x, box.y), // tl
|
|
||||||
new Point(box.x + box.width, box.y), // tr
|
|
||||||
new Point(box.x + box.width, box.y + box.height), // br
|
|
||||||
new Point(box.x, box.y + box.height), // bl
|
|
||||||
};
|
|
||||||
|
|
||||||
m_targetCorners = List.of(cornerPoints);
|
|
||||||
MatOfPoint contourMat = new MatOfPoint(cornerPoints);
|
|
||||||
m_approximateBoundingPolygon = new MatOfPoint2f(cornerPoints);
|
|
||||||
|
|
||||||
m_mainContour = new Contour(contourMat);
|
|
||||||
m_area = m_mainContour.getArea() / params.imageArea * 100;
|
|
||||||
|
|
||||||
m_classId = class_id;
|
|
||||||
m_confidence = confidence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrackedTarget(
|
|
||||||
NeuralNetworkPipeResult t, TargetCalculationParameters targetCalculationParameters) {
|
|
||||||
this(t.box, t.classIdx, t.confidence, targetCalculationParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the confidence of the detection ranging from 0 - 1.
|
* @return Returns the confidence of the detection ranging from 0 - 1.
|
||||||
*/
|
*/
|
||||||
|
|||||||
26
photon-lib/py/photonlibpy/estimatedRobotPose.py
Normal file
26
photon-lib/py/photonlibpy/estimatedRobotPose.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from wpimath.geometry import Pose3d
|
||||||
|
|
||||||
|
from .photonTrackedTarget import PhotonTrackedTarget
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .photonPoseEstimator import PoseStrategy
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EstimatedRobotPose:
|
||||||
|
"""An estimated pose based on pipeline result"""
|
||||||
|
|
||||||
|
estimatedPose: Pose3d
|
||||||
|
"""The estimated pose"""
|
||||||
|
|
||||||
|
timestampSeconds: float
|
||||||
|
"""The estimated time the frame used to derive the robot pose was taken"""
|
||||||
|
|
||||||
|
targetsUsed: [PhotonTrackedTarget]
|
||||||
|
"""A list of the targets used to compute this pose"""
|
||||||
|
|
||||||
|
strategy: "PoseStrategy"
|
||||||
|
"""The strategy actually used to produce this pose"""
|
||||||
@@ -17,7 +17,6 @@ class PhotonPipelineResult:
|
|||||||
self.latencyMillis = packet.decodeDouble()
|
self.latencyMillis = packet.decodeDouble()
|
||||||
targetCount = packet.decode8()
|
targetCount = packet.decode8()
|
||||||
|
|
||||||
print(f"targetCount = {targetCount}")
|
|
||||||
for _ in range(targetCount):
|
for _ in range(targetCount):
|
||||||
target = PhotonTrackedTarget()
|
target = PhotonTrackedTarget()
|
||||||
target.createFromPacket(packet)
|
target.createFromPacket(packet)
|
||||||
|
|||||||
321
photon-lib/py/photonlibpy/photonPoseEstimator.py
Normal file
321
photon-lib/py/photonlibpy/photonPoseEstimator.py
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
import enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import wpilib
|
||||||
|
from robotpy_apriltag import AprilTagFieldLayout
|
||||||
|
from wpimath.geometry import Transform3d, Pose3d, Pose2d
|
||||||
|
|
||||||
|
from .photonPipelineResult import PhotonPipelineResult
|
||||||
|
from .photonCamera import PhotonCamera
|
||||||
|
from .estimatedRobotPose import EstimatedRobotPose
|
||||||
|
|
||||||
|
|
||||||
|
class PoseStrategy(enum.Enum):
|
||||||
|
"""
|
||||||
|
Position estimation strategies that can be used by the PhotonPoseEstimator class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOWEST_AMBIGUITY = enum.auto()
|
||||||
|
"""Choose the Pose with the lowest ambiguity."""
|
||||||
|
|
||||||
|
CLOSEST_TO_CAMERA_HEIGHT = enum.auto()
|
||||||
|
"""Choose the Pose which is closest to the camera height."""
|
||||||
|
|
||||||
|
CLOSEST_TO_REFERENCE_POSE = enum.auto()
|
||||||
|
"""Choose the Pose which is closest to a set Reference position."""
|
||||||
|
|
||||||
|
CLOSEST_TO_LAST_POSE = enum.auto()
|
||||||
|
"""Choose the Pose which is closest to the last pose calculated."""
|
||||||
|
|
||||||
|
AVERAGE_BEST_TARGETS = enum.auto()
|
||||||
|
"""Return the average of the best target poses using ambiguity as weight."""
|
||||||
|
|
||||||
|
MULTI_TAG_PNP_ON_COPROCESSOR = enum.auto()
|
||||||
|
"""
|
||||||
|
Use all visible tags to compute a single pose estimate on coprocessor.
|
||||||
|
This option needs to be enabled on the PhotonVision web UI as well.
|
||||||
|
"""
|
||||||
|
|
||||||
|
MULTI_TAG_PNP_ON_RIO = enum.auto()
|
||||||
|
"""
|
||||||
|
Use all visible tags to compute a single pose estimate.
|
||||||
|
This runs on the RoboRIO, and can take a lot of time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PhotonPoseEstimator:
|
||||||
|
"""
|
||||||
|
The PhotonPoseEstimator class filters or combines readings from all the AprilTags visible at a
|
||||||
|
given timestamp on the field to produce a single robot in field pose, using the strategy set
|
||||||
|
below. Example usage can be found in our apriltagExample example project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
fieldTags: AprilTagFieldLayout,
|
||||||
|
strategy: PoseStrategy,
|
||||||
|
camera: PhotonCamera,
|
||||||
|
robotToCamera: Transform3d,
|
||||||
|
):
|
||||||
|
"""Create a new PhotonPoseEstimator.
|
||||||
|
|
||||||
|
:param fieldTags: A WPILib AprilTagFieldLayout linking AprilTag IDs to Pose3d objects
|
||||||
|
with respect to the FIRST field using the Field Coordinate System.
|
||||||
|
Note that setting the origin of this layout object will affect the
|
||||||
|
results from this class.
|
||||||
|
:param strategy: The strategy it should use to determine the best pose.
|
||||||
|
:param camera: PhotonCamera
|
||||||
|
:param robotToCamera: Transform3d from the center of the robot to the camera mount position (i.e.,
|
||||||
|
robot ➔ camera) in the Robot Coordinate System.
|
||||||
|
"""
|
||||||
|
self._fieldTags = fieldTags
|
||||||
|
self._primaryStrategy = strategy
|
||||||
|
self._camera = camera
|
||||||
|
self.robotToCamera = robotToCamera
|
||||||
|
|
||||||
|
self._multiTagFallbackStrategy = PoseStrategy.LOWEST_AMBIGUITY
|
||||||
|
self._reportedErrors: set[int] = set()
|
||||||
|
self._poseCacheTimestampSeconds = -1
|
||||||
|
self._lastPose: Optional[Pose3d] = None
|
||||||
|
self._referencePose: Optional[Pose3d] = None
|
||||||
|
|
||||||
|
# TODO: Implement HAL reporting
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fieldTags(self) -> AprilTagFieldLayout:
|
||||||
|
"""Get the AprilTagFieldLayout being used by the PositionEstimator.
|
||||||
|
|
||||||
|
Note: Setting the origin of this layout will affect the results from this class.
|
||||||
|
|
||||||
|
:returns: the AprilTagFieldLayout
|
||||||
|
"""
|
||||||
|
return self._fieldTags
|
||||||
|
|
||||||
|
@fieldTags.setter
|
||||||
|
def fieldTags(self, fieldTags: AprilTagFieldLayout):
|
||||||
|
"""Set the AprilTagFieldLayout being used by the PositionEstimator.
|
||||||
|
|
||||||
|
Note: Setting the origin of this layout will affect the results from this class.
|
||||||
|
|
||||||
|
:param fieldTags: the AprilTagFieldLayout
|
||||||
|
"""
|
||||||
|
self._checkUpdate(self._fieldTags, fieldTags)
|
||||||
|
self._fieldTags = fieldTags
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primaryStrategy(self) -> PoseStrategy:
|
||||||
|
"""Get the Position Estimation Strategy being used by the Position Estimator.
|
||||||
|
|
||||||
|
:returns: the strategy
|
||||||
|
"""
|
||||||
|
return self._primaryStrategy
|
||||||
|
|
||||||
|
@primaryStrategy.setter
|
||||||
|
def primaryStrategy(self, strategy: PoseStrategy):
|
||||||
|
"""Set the Position Estimation Strategy used by the Position Estimator.
|
||||||
|
|
||||||
|
:param strategy: the strategy to set
|
||||||
|
"""
|
||||||
|
self._checkUpdate(self._primaryStrategy, strategy)
|
||||||
|
self._primaryStrategy = strategy
|
||||||
|
|
||||||
|
@property
|
||||||
|
def multiTagFallbackStrategy(self) -> PoseStrategy:
|
||||||
|
return self._multiTagFallbackStrategy
|
||||||
|
|
||||||
|
@multiTagFallbackStrategy.setter
|
||||||
|
def multiTagFallbackStrategy(self, strategy: PoseStrategy):
|
||||||
|
"""Set the Position Estimation Strategy used in multi-tag mode when only one tag can be seen. Must
|
||||||
|
NOT be MULTI_TAG_PNP
|
||||||
|
|
||||||
|
:param strategy: the strategy to set
|
||||||
|
"""
|
||||||
|
self._checkUpdate(self._multiTagFallbackStrategy, strategy)
|
||||||
|
if (
|
||||||
|
strategy is PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR
|
||||||
|
or strategy is PoseStrategy.MULTI_TAG_PNP_ON_RIO
|
||||||
|
):
|
||||||
|
wpilib.reportWarning(
|
||||||
|
"Fallback cannot be set to MULTI_TAG_PNP! Setting to lowest ambiguity",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
strategy = PoseStrategy.LOWEST_AMBIGUITY
|
||||||
|
self._multiTagFallbackStrategy = strategy
|
||||||
|
|
||||||
|
@property
|
||||||
|
def referencePose(self) -> Pose3d:
|
||||||
|
"""Return the reference position that is being used by the estimator.
|
||||||
|
|
||||||
|
:returns: the referencePose
|
||||||
|
"""
|
||||||
|
return self._referencePose
|
||||||
|
|
||||||
|
@referencePose.setter
|
||||||
|
def referencePose(self, referencePose: Pose3d | Pose2d):
|
||||||
|
"""Update the stored reference pose for use when using the **CLOSEST_TO_REFERENCE_POSE**
|
||||||
|
strategy.
|
||||||
|
|
||||||
|
:param referencePose: the referencePose to set
|
||||||
|
"""
|
||||||
|
if isinstance(referencePose, Pose2d):
|
||||||
|
referencePose = Pose3d(referencePose)
|
||||||
|
self._checkUpdate(self._referencePose, referencePose)
|
||||||
|
self._referencePose = referencePose
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lastPose(self) -> Pose3d:
|
||||||
|
return self._lastPose
|
||||||
|
|
||||||
|
@lastPose.setter
|
||||||
|
def lastPose(self, lastPose: Pose3d | Pose2d):
|
||||||
|
"""Update the stored last pose. Useful for setting the initial estimate when using the
|
||||||
|
**CLOSEST_TO_LAST_POSE** strategy.
|
||||||
|
|
||||||
|
:param lastPose: the lastPose to set
|
||||||
|
"""
|
||||||
|
if isinstance(lastPose, Pose2d):
|
||||||
|
lastPose = Pose3d(lastPose)
|
||||||
|
self._checkUpdate(self._lastPose, lastPose)
|
||||||
|
self._lastPose = lastPose
|
||||||
|
|
||||||
|
def _invalidatePoseCache(self):
|
||||||
|
self._poseCacheTimestampSeconds = -1
|
||||||
|
|
||||||
|
def _checkUpdate(self, oldObj, newObj):
|
||||||
|
if oldObj != newObj and oldObj is not None and oldObj is not newObj:
|
||||||
|
self._invalidatePoseCache()
|
||||||
|
|
||||||
|
def update(
|
||||||
|
self, cameraResult: Optional[PhotonPipelineResult] = None
|
||||||
|
) -> Optional[EstimatedRobotPose]:
|
||||||
|
"""
|
||||||
|
Updates the estimated position of the robot. Returns empty if:
|
||||||
|
|
||||||
|
- The timestamp of the provided pipeline result is the same as in the previous call to
|
||||||
|
``update()``.
|
||||||
|
|
||||||
|
- No targets were found in the pipeline results.
|
||||||
|
|
||||||
|
:param cameraResult: The latest pipeline result from the camera
|
||||||
|
|
||||||
|
:returns: an :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used to
|
||||||
|
create the estimate.
|
||||||
|
"""
|
||||||
|
if not cameraResult:
|
||||||
|
if not self._camera:
|
||||||
|
wpilib.reportError("[PhotonPoseEstimator] Missing camera!", False)
|
||||||
|
return
|
||||||
|
cameraResult = self._camera.getLatestResult()
|
||||||
|
|
||||||
|
if cameraResult.timestampSec < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If the pose cache timestamp was set, and the result is from the same
|
||||||
|
# timestamp, return an
|
||||||
|
# empty result
|
||||||
|
if (
|
||||||
|
self._poseCacheTimestampSeconds > 0
|
||||||
|
and abs(self._poseCacheTimestampSeconds - cameraResult.timestampSec) < 1e-6
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remember the timestamp of the current result used
|
||||||
|
self._poseCacheTimestampSeconds = cameraResult.timestampSec
|
||||||
|
|
||||||
|
# If no targets seen, trivial case -- return empty result
|
||||||
|
if not cameraResult.targets:
|
||||||
|
return
|
||||||
|
|
||||||
|
return self._update(cameraResult, self._primaryStrategy)
|
||||||
|
|
||||||
|
def _update(
|
||||||
|
self, cameraResult: PhotonPipelineResult, strat: PoseStrategy
|
||||||
|
) -> Optional[EstimatedRobotPose]:
|
||||||
|
if strat is PoseStrategy.LOWEST_AMBIGUITY:
|
||||||
|
estimatedPose = self._lowestAmbiguityStrategy(cameraResult)
|
||||||
|
elif strat is PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR:
|
||||||
|
estimatedPose = self._multiTagOnCoprocStrategy(cameraResult)
|
||||||
|
else:
|
||||||
|
wpilib.reportError(
|
||||||
|
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", False
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not estimatedPose:
|
||||||
|
self._lastPose = None
|
||||||
|
|
||||||
|
return estimatedPose
|
||||||
|
|
||||||
|
def _multiTagOnCoprocStrategy(
|
||||||
|
self, result: PhotonPipelineResult
|
||||||
|
) -> Optional[EstimatedRobotPose]:
|
||||||
|
if result.multiTagResult.estimatedPose.isPresent:
|
||||||
|
best_tf = result.multiTagResult.estimatedPose.best
|
||||||
|
best = (
|
||||||
|
Pose3d()
|
||||||
|
.transformBy(best_tf) # field-to-camera
|
||||||
|
.relativeTo(self._fieldTags.getOrigin())
|
||||||
|
.transformBy(self.robotToCamera.inverse()) # field-to-robot
|
||||||
|
)
|
||||||
|
return EstimatedRobotPose(
|
||||||
|
best,
|
||||||
|
result.timestampSec,
|
||||||
|
result.targets,
|
||||||
|
PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self._update(result, self._multiTagFallbackStrategy)
|
||||||
|
|
||||||
|
def _lowestAmbiguityStrategy(
|
||||||
|
self, result: PhotonPipelineResult
|
||||||
|
) -> Optional[EstimatedRobotPose]:
|
||||||
|
"""
|
||||||
|
Return the estimated position of the robot with the lowest position ambiguity from a List of
|
||||||
|
pipeline results.
|
||||||
|
|
||||||
|
:param result: pipeline result
|
||||||
|
|
||||||
|
:returns: the estimated position of the robot in the FCS and the estimated timestamp of this
|
||||||
|
estimation.
|
||||||
|
"""
|
||||||
|
lowestAmbiguityTarget = None
|
||||||
|
|
||||||
|
lowestAmbiguityScore = 10
|
||||||
|
|
||||||
|
for target in result.targets:
|
||||||
|
targetPoseAmbiguity = target.poseAmbiguity
|
||||||
|
|
||||||
|
# Make sure the target is a Fiducial target.
|
||||||
|
if targetPoseAmbiguity != -1 and targetPoseAmbiguity < lowestAmbiguityScore:
|
||||||
|
lowestAmbiguityScore = targetPoseAmbiguity
|
||||||
|
lowestAmbiguityTarget = target
|
||||||
|
|
||||||
|
# Although there are confirmed to be targets, none of them may be fiducial
|
||||||
|
# targets.
|
||||||
|
if not lowestAmbiguityTarget:
|
||||||
|
return
|
||||||
|
|
||||||
|
targetFiducialId = lowestAmbiguityTarget.fiducialId
|
||||||
|
|
||||||
|
targetPosition = self._fieldTags.getTagPose(targetFiducialId)
|
||||||
|
|
||||||
|
if not targetPosition:
|
||||||
|
self._reportFiducialPoseError(targetFiducialId)
|
||||||
|
return
|
||||||
|
|
||||||
|
return EstimatedRobotPose(
|
||||||
|
targetPosition.transformBy(
|
||||||
|
lowestAmbiguityTarget.getBestCameraToTarget().inverse()
|
||||||
|
).transformBy(self.robotToCamera.inverse()),
|
||||||
|
result.timestampSec,
|
||||||
|
result.targets,
|
||||||
|
PoseStrategy.LOWEST_AMBIGUITY,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _reportFiducialPoseError(self, fiducialId: int) -> None:
|
||||||
|
if fiducialId not in self._reportedErrors:
|
||||||
|
wpilib.reportError(
|
||||||
|
f"[PhotonPoseEstimator] Tried to get pose of unknown AprilTag: {fiducialId}",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
self._reportedErrors.add(fiducialId)
|
||||||
@@ -60,6 +60,7 @@ setup(
|
|||||||
install_requires=[
|
install_requires=[
|
||||||
"wpilib<2025,>=2024.0.0b2",
|
"wpilib<2025,>=2024.0.0b2",
|
||||||
"robotpy-wpimath<2025,>=2024.0.0b2",
|
"robotpy-wpimath<2025,>=2024.0.0b2",
|
||||||
|
"robotpy-apriltag<2025,>=2024.0.0b2",
|
||||||
"pyntcore<2025,>=2024.0.0b2",
|
"pyntcore<2025,>=2024.0.0b2",
|
||||||
],
|
],
|
||||||
description=descriptionStr,
|
description=descriptionStr,
|
||||||
|
|||||||
243
photon-lib/py/test/photonPoseEstimator_test.py
Normal file
243
photon-lib/py/test/photonPoseEstimator_test.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult, PNPResult
|
||||||
|
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
||||||
|
from photonlibpy.photonPoseEstimator import PhotonPoseEstimator, PoseStrategy
|
||||||
|
from photonlibpy.photonTrackedTarget import PhotonTrackedTarget, TargetCorner
|
||||||
|
from robotpy_apriltag import AprilTag, AprilTagFieldLayout
|
||||||
|
from wpimath.geometry import Pose3d, Rotation3d, Transform3d, Translation3d
|
||||||
|
|
||||||
|
|
||||||
|
class PhotonCameraInjector:
|
||||||
|
result: PhotonPipelineResult
|
||||||
|
|
||||||
|
def getLatestResult(self) -> PhotonPipelineResult:
|
||||||
|
return self.result
|
||||||
|
|
||||||
|
|
||||||
|
def setupCommon() -> AprilTagFieldLayout:
|
||||||
|
tagList = []
|
||||||
|
tagPoses = (
|
||||||
|
Pose3d(3, 3, 3, Rotation3d()),
|
||||||
|
Pose3d(5, 5, 5, Rotation3d()),
|
||||||
|
)
|
||||||
|
for id_, pose in enumerate(tagPoses):
|
||||||
|
aprilTag = AprilTag()
|
||||||
|
aprilTag.ID = id_
|
||||||
|
aprilTag.pose = pose
|
||||||
|
tagList.append(aprilTag)
|
||||||
|
|
||||||
|
fieldLength = 54 / 3.281 # 54 ft -> meters
|
||||||
|
fieldWidth = 27 / 3.281 # 24 ft -> meters
|
||||||
|
|
||||||
|
return AprilTagFieldLayout(tagList, fieldLength, fieldWidth)
|
||||||
|
|
||||||
|
|
||||||
|
def test_lowestAmbiguityStrategy():
|
||||||
|
aprilTags = setupCommon()
|
||||||
|
|
||||||
|
cameraOne = PhotonCameraInjector()
|
||||||
|
cameraOne.result = PhotonPipelineResult(
|
||||||
|
2,
|
||||||
|
11,
|
||||||
|
[
|
||||||
|
PhotonTrackedTarget(
|
||||||
|
3.0,
|
||||||
|
-4.0,
|
||||||
|
9.0,
|
||||||
|
4.0,
|
||||||
|
0,
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
0.7,
|
||||||
|
),
|
||||||
|
PhotonTrackedTarget(
|
||||||
|
3.0,
|
||||||
|
-4.0,
|
||||||
|
9.1,
|
||||||
|
6.7,
|
||||||
|
1,
|
||||||
|
Transform3d(Translation3d(4, 2, 3), Rotation3d(0, 0, 0)),
|
||||||
|
Transform3d(Translation3d(4, 2, 3), Rotation3d(1, 5, 3)),
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
0.3,
|
||||||
|
),
|
||||||
|
PhotonTrackedTarget(
|
||||||
|
9.0,
|
||||||
|
-2.0,
|
||||||
|
19.0,
|
||||||
|
3.0,
|
||||||
|
0,
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
0.4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
estimator = PhotonPoseEstimator(
|
||||||
|
aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
||||||
|
)
|
||||||
|
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
pose = estimatedPose.estimatedPose
|
||||||
|
|
||||||
|
assertEquals(11, estimatedPose.timestampSeconds)
|
||||||
|
assertEquals(1, pose.x, 0.01)
|
||||||
|
assertEquals(3, pose.y, 0.01)
|
||||||
|
assertEquals(2, pose.z, 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiTagOnCoprocStrategy():
|
||||||
|
cameraOne = PhotonCameraInjector()
|
||||||
|
cameraOne.result = PhotonPipelineResult(
|
||||||
|
2,
|
||||||
|
11,
|
||||||
|
# There needs to be at least one target present for pose estimation to work
|
||||||
|
# Doesn't matter which/how many targets for this test
|
||||||
|
[
|
||||||
|
PhotonTrackedTarget(
|
||||||
|
3.0,
|
||||||
|
-4.0,
|
||||||
|
9.0,
|
||||||
|
4.0,
|
||||||
|
0,
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
0.7,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
multiTagResult=MultiTargetPNPResult(
|
||||||
|
PNPResult(True, Transform3d(1, 3, 2, Rotation3d()))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
estimator = PhotonPoseEstimator(
|
||||||
|
AprilTagFieldLayout(),
|
||||||
|
PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
|
||||||
|
cameraOne,
|
||||||
|
Transform3d(),
|
||||||
|
)
|
||||||
|
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
pose = estimatedPose.estimatedPose
|
||||||
|
|
||||||
|
assertEquals(11, estimatedPose.timestampSeconds)
|
||||||
|
assertEquals(1, pose.x, 0.01)
|
||||||
|
assertEquals(3, pose.y, 0.01)
|
||||||
|
assertEquals(2, pose.z, 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cacheIsInvalidated():
|
||||||
|
aprilTags = setupCommon()
|
||||||
|
|
||||||
|
cameraOne = PhotonCameraInjector()
|
||||||
|
result = PhotonPipelineResult(
|
||||||
|
2,
|
||||||
|
20,
|
||||||
|
[
|
||||||
|
PhotonTrackedTarget(
|
||||||
|
3.0,
|
||||||
|
-4.0,
|
||||||
|
9.0,
|
||||||
|
4.0,
|
||||||
|
0,
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
Transform3d(Translation3d(1, 2, 3), Rotation3d(1, 2, 3)),
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
TargetCorner(1, 2),
|
||||||
|
TargetCorner(3, 4),
|
||||||
|
TargetCorner(5, 6),
|
||||||
|
TargetCorner(7, 8),
|
||||||
|
],
|
||||||
|
0.7,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
estimator = PhotonPoseEstimator(
|
||||||
|
aprilTags, PoseStrategy.LOWEST_AMBIGUITY, cameraOne, Transform3d()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Empty result, expect empty result
|
||||||
|
cameraOne.result = PhotonPipelineResult(timestampSec=1)
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
assert estimatedPose is None
|
||||||
|
|
||||||
|
# Set actual result
|
||||||
|
cameraOne.result = result
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
assert estimatedPose is not None
|
||||||
|
assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
||||||
|
assertEquals(20, estimator._poseCacheTimestampSeconds)
|
||||||
|
|
||||||
|
# And again -- pose cache should mean this is empty
|
||||||
|
cameraOne.result = result
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
assert estimatedPose is None
|
||||||
|
# Expect the old timestamp to still be here
|
||||||
|
assertEquals(20, estimator._poseCacheTimestampSeconds)
|
||||||
|
|
||||||
|
# Set new field layout -- right after, the pose cache timestamp should be -1
|
||||||
|
estimator.fieldTags = AprilTagFieldLayout([AprilTag()], 0, 0)
|
||||||
|
assertEquals(-1, estimator._poseCacheTimestampSeconds)
|
||||||
|
# Update should cache the current timestamp (20) again
|
||||||
|
cameraOne.result = result
|
||||||
|
estimatedPose = estimator.update()
|
||||||
|
assertEquals(20, estimatedPose.timestampSeconds, 0.01)
|
||||||
|
assertEquals(20, estimator._poseCacheTimestampSeconds)
|
||||||
|
|
||||||
|
|
||||||
|
def assertEquals(expected, actual, epsilon=0.0):
|
||||||
|
assert abs(expected - actual) <= epsilon
|
||||||
@@ -435,7 +435,7 @@ class PhotonCameraSim {
|
|||||||
double minTargetAreaPercent;
|
double minTargetAreaPercent;
|
||||||
|
|
||||||
frc::AprilTagFieldLayout tagLayout{
|
frc::AprilTagFieldLayout tagLayout{
|
||||||
frc::LoadAprilTagLayoutField(frc::AprilTagField::k2023ChargedUp)};
|
frc::LoadAprilTagLayoutField(frc::AprilTagField::k2024Crescendo)};
|
||||||
|
|
||||||
cs::CvSource videoSimRaw;
|
cs::CvSource videoSimRaw;
|
||||||
cv::Mat videoSimFrameRaw{};
|
cv::Mat videoSimFrameRaw{};
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ public class RequestHandler {
|
|||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
ctx.result("Successfully saved the uploaded settings zip, rebooting...");
|
ctx.result("Successfully saved the uploaded settings zip, rebooting...");
|
||||||
logger.info("Successfully saved the uploaded settings zip, rebooting...");
|
logger.info("Successfully saved the uploaded settings zip, rebooting...");
|
||||||
|
ConfigManager.getInstance().disableFlushOnShutdown();
|
||||||
restartProgram();
|
restartProgram();
|
||||||
} else {
|
} else {
|
||||||
ctx.status(500);
|
ctx.status(500);
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ public class Server {
|
|||||||
app.post("/api/calibration/importFromData", RequestHandler::onDataCalibrationImportRequest);
|
app.post("/api/calibration/importFromData", RequestHandler::onDataCalibrationImportRequest);
|
||||||
|
|
||||||
app.start(port);
|
app.start(port);
|
||||||
System.out.println("hi");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ bool PhotonPipelineResult::operator==(const PhotonPipelineResult& other) const {
|
|||||||
|
|
||||||
Packet& operator<<(Packet& packet, const PhotonPipelineResult& result) {
|
Packet& operator<<(Packet& packet, const PhotonPipelineResult& result) {
|
||||||
// Encode latency and number of targets.
|
// Encode latency and number of targets.
|
||||||
packet << result.latency.value() << result.multitagResult
|
packet << result.latency.value()
|
||||||
<< static_cast<int8_t>(result.targets.size());
|
<< static_cast<int8_t>(result.targets.size());
|
||||||
|
|
||||||
// Encode the information of each target.
|
// Encode the information of each target.
|
||||||
for (auto& target : result.targets) packet << target;
|
for (auto& target : result.targets) packet << target;
|
||||||
|
|
||||||
|
packet << result.multitagResult;
|
||||||
// Return the packet
|
// Return the packet
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ Packet& operator>>(Packet& packet, PhotonPipelineResult& result) {
|
|||||||
// Decode latency, existence of targets, and number of targets.
|
// Decode latency, existence of targets, and number of targets.
|
||||||
double latencyMillis = 0;
|
double latencyMillis = 0;
|
||||||
int8_t targetCount = 0;
|
int8_t targetCount = 0;
|
||||||
packet >> latencyMillis >> result.multitagResult >> targetCount;
|
packet >> latencyMillis >> targetCount;
|
||||||
result.latency = units::millisecond_t(latencyMillis);
|
result.latency = units::millisecond_t(latencyMillis);
|
||||||
|
|
||||||
result.targets.clear();
|
result.targets.clear();
|
||||||
@@ -62,6 +63,8 @@ Packet& operator>>(Packet& packet, PhotonPipelineResult& result) {
|
|||||||
packet >> target;
|
packet >> target;
|
||||||
result.targets.push_back(target);
|
result.targets.push_back(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packet >> result.multitagResult;
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
} // namespace photon
|
} // namespace photon
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "google-test-test-suite"
|
id "google-test-test-suite"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
|
|
||||||
id "com.dorongold.task-tree" version "2.1.0"
|
id "com.dorongold.task-tree" version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "google-test-test-suite"
|
id "google-test-test-suite"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
|
|
||||||
id "com.dorongold.task-tree" version "2.1.0"
|
id "com.dorongold.task-tree" version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "google-test-test-suite"
|
id "google-test-test-suite"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
|
|
||||||
id "com.dorongold.task-tree" version "2.1.0"
|
id "com.dorongold.task-tree" version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "com.diffplug.spotless" version "6.1.2"
|
id "com.diffplug.spotless" version "6.1.2"
|
||||||
id "edu.wpi.first.GradleRIO" version "2024.1.1" apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url = "https://maven.photonvision.org/repository/internal/" }
|
maven { url = "https://maven.photonvision.org/releases" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "google-test-test-suite"
|
id "google-test-test-suite"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
|
|
||||||
id "com.dorongold.task-tree" version "2.1.0"
|
id "com.dorongold.task-tree" version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "cpp"
|
id "cpp"
|
||||||
id "google-test-test-suite"
|
id "google-test-test-suite"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
|
|
||||||
id "com.dorongold.task-tree" version "2.1.0"
|
id "com.dorongold.task-tree" version "2.1.0"
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wpi.maven.useDevelopment = true
|
wpi.maven.useDevelopment = true
|
||||||
wpi.versions.wpilibVersion = "2024.1.1"
|
wpi.versions.wpilibVersion = "2024.2.1"
|
||||||
wpi.versions.wpimathVersion = "2024.1.1"
|
wpi.versions.wpimathVersion = "2024.2.1"
|
||||||
|
|
||||||
apply from: "${rootDir}/../shared/examples_common.gradle"
|
apply from: "${rootDir}/../shared/examples_common.gradle"
|
||||||
|
|
||||||
|
|||||||
@@ -35,48 +35,48 @@
|
|||||||
|
|
||||||
namespace constants {
|
namespace constants {
|
||||||
namespace Vision {
|
namespace Vision {
|
||||||
static constexpr std::string_view kCameraName{"YOUR CAMERA NAME"};
|
inline constexpr std::string_view kCameraName{"YOUR CAMERA NAME"};
|
||||||
static const frc::Transform3d kRobotToCam{
|
inline const frc::Transform3d kRobotToCam{
|
||||||
frc::Translation3d{0.5_m, 0.0_m, 0.5_m},
|
frc::Translation3d{0.5_m, 0.0_m, 0.5_m},
|
||||||
frc::Rotation3d{0_rad, 0_rad, 0_rad}};
|
frc::Rotation3d{0_rad, 0_rad, 0_rad}};
|
||||||
static const frc::AprilTagFieldLayout kTagLayout{
|
inline const frc::AprilTagFieldLayout kTagLayout{
|
||||||
frc::LoadAprilTagLayoutField(frc::AprilTagField::k2023ChargedUp)};
|
frc::LoadAprilTagLayoutField(frc::AprilTagField::k2024Crescendo)};
|
||||||
|
|
||||||
static const Eigen::Matrix<double, 3, 1> kSingleTagStdDevs{4, 4, 8};
|
inline const Eigen::Matrix<double, 3, 1> kSingleTagStdDevs{4, 4, 8};
|
||||||
static const Eigen::Matrix<double, 3, 1> kMultiTagStdDevs{0.5, 0.5, 1};
|
inline const Eigen::Matrix<double, 3, 1> kMultiTagStdDevs{0.5, 0.5, 1};
|
||||||
} // namespace Vision
|
} // namespace Vision
|
||||||
namespace Swerve {
|
namespace Swerve {
|
||||||
|
|
||||||
static constexpr units::meter_t kTrackWidth{18.5_in};
|
inline constexpr units::meter_t kTrackWidth{18.5_in};
|
||||||
static constexpr units::meter_t kTrackLength{18.5_in};
|
inline constexpr units::meter_t kTrackLength{18.5_in};
|
||||||
static constexpr units::meter_t kRobotWidth{25_in + 3.25_in * 2};
|
inline constexpr units::meter_t kRobotWidth{25_in + 3.25_in * 2};
|
||||||
static constexpr units::meter_t kRobotLength{25_in + 3.25_in * 2};
|
inline constexpr units::meter_t kRobotLength{25_in + 3.25_in * 2};
|
||||||
static constexpr units::meters_per_second_t kMaxLinearSpeed{15.5_fps};
|
inline constexpr units::meters_per_second_t kMaxLinearSpeed{15.5_fps};
|
||||||
static constexpr units::radians_per_second_t kMaxAngularSpeed{720_deg_per_s};
|
inline constexpr units::radians_per_second_t kMaxAngularSpeed{720_deg_per_s};
|
||||||
static constexpr units::meter_t kWheelDiameter{4_in};
|
inline constexpr units::meter_t kWheelDiameter{4_in};
|
||||||
static constexpr units::meter_t kWheelCircumference{kWheelDiameter *
|
inline constexpr units::meter_t kWheelCircumference{kWheelDiameter *
|
||||||
std::numbers::pi};
|
std::numbers::pi};
|
||||||
|
|
||||||
static constexpr double kDriveGearRatio = 6.75;
|
inline constexpr double kDriveGearRatio = 6.75;
|
||||||
static constexpr double kSteerGearRatio = 12.8;
|
inline constexpr double kSteerGearRatio = 12.8;
|
||||||
|
|
||||||
static constexpr units::meter_t kDriveDistPerPulse =
|
inline constexpr units::meter_t kDriveDistPerPulse =
|
||||||
kWheelCircumference / 1024.0 / kDriveGearRatio;
|
kWheelCircumference / 1024.0 / kDriveGearRatio;
|
||||||
static constexpr units::radian_t kSteerRadPerPulse =
|
inline constexpr units::radian_t kSteerRadPerPulse =
|
||||||
units::radian_t{2 * std::numbers::pi} / 1024.0;
|
units::radian_t{2 * std::numbers::pi} / 1024.0;
|
||||||
|
|
||||||
static constexpr double kDriveKP = 1.0;
|
inline constexpr double kDriveKP = 1.0;
|
||||||
static constexpr double kDriveKI = 0.0;
|
inline constexpr double kDriveKI = 0.0;
|
||||||
static constexpr double kDriveKD = 0.0;
|
inline constexpr double kDriveKD = 0.0;
|
||||||
|
|
||||||
static constexpr double kSteerKP = 20.0;
|
inline constexpr double kSteerKP = 20.0;
|
||||||
static constexpr double kSteerKI = 0.0;
|
inline constexpr double kSteerKI = 0.0;
|
||||||
static constexpr double kSteerKD = 0.25;
|
inline constexpr double kSteerKD = 0.25;
|
||||||
|
|
||||||
static const frc::SimpleMotorFeedforward<units::meters> kDriveFF{
|
inline const frc::SimpleMotorFeedforward<units::meters> kDriveFF{
|
||||||
0.25_V, 2.5_V / 1_mps, 0.3_V / 1_mps_sq};
|
0.25_V, 2.5_V / 1_mps, 0.3_V / 1_mps_sq};
|
||||||
|
|
||||||
static const frc::SimpleMotorFeedforward<units::radians> kSteerFF{
|
inline const frc::SimpleMotorFeedforward<units::radians> kSteerFF{
|
||||||
0.5_V, 0.25_V / 1_rad_per_s, 0.01_V / 1_rad_per_s_sq};
|
0.5_V, 0.25_V / 1_rad_per_s, 0.01_V / 1_rad_per_s_sq};
|
||||||
|
|
||||||
struct ModuleConstants {
|
struct ModuleConstants {
|
||||||
@@ -106,13 +106,13 @@ struct ModuleConstants {
|
|||||||
centerOffset(frc::Translation2d{xOffset, yOffset}) {}
|
centerOffset(frc::Translation2d{xOffset, yOffset}) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
static const ModuleConstants FL_CONSTANTS{
|
inline const ModuleConstants FL_CONSTANTS{
|
||||||
1, 0, 0, 1, 1, 2, 3, 0, kTrackLength / 2, kTrackWidth / 2};
|
1, 0, 0, 1, 1, 2, 3, 0, kTrackLength / 2, kTrackWidth / 2};
|
||||||
static const ModuleConstants FR_CONSTANTS{
|
inline const ModuleConstants FR_CONSTANTS{
|
||||||
2, 2, 4, 5, 3, 6, 7, 0, kTrackLength / 2, -kTrackWidth / 2};
|
2, 2, 4, 5, 3, 6, 7, 0, kTrackLength / 2, -kTrackWidth / 2};
|
||||||
static const ModuleConstants BL_CONSTANTS{
|
inline const ModuleConstants BL_CONSTANTS{
|
||||||
3, 4, 8, 9, 5, 10, 11, 0, -kTrackLength / 2, kTrackWidth / 2};
|
3, 4, 8, 9, 5, 10, 11, 0, -kTrackLength / 2, kTrackWidth / 2};
|
||||||
static const ModuleConstants BR_CONSTANTS{
|
inline const ModuleConstants BR_CONSTANTS{
|
||||||
4, 6, 12, 13, 7, 14, 15, 0, -kTrackLength / 2, -kTrackWidth / 2};
|
4, 6, 12, 13, 7, 14, 15, 0, -kTrackLength / 2, -kTrackWidth / 2};
|
||||||
} // namespace Swerve
|
} // namespace Swerve
|
||||||
} // namespace constants
|
} // namespace constants
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Vision {
|
|||||||
cameraSim = std::make_shared<photon::PhotonCameraSim>(camera.get(),
|
cameraSim = std::make_shared<photon::PhotonCameraSim>(camera.get(),
|
||||||
*cameraProp.get());
|
*cameraProp.get());
|
||||||
|
|
||||||
visionSim->AddCamera(cameraSim.get(), robotToCam);
|
visionSim->AddCamera(cameraSim.get(), constants::Vision::kRobotToCam);
|
||||||
cameraSim->EnableDrawWireframe(true);
|
cameraSim->EnableDrawWireframe(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,12 +138,10 @@ class Vision {
|
|||||||
frc::Field2d& GetSimDebugField() { return visionSim->GetDebugField(); }
|
frc::Field2d& GetSimDebugField() { return visionSim->GetDebugField(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
frc::Transform3d robotToCam{frc::Translation3d{0.5_m, 0.5_m, 0.5_m},
|
|
||||||
frc::Rotation3d{}};
|
|
||||||
photon::PhotonPoseEstimator photonEstimator{
|
photon::PhotonPoseEstimator photonEstimator{
|
||||||
LoadAprilTagLayoutField(frc::AprilTagField::k2023ChargedUp),
|
constants::Vision::kTagLayout,
|
||||||
photon::PoseStrategy::MULTI_TAG_PNP_ON_COPROCESSOR,
|
photon::PoseStrategy::MULTI_TAG_PNP_ON_COPROCESSOR,
|
||||||
photon::PhotonCamera{"photonvision"}, robotToCam};
|
photon::PhotonCamera{"photonvision"}, constants::Vision::kRobotToCam};
|
||||||
std::shared_ptr<photon::PhotonCamera> camera{photonEstimator.GetCamera()};
|
std::shared_ptr<photon::PhotonCamera> camera{photonEstimator.GetCamera()};
|
||||||
std::unique_ptr<photon::VisionSystemSim> visionSim;
|
std::unique_ptr<photon::VisionSystemSim> visionSim;
|
||||||
std::unique_ptr<photon::SimCameraProperties> cameraProp;
|
std::unique_ptr<photon::SimCameraProperties> cameraProp;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "com.diffplug.spotless" version "6.1.2"
|
id "com.diffplug.spotless" version "6.1.2"
|
||||||
id "edu.wpi.first.GradleRIO" version "2024.1.1" apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "examples.gradle"
|
apply from: "examples.gradle"
|
||||||
@@ -9,7 +8,7 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url = "https://maven.photonvision.org/repository/internal/" }
|
maven { url = "https://maven.photonvision.org/releases" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "edu.wpi.first.GradleRIO"
|
id "edu.wpi.first.GradleRIO" version "2024.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
@@ -11,8 +11,8 @@ apply from: "${rootDir}/../shared/examples_common.gradle"
|
|||||||
def ROBOT_MAIN_CLASS = "frc.robot.Main"
|
def ROBOT_MAIN_CLASS = "frc.robot.Main"
|
||||||
|
|
||||||
wpi.maven.useDevelopment = true
|
wpi.maven.useDevelopment = true
|
||||||
wpi.versions.wpilibVersion = "2024.1.1"
|
wpi.versions.wpilibVersion = "2024.2.1"
|
||||||
wpi.versions.wpimathVersion = "2024.1.1"
|
wpi.versions.wpimathVersion = "2024.2.1"
|
||||||
|
|
||||||
|
|
||||||
// Define my targets (RoboRIO) and artifacts (deployable files)
|
// Define my targets (RoboRIO) and artifacts (deployable files)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ publishing {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url ('https://maven.photonvision.org/repository/' + (isDev ? 'snapshots' : 'internal'))
|
url(photonMavenURL)
|
||||||
credentials {
|
credentials {
|
||||||
username 'ghactions'
|
username 'ghactions'
|
||||||
password System.getenv("ARTIFACTORY_API_KEY")
|
password System.getenv("ARTIFACTORY_API_KEY")
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ model {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url ('https://maven.photonvision.org/repository/' + (isDev ? 'snapshots' : 'internal'))
|
url(photonMavenURL)
|
||||||
credentials {
|
credentials {
|
||||||
username 'ghactions'
|
username 'ghactions'
|
||||||
password System.getenv("ARTIFACTORY_API_KEY")
|
password System.getenv("ARTIFACTORY_API_KEY")
|
||||||
|
|||||||
Reference in New Issue
Block a user