mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099a4eb58c | ||
|
|
bc55218739 | ||
|
|
e616d93d59 | ||
|
|
5851509a9e | ||
|
|
ea1b701ba7 | ||
|
|
62112cd2fd | ||
|
|
c7508fea46 |
289
.github/workflows/build.yml
vendored
289
.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,179 +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.6/photonvision_opi5.img.xz
|
|
||||||
cpu: cortex-a8
|
|
||||||
image_additional_mb: 4096
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: LinuxArm64
|
|
||||||
image_suffix: orangepi5plus
|
|
||||||
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.6/photonvision_opi5plus.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
|
|
||||||
11
build.gradle
11
build.gradle
@@ -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)
|
||||||
@@ -30,7 +31,7 @@ ext {
|
|||||||
joglVersion = "2.4.0-rc-20200307"
|
joglVersion = "2.4.0-rc-20200307"
|
||||||
javalinVersion = "5.6.2"
|
javalinVersion = "5.6.2"
|
||||||
photonGlDriverLibVersion = "dev-v2023.1.0-9-g75fc678"
|
photonGlDriverLibVersion = "dev-v2023.1.0-9-g75fc678"
|
||||||
rknnVersion = "dev-v2024.0.0-30-g001b5ec"
|
rknnVersion = "dev-v2024.0.0-64-gc0836a6"
|
||||||
frcYear = "2024"
|
frcYear = "2024"
|
||||||
mrcalVersion = "dev-v2024.0.0-7-gc976aaa";
|
mrcalVersion = "dev-v2024.0.0-7-gc976aaa";
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -25,15 +25,8 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
|
|||||||
|
|
||||||
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
|
||||||
if (calib !== undefined) {
|
if (calib !== undefined) {
|
||||||
// Is this the right formula for RMS error? who knows! not me!
|
|
||||||
const perViewSumSquareReprojectionError = calib.observations.flatMap((it) =>
|
|
||||||
it.reprojectionErrors.flatMap((it2) => [it2.x, it2.y])
|
|
||||||
);
|
|
||||||
// For each error, square it, sum the squares, and divide by total points N
|
// For each error, square it, sum the squares, and divide by total points N
|
||||||
format.mean = Math.sqrt(
|
format.mean = calib.meanErrors.reduce((a, b) => a + b) / calib.meanErrors.length;
|
||||||
perViewSumSquareReprojectionError.map((it) => Math.pow(it, 2)).reduce((a, b) => a + b, 0) /
|
|
||||||
perViewSumSquareReprojectionError.length
|
|
||||||
);
|
|
||||||
|
|
||||||
format.horizontalFOV =
|
format.horizontalFOV =
|
||||||
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
|
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
|
||||||
|
|||||||
@@ -1,51 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BoardObservation, CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
|
import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import loadingImage from "@/assets/images/loading.svg";
|
|
||||||
import { getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
import { getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
videoFormat: VideoFormat;
|
videoFormat: VideoFormat;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const getMeanFromView = (o: BoardObservation) => {
|
const exportCalibration = ref();
|
||||||
// Is this the right formula for RMS error? who knows! not me!
|
const openExportCalibrationPrompt = () => {
|
||||||
const perViewSumSquareReprojectionError = o.reprojectionErrors.flatMap((it2) => [it2.x, it2.y]);
|
exportCalibration.value.click();
|
||||||
|
|
||||||
// For each error, square it, sum the squares, and divide by total points N
|
|
||||||
return Math.sqrt(
|
|
||||||
perViewSumSquareReprojectionError.map((it) => Math.pow(it, 2)).reduce((a, b) => a + b, 0) /
|
|
||||||
perViewSumSquareReprojectionError.length
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Import and export functions
|
|
||||||
const downloadCalibration = () => {
|
|
||||||
const calibData = useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution);
|
|
||||||
if (calibData === undefined) {
|
|
||||||
useStateStore().showSnackbarMessage({
|
|
||||||
color: "error",
|
|
||||||
message:
|
|
||||||
"Calibration data isn't available for the requested resolution, please calibrate the requested resolution first"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const camUniqueName = useCameraSettingsStore().currentCameraSettings.uniqueName;
|
|
||||||
const filename = `photon_calibration_${camUniqueName}_${calibData.resolution.width}x${calibData.resolution.height}.json`;
|
|
||||||
const fileData = JSON.stringify(calibData);
|
|
||||||
|
|
||||||
const element = document.createElement("a");
|
|
||||||
element.style.display = "none";
|
|
||||||
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(fileData));
|
|
||||||
element.setAttribute("download", filename);
|
|
||||||
|
|
||||||
document.body.appendChild(element);
|
|
||||||
element.click();
|
|
||||||
document.body.removeChild(element);
|
|
||||||
};
|
|
||||||
const importCalibrationFromPhotonJson = ref();
|
const importCalibrationFromPhotonJson = ref();
|
||||||
const openUploadPhotonCalibJsonPrompt = () => {
|
const openUploadPhotonCalibJsonPrompt = () => {
|
||||||
importCalibrationFromPhotonJson.value.click();
|
importCalibrationFromPhotonJson.value.click();
|
||||||
@@ -97,19 +65,28 @@ const importCalibration = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface ObservationDetails {
|
interface ObservationDetails {
|
||||||
snapshotSrc: any;
|
|
||||||
mean: number;
|
mean: number;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentCalibrationCoeffs = computed<CameraCalibrationResult | undefined>(() =>
|
||||||
|
useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution)
|
||||||
|
);
|
||||||
|
|
||||||
const getObservationDetails = (): ObservationDetails[] | undefined => {
|
const getObservationDetails = (): ObservationDetails[] | undefined => {
|
||||||
return useCameraSettingsStore()
|
const coefficients = currentCalibrationCoeffs.value;
|
||||||
.getCalibrationCoeffs(props.videoFormat.resolution)
|
|
||||||
?.observations.map((o, i) => ({
|
return coefficients?.meanErrors.map((m, i) => ({
|
||||||
index: i,
|
index: i,
|
||||||
mean: parseFloat(getMeanFromView(o).toFixed(2)),
|
mean: parseFloat(m.toFixed(2))
|
||||||
snapshotSrc: o.includeObservationInCalibration ? "data:image/png;base64," + o.snapshotData.data : loadingImage
|
}));
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportCalibrationURL = computed<string>(() =>
|
||||||
|
useCameraSettingsStore().getCalJSONUrl(inject("backendHost") as string, props.videoFormat.resolution)
|
||||||
|
);
|
||||||
|
const calibrationImageURL = (index: number) =>
|
||||||
|
useCameraSettingsStore().getCalImageUrl(inject<string>("backendHost") as string, props.videoFormat.resolution, index);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -140,19 +117,22 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
|
|||||||
<v-btn
|
<v-btn
|
||||||
color="secondary"
|
color="secondary"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
:disabled="useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution) === undefined"
|
:disabled="!currentCalibrationCoeffs"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@click="downloadCalibration"
|
@click="openExportCalibrationPrompt"
|
||||||
>
|
>
|
||||||
<v-icon left>mdi-export</v-icon>
|
<v-icon left>mdi-export</v-icon>
|
||||||
<span>Export</span>
|
<span>Export</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<a
|
||||||
|
ref="exportCalibration"
|
||||||
|
style="color: black; text-decoration: none; display: none"
|
||||||
|
:href="exportCalibrationURL"
|
||||||
|
target="_blank"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row
|
<v-row v-if="currentCalibrationCoeffs" class="pt-2">
|
||||||
v-if="useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution) !== undefined"
|
|
||||||
class="pt-2"
|
|
||||||
>
|
|
||||||
<v-card-subtitle>Calibration Details</v-card-subtitle>
|
<v-card-subtitle>Calibration Details</v-card-subtitle>
|
||||||
<v-simple-table dense style="width: 100%" class="pl-2 pr-2">
|
<v-simple-table dense style="width: 100%" class="pl-2 pr-2">
|
||||||
<template #default>
|
<template #default>
|
||||||
@@ -231,7 +211,9 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Horizontal FOV</td>
|
<td>Horizontal FOV</td>
|
||||||
<td>{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
|
<td>
|
||||||
|
{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Vertical FOV</td>
|
<td>Vertical FOV</td>
|
||||||
@@ -242,11 +224,7 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
|
|||||||
<td>{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
|
<td>{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Board warp, only shown for mrcal-calibrated cameras -->
|
<!-- Board warp, only shown for mrcal-calibrated cameras -->
|
||||||
<tr
|
<tr v-if="currentCalibrationCoeffs?.calobjectWarp?.length === 2">
|
||||||
v-if="
|
|
||||||
useCameraSettingsStore().getCalibrationCoeffs(props.videoFormat.resolution)?.calobjectWarp?.length === 2
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<td>Board warp, X/Y</td>
|
<td>Board warp, X/Y</td>
|
||||||
<td>
|
<td>
|
||||||
{{
|
{{
|
||||||
@@ -278,7 +256,7 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
|
|||||||
<template #expanded-item="{ headers, item }">
|
<template #expanded-item="{ headers, item }">
|
||||||
<td :colspan="headers.length">
|
<td :colspan="headers.length">
|
||||||
<div style="display: flex; justify-content: center; width: 100%">
|
<div style="display: flex; justify-content: center; width: 100%">
|
||||||
<img :src="item.snapshotSrc" alt="observation image" class="snapshot-preview pt-2 pb-2" />
|
<img :src="calibrationImageURL(item.index)" alt="observation image" class="snapshot-preview pt-2 pb-2" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -27,42 +27,54 @@ const generalMetrics = computed<MetricItem[]>(() => [
|
|||||||
value: useSettingsStore().general.gpuAcceleration || "Unknown"
|
value: useSettingsStore().general.gpuAcceleration || "Unknown"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const platformMetrics = computed<MetricItem[]>(() => [
|
|
||||||
{
|
const platformMetrics = computed<MetricItem[]>(() => {
|
||||||
header: "CPU Temp",
|
const stats = [
|
||||||
value: useSettingsStore().metrics.cpuTemp === undefined ? "Unknown" : `${useSettingsStore().metrics.cpuTemp}°C`
|
{
|
||||||
},
|
header: "CPU Temp",
|
||||||
{
|
value: useSettingsStore().metrics.cpuTemp === undefined ? "Unknown" : `${useSettingsStore().metrics.cpuTemp}°C`
|
||||||
header: "CPU Usage",
|
},
|
||||||
value: useSettingsStore().metrics.cpuUtil === undefined ? "Unknown" : `${useSettingsStore().metrics.cpuUtil}%`
|
{
|
||||||
},
|
header: "CPU Usage",
|
||||||
{
|
value: useSettingsStore().metrics.cpuUtil === undefined ? "Unknown" : `${useSettingsStore().metrics.cpuUtil}%`
|
||||||
header: "CPU Memory Usage",
|
},
|
||||||
value:
|
{
|
||||||
useSettingsStore().metrics.ramUtil === undefined || useSettingsStore().metrics.cpuMem === undefined
|
header: "CPU Memory Usage",
|
||||||
? "Unknown"
|
value:
|
||||||
: `${useSettingsStore().metrics.ramUtil || "Unknown"}MB of ${useSettingsStore().metrics.cpuMem}MB`
|
useSettingsStore().metrics.ramUtil === undefined || useSettingsStore().metrics.cpuMem === undefined
|
||||||
},
|
? "Unknown"
|
||||||
{
|
: `${useSettingsStore().metrics.ramUtil || "Unknown"}MB of ${useSettingsStore().metrics.cpuMem}MB`
|
||||||
header: "GPU Memory Usage",
|
},
|
||||||
value:
|
{
|
||||||
useSettingsStore().metrics.gpuMemUtil === undefined || useSettingsStore().metrics.gpuMem === undefined
|
header: "GPU Memory Usage",
|
||||||
? "Unknown"
|
value:
|
||||||
: `${useSettingsStore().metrics.gpuMemUtil}MB of ${useSettingsStore().metrics.gpuMem}MB`
|
useSettingsStore().metrics.gpuMemUtil === undefined || useSettingsStore().metrics.gpuMem === undefined
|
||||||
},
|
? "Unknown"
|
||||||
{
|
: `${useSettingsStore().metrics.gpuMemUtil}MB of ${useSettingsStore().metrics.gpuMem}MB`
|
||||||
header: "CPU Throttling",
|
},
|
||||||
value: useSettingsStore().metrics.cpuThr || "Unknown"
|
{
|
||||||
},
|
header: "CPU Throttling",
|
||||||
{
|
value: useSettingsStore().metrics.cpuThr || "Unknown"
|
||||||
header: "CPU Uptime",
|
},
|
||||||
value: useSettingsStore().metrics.cpuUptime || "Unknown"
|
{
|
||||||
},
|
header: "CPU Uptime",
|
||||||
{
|
value: useSettingsStore().metrics.cpuUptime || "Unknown"
|
||||||
header: "Disk Usage",
|
},
|
||||||
value: useSettingsStore().metrics.diskUtilPct || "Unknown"
|
{
|
||||||
|
header: "Disk Usage",
|
||||||
|
value: useSettingsStore().metrics.diskUtilPct || "Unknown"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (useSettingsStore().metrics.npuUsage) {
|
||||||
|
stats.push({
|
||||||
|
header: "NPU Usage",
|
||||||
|
value: useSettingsStore().metrics.npuUsage || "Unknown"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
|
return stats;
|
||||||
|
});
|
||||||
|
|
||||||
const metricsLastFetched = ref("Never");
|
const metricsLastFetched = ref("Never");
|
||||||
const fetchMetrics = () => {
|
const fetchMetrics = () => {
|
||||||
|
|||||||
@@ -416,6 +416,23 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
cameraIndex: number = useStateStore().currentCameraIndex
|
cameraIndex: number = useStateStore().currentCameraIndex
|
||||||
): CameraCalibrationResult | undefined {
|
): CameraCalibrationResult | undefined {
|
||||||
return this.cameras[cameraIndex].completeCalibrations.find((v) => resolutionsAreEqual(v.resolution, resolution));
|
return this.cameras[cameraIndex].completeCalibrations.find((v) => resolutionsAreEqual(v.resolution, resolution));
|
||||||
|
},
|
||||||
|
getCalImageUrl(host: string, resolution: Resolution, idx: number, cameraIdx = useStateStore().currentCameraIndex) {
|
||||||
|
const url = new URL(`http://${host}/api/utils/getCalSnapshot`);
|
||||||
|
url.searchParams.set("width", Math.round(resolution.width).toFixed(0));
|
||||||
|
url.searchParams.set("height", Math.round(resolution.height).toFixed(0));
|
||||||
|
url.searchParams.set("snapshotIdx", Math.round(idx).toFixed(0));
|
||||||
|
url.searchParams.set("cameraIdx", Math.round(cameraIdx).toFixed(0));
|
||||||
|
|
||||||
|
return url.href;
|
||||||
|
},
|
||||||
|
getCalJSONUrl(host: string, resolution: Resolution, cameraIdx = useStateStore().currentCameraIndex) {
|
||||||
|
const url = new URL(`http://${host}/api/utils/getCalibrationJSON`);
|
||||||
|
url.searchParams.set("width", Math.round(resolution.width).toFixed(0));
|
||||||
|
url.searchParams.set("height", Math.round(resolution.height).toFixed(0));
|
||||||
|
url.searchParams.set("cameraIdx", Math.round(cameraIdx).toFixed(0));
|
||||||
|
|
||||||
|
return url.href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
gpuMemUtil: undefined,
|
gpuMemUtil: undefined,
|
||||||
cpuThr: undefined,
|
cpuThr: undefined,
|
||||||
cpuUptime: undefined,
|
cpuUptime: undefined,
|
||||||
diskUtilPct: undefined
|
diskUtilPct: undefined,
|
||||||
|
npuUsage: undefined
|
||||||
},
|
},
|
||||||
currentFieldLayout: {
|
currentFieldLayout: {
|
||||||
field: {
|
field: {
|
||||||
@@ -91,7 +92,8 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
gpuMemUtil: data.gpuMemUtil || undefined,
|
gpuMemUtil: data.gpuMemUtil || undefined,
|
||||||
cpuThr: data.cpuThr || undefined,
|
cpuThr: data.cpuThr || undefined,
|
||||||
cpuUptime: data.cpuUptime || undefined,
|
cpuUptime: data.cpuUptime || undefined,
|
||||||
diskUtilPct: data.diskUtilPct || undefined
|
diskUtilPct: data.diskUtilPct || undefined,
|
||||||
|
npuUsage: data.npuUsage || undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
|
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface MetricData {
|
|||||||
cpuThr?: string;
|
cpuThr?: string;
|
||||||
cpuUptime?: string;
|
cpuUptime?: string;
|
||||||
diskUtilPct?: string;
|
diskUtilPct?: string;
|
||||||
|
npuUsage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NetworkConnectionType {
|
export enum NetworkConnectionType {
|
||||||
@@ -138,6 +139,9 @@ export interface CameraCalibrationResult {
|
|||||||
distCoeffs: JsonMatOfDouble;
|
distCoeffs: JsonMatOfDouble;
|
||||||
observations: BoardObservation[];
|
observations: BoardObservation[];
|
||||||
calobjectWarp?: number[];
|
calobjectWarp?: number[];
|
||||||
|
// We might have to omit observations for bandwith, so backend will send us this
|
||||||
|
numSnapshots: number;
|
||||||
|
meanErrors: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ValidQuirks {
|
export enum ValidQuirks {
|
||||||
@@ -255,7 +259,9 @@ export const PlaceholderCameraSettings: CameraSettings = {
|
|||||||
snapshotName: "img0.png",
|
snapshotName: "img0.png",
|
||||||
snapshotData: { rows: 480, cols: 640, type: CvType.CV_8U, data: "" }
|
snapshotData: { rows: 480, cols: 640, type: CvType.CV_8U, data: "" }
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
numSnapshots: 1,
|
||||||
|
meanErrors: [123.45]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
pipelineNicknames: ["Placeholder Pipeline"],
|
pipelineNicknames: ["Placeholder Pipeline"],
|
||||||
|
|||||||
@@ -37,9 +37,8 @@ dependencies {
|
|||||||
implementation 'org.zeroturnaround:zt-zip:1.14'
|
implementation 'org.zeroturnaround:zt-zip:1.14'
|
||||||
|
|
||||||
implementation "org.xerial:sqlite-jdbc:3.41.0.0"
|
implementation "org.xerial:sqlite-jdbc:3.41.0.0"
|
||||||
def rknnjniversion = "dev-v2024.0.0-44-g8022c40"
|
implementation "org.photonvision:rknn_jni-jni:$rknnVersion:linuxarm64"
|
||||||
implementation "org.photonvision:rknn_jni-jni:$rknnjniversion:linuxarm64"
|
implementation "org.photonvision:rknn_jni-java:$rknnVersion"
|
||||||
implementation "org.photonvision:rknn_jni-java:$rknnjniversion"
|
|
||||||
implementation "org.photonvision:photon-libcamera-gl-driver-jni:$photonGlDriverLibVersion:linuxarm64"
|
implementation "org.photonvision:photon-libcamera-gl-driver-jni:$photonGlDriverLibVersion:linuxarm64"
|
||||||
implementation "org.photonvision:photon-libcamera-gl-driver-java:$photonGlDriverLibVersion"
|
implementation "org.photonvision:photon-libcamera-gl-driver-java:$photonGlDriverLibVersion"
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ import java.nio.file.Paths;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
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.rknn.RknnJNI;
|
||||||
|
|
||||||
public class NeuralNetworkModelManager {
|
public class NeuralNetworkModelManager {
|
||||||
private static NeuralNetworkModelManager INSTANCE;
|
private static NeuralNetworkModelManager INSTANCE;
|
||||||
private static final Logger logger = new Logger(NeuralNetworkModelManager.class, LogGroup.Config);
|
private static final Logger logger = new Logger(NeuralNetworkModelManager.class, LogGroup.Config);
|
||||||
|
|
||||||
private final String MODEL_NAME = "note-640-640-yolov5s.rknn";
|
private final String MODEL_NAME = "note-640-640-yolov5s.rknn";
|
||||||
|
private final RknnJNI.ModelVersion modelVersion = RknnJNI.ModelVersion.YOLO_V5;
|
||||||
private File defaultModelFile;
|
private File defaultModelFile;
|
||||||
private List<String> labels;
|
private List<String> labels;
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ public class NeuralNetworkModelManager {
|
|||||||
this.defaultModelFile = new File(modelsFolder, MODEL_NAME);
|
this.defaultModelFile = new File(modelsFolder, MODEL_NAME);
|
||||||
extractResource(modelResourcePath, defaultModelFile);
|
extractResource(modelResourcePath, defaultModelFile);
|
||||||
|
|
||||||
File labelsFile = new File(modelsFolder, "labels.txt");
|
File labelsFile = new File(modelsFolder, "labels_v5.txt");
|
||||||
var labelResourcePath = "/models/" + labelsFile.getName();
|
var labelResourcePath = "/models/" + labelsFile.getName();
|
||||||
extractResource(labelResourcePath, labelsFile);
|
extractResource(labelResourcePath, labelsFile);
|
||||||
|
|
||||||
@@ -95,4 +97,8 @@ public class NeuralNetworkModelManager {
|
|||||||
public List<String> getLabels() {
|
public List<String> getLabels() {
|
||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RknnJNI.ModelVersion getModelVersion() {
|
||||||
|
return modelVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import org.photonvision.common.util.SerializationUtils;
|
|||||||
import org.photonvision.jni.RknnDetectorJNI;
|
import org.photonvision.jni.RknnDetectorJNI;
|
||||||
import org.photonvision.mrcal.MrCalJNILoader;
|
import org.photonvision.mrcal.MrCalJNILoader;
|
||||||
import org.photonvision.raspi.LibCameraJNILoader;
|
import org.photonvision.raspi.LibCameraJNILoader;
|
||||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
|
||||||
import org.photonvision.vision.camera.QuirkyCamera;
|
import org.photonvision.vision.camera.QuirkyCamera;
|
||||||
import org.photonvision.vision.processes.VisionModule;
|
import org.photonvision.vision.processes.VisionModule;
|
||||||
import org.photonvision.vision.processes.VisionModuleManager;
|
import org.photonvision.vision.processes.VisionModuleManager;
|
||||||
@@ -126,13 +126,6 @@ public class PhotonConfiguration {
|
|||||||
|
|
||||||
settingsSubmap.put("networkSettings", netConfigMap);
|
settingsSubmap.put("networkSettings", netConfigMap);
|
||||||
|
|
||||||
map.put(
|
|
||||||
"cameraSettings",
|
|
||||||
VisionModuleManager.getInstance().getModules().stream()
|
|
||||||
.map(VisionModule::toUICameraConfig)
|
|
||||||
.map(SerializationUtils::objectToHashMap)
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
|
|
||||||
var lightingConfig = new UILightingConfig();
|
var lightingConfig = new UILightingConfig();
|
||||||
lightingConfig.brightness = hardwareSettings.ledBrightnessPercentage;
|
lightingConfig.brightness = hardwareSettings.ledBrightnessPercentage;
|
||||||
lightingConfig.supported = !hardwareConfig.ledPins.isEmpty();
|
lightingConfig.supported = !hardwareConfig.ledPins.isEmpty();
|
||||||
@@ -181,7 +174,7 @@ public class PhotonConfiguration {
|
|||||||
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
|
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
|
||||||
public int outputStreamPort;
|
public int outputStreamPort;
|
||||||
public int inputStreamPort;
|
public int inputStreamPort;
|
||||||
public List<CameraCalibrationCoefficients> calibrations;
|
public List<UICameraCalibrationCoefficients> calibrations;
|
||||||
public boolean isFovConfigurable = true;
|
public boolean isFovConfigurable = true;
|
||||||
public QuirkyCamera cameraQuirks;
|
public QuirkyCamera cameraQuirks;
|
||||||
public boolean isCSICamera;
|
public boolean isCSICamera;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.photonvision.common.logging.LogGroup;
|
|||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
import org.photonvision.common.util.SerializationUtils;
|
import org.photonvision.common.util.SerializationUtils;
|
||||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||||
|
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
||||||
|
|
||||||
public class UIDataPublisher implements CVPipelineResultConsumer {
|
public class UIDataPublisher implements CVPipelineResultConsumer {
|
||||||
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
|
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
|
||||||
@@ -41,16 +42,22 @@ public class UIDataPublisher implements CVPipelineResultConsumer {
|
|||||||
public void accept(CVPipelineResult result) {
|
public void accept(CVPipelineResult result) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
// only update the UI at 15hz
|
// only update the UI at 10hz
|
||||||
if (lastUIResultUpdateTime + 1000.0 / 10.0 > now) return;
|
if (lastUIResultUpdateTime + 1000.0 / 10.0 > now) return;
|
||||||
|
|
||||||
var dataMap = new HashMap<String, Object>();
|
var dataMap = new HashMap<String, Object>();
|
||||||
dataMap.put("fps", result.fps);
|
dataMap.put("fps", result.fps);
|
||||||
dataMap.put("latency", result.getLatencyMillis());
|
dataMap.put("latency", result.getLatencyMillis());
|
||||||
var uiTargets = new ArrayList<HashMap<String, Object>>(result.targets.size());
|
var uiTargets = new ArrayList<HashMap<String, Object>>(result.targets.size());
|
||||||
for (var t : result.targets) {
|
|
||||||
uiTargets.add(t.toHashMap());
|
// We don't actually need to send targets during calibration and it can take up a lot (up to
|
||||||
|
// 1.2Mbps for 60 snapshots) of target results with no pitch/yaw/etc set
|
||||||
|
if (!(result instanceof CalibrationPipelineResult)) {
|
||||||
|
for (var t : result.targets) {
|
||||||
|
uiTargets.add(t.toHashMap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataMap.put("targets", uiTargets);
|
dataMap.put("targets", uiTargets);
|
||||||
dataMap.put("classNames", result.objectDetectionClassNames);
|
dataMap.put("classNames", result.objectDetectionClassNames);
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ public class MetricsManager {
|
|||||||
return safeExecute(cmds.cpuThrottleReasonCmd);
|
return safeExecute(cmds.cpuThrottleReasonCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNpuUsage() {
|
||||||
|
return safeExecute(cmds.npuUsageCommand);
|
||||||
|
}
|
||||||
|
|
||||||
private String gpuMemSave = null;
|
private String gpuMemSave = null;
|
||||||
|
|
||||||
public String getGPUMemorySplit() {
|
public String getGPUMemorySplit() {
|
||||||
@@ -128,6 +132,7 @@ public class MetricsManager {
|
|||||||
metrics.put("ramUtil", this.getUsedRam());
|
metrics.put("ramUtil", this.getUsedRam());
|
||||||
metrics.put("gpuMemUtil", this.getMallocedMemory());
|
metrics.put("gpuMemUtil", this.getMallocedMemory());
|
||||||
metrics.put("diskUtilPct", this.getUsedDiskPct());
|
metrics.put("diskUtilPct", this.getUsedDiskPct());
|
||||||
|
metrics.put("npuUsage", this.getNpuUsage());
|
||||||
|
|
||||||
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
|
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public class CmdBase {
|
|||||||
// GPU
|
// GPU
|
||||||
public String gpuMemoryCommand = "";
|
public String gpuMemoryCommand = "";
|
||||||
public String gpuMemUsageCommand = "";
|
public String gpuMemUsageCommand = "";
|
||||||
|
// NPU
|
||||||
|
public String npuUsageCommand = "";
|
||||||
// RAM
|
// RAM
|
||||||
public String ramUsageCommand = "";
|
public String ramUsageCommand = "";
|
||||||
// Disk
|
// Disk
|
||||||
|
|||||||
@@ -44,5 +44,7 @@ public class RK3588Cmds extends LinuxCmds {
|
|||||||
*/
|
*/
|
||||||
cpuTemperatureCommand =
|
cpuTemperatureCommand =
|
||||||
"cat /sys/class/thermal/thermal_zone1/temp | awk '{printf \"%.1f\", $1/1000}'";
|
"cat /sys/class/thermal/thermal_zone1/temp | awk '{printf \"%.1f\", $1/1000}'";
|
||||||
|
|
||||||
|
npuUsageCommand = "cat /sys/kernel/debug/rknpu/load | sed 's/NPU load://; s/^ *//; s/ *$//'";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,11 @@ public class RknnDetectorJNI extends PhotonJNICommon {
|
|||||||
long objPointer = -1;
|
long objPointer = -1;
|
||||||
private List<String> labels;
|
private List<String> labels;
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
private static final CopyOnWriteArrayList<Long> detectors = new CopyOnWriteArrayList<>();
|
private static final CopyOnWriteArrayList<Long> detectors = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public RknnObjectDetector(String modelPath, List<String> labels) {
|
public RknnObjectDetector(String modelPath, List<String> labels, RknnJNI.ModelVersion version) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
objPointer = RknnJNI.create(modelPath, labels.size());
|
objPointer = RknnJNI.create(modelPath, labels.size(), version.ordinal(), -1);
|
||||||
detectors.add(objPointer);
|
detectors.add(objPointer);
|
||||||
System.out.println(
|
System.out.println(
|
||||||
"Created " + objPointer + "! Detectors: " + Arrays.toString(detectors.toArray()));
|
"Created " + objPointer + "! Detectors: " + Arrays.toString(detectors.toArray()));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.util.List;
|
|||||||
import org.opencv.core.Point;
|
import org.opencv.core.Point;
|
||||||
import org.opencv.core.Point3;
|
import org.opencv.core.Point3;
|
||||||
|
|
||||||
public final class BoardObservation {
|
public final class BoardObservation implements Cloneable {
|
||||||
// Expected feature 3d location in the camera frame
|
// Expected feature 3d location in the camera frame
|
||||||
@JsonProperty("locationInObjectSpace")
|
@JsonProperty("locationInObjectSpace")
|
||||||
public List<Point3> locationInObjectSpace;
|
public List<Point3> locationInObjectSpace;
|
||||||
@@ -68,4 +68,33 @@ public final class BoardObservation {
|
|||||||
this.snapshotName = snapshotName;
|
this.snapshotName = snapshotName;
|
||||||
this.snapshotData = snapshotData;
|
this.snapshotData = snapshotData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BoardObservation [locationInObjectSpace="
|
||||||
|
+ locationInObjectSpace
|
||||||
|
+ ", locationInImageSpace="
|
||||||
|
+ locationInImageSpace
|
||||||
|
+ ", reprojectionErrors="
|
||||||
|
+ reprojectionErrors
|
||||||
|
+ ", optimisedCameraToObject="
|
||||||
|
+ optimisedCameraToObject
|
||||||
|
+ ", includeObservationInCalibration="
|
||||||
|
+ includeObservationInCalibration
|
||||||
|
+ ", snapshotName="
|
||||||
|
+ snapshotName
|
||||||
|
+ ", snapshotData="
|
||||||
|
+ snapshotData
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoardObservation clone() {
|
||||||
|
try {
|
||||||
|
return (BoardObservation) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
System.err.println("Guhhh clone buh");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
+ cameraIntrinsics
|
+ cameraIntrinsics
|
||||||
+ ", distCoeffs="
|
+ ", distCoeffs="
|
||||||
+ distCoeffs
|
+ distCoeffs
|
||||||
+ ", observations="
|
+ ", observationslen="
|
||||||
+ observations
|
+ observations.size()
|
||||||
+ ", calobjectWarp="
|
+ ", calobjectWarp="
|
||||||
+ Arrays.toString(calobjectWarp)
|
+ Arrays.toString(calobjectWarp)
|
||||||
+ ", intrinsicsArr="
|
+ ", intrinsicsArr="
|
||||||
@@ -201,4 +201,16 @@ public class CameraCalibrationCoefficients implements Releasable {
|
|||||||
+ Arrays.toString(distCoeffsArr)
|
+ Arrays.toString(distCoeffsArr)
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UICameraCalibrationCoefficients cloneWithoutObservations() {
|
||||||
|
return new UICameraCalibrationCoefficients(
|
||||||
|
resolution,
|
||||||
|
cameraIntrinsics,
|
||||||
|
distCoeffs,
|
||||||
|
calobjectWarp,
|
||||||
|
observations,
|
||||||
|
calobjectSize,
|
||||||
|
calobjectSpacing,
|
||||||
|
lensmodel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,4 +76,17 @@ public class JsonImageMat implements Releasable {
|
|||||||
public void release() {
|
public void release() {
|
||||||
if (wrappedMat != null) wrappedMat.release();
|
if (wrappedMat != null) wrappedMat.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JsonImageMat [rows="
|
||||||
|
+ rows
|
||||||
|
+ ", cols="
|
||||||
|
+ cols
|
||||||
|
+ ", type="
|
||||||
|
+ type
|
||||||
|
+ ", datalen="
|
||||||
|
+ data.length()
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class JsonMatOfDouble implements Releasable {
|
|||||||
@JsonIgnore private Mat wrappedMat = null;
|
@JsonIgnore private Mat wrappedMat = null;
|
||||||
@JsonIgnore private Matrix wpilibMat = null;
|
@JsonIgnore private Matrix wpilibMat = null;
|
||||||
|
|
||||||
private MatOfDouble wrappedMatOfDouble;
|
@JsonIgnore private MatOfDouble wrappedMatOfDouble;
|
||||||
|
|
||||||
public JsonMatOfDouble(int rows, int cols, double[] data) {
|
public JsonMatOfDouble(int rows, int cols, double[] data) {
|
||||||
this(rows, cols, CvType.CV_64FC1, data);
|
this(rows, cols, CvType.CV_64FC1, data);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Photon Vision.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.vision.calibration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.opencv.core.Size;
|
||||||
|
|
||||||
|
public class UICameraCalibrationCoefficients extends CameraCalibrationCoefficients {
|
||||||
|
public int numSnapshots;
|
||||||
|
public List<Double> meanErrors;
|
||||||
|
|
||||||
|
public UICameraCalibrationCoefficients(
|
||||||
|
Size resolution,
|
||||||
|
JsonMatOfDouble cameraIntrinsics,
|
||||||
|
JsonMatOfDouble distCoeffs,
|
||||||
|
double[] calobjectWarp,
|
||||||
|
List<BoardObservation> observations,
|
||||||
|
Size calobjectSize,
|
||||||
|
double calobjectSpacing,
|
||||||
|
CameraLensModel lensmodel) {
|
||||||
|
// yeet observations, keep all else
|
||||||
|
super(
|
||||||
|
resolution,
|
||||||
|
cameraIntrinsics,
|
||||||
|
distCoeffs,
|
||||||
|
calobjectWarp,
|
||||||
|
List.of(),
|
||||||
|
calobjectSize,
|
||||||
|
calobjectSpacing,
|
||||||
|
lensmodel);
|
||||||
|
|
||||||
|
this.numSnapshots = observations.size();
|
||||||
|
this.meanErrors =
|
||||||
|
observations.stream()
|
||||||
|
.map(
|
||||||
|
it2 ->
|
||||||
|
it2.reprojectionErrors.stream()
|
||||||
|
.mapToDouble(it -> Math.sqrt(it.x * it.x + it.y * it.y))
|
||||||
|
.average()
|
||||||
|
.orElse(0))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,8 @@ public class RknnDetectionPipe
|
|||||||
this.detector =
|
this.detector =
|
||||||
new RknnObjectDetector(
|
new RknnObjectDetector(
|
||||||
NeuralNetworkModelManager.getInstance().getDefaultRknnModel().getAbsolutePath(),
|
NeuralNetworkModelManager.getInstance().getDefaultRknnModel().getAbsolutePath(),
|
||||||
NeuralNetworkModelManager.getInstance().getLabels());
|
NeuralNetworkModelManager.getInstance().getLabels(),
|
||||||
|
NeuralNetworkModelManager.getInstance().getModelVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.opencv.core.Size;
|
import org.opencv.core.Size;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
@@ -536,7 +537,10 @@ public class VisionModule {
|
|||||||
ret.outputStreamPort = this.outputStreamPort;
|
ret.outputStreamPort = this.outputStreamPort;
|
||||||
ret.inputStreamPort = this.inputStreamPort;
|
ret.inputStreamPort = this.inputStreamPort;
|
||||||
|
|
||||||
ret.calibrations = visionSource.getSettables().getConfiguration().calibrations;
|
ret.calibrations =
|
||||||
|
visionSource.getSettables().getConfiguration().calibrations.stream()
|
||||||
|
.map(CameraCalibrationCoefficients::cloneWithoutObservations)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
ret.isFovConfigurable =
|
ret.isFovConfigurable =
|
||||||
!(ConfigManager.getInstance().getConfig().getHardwareConfig().hasPresetFOV()
|
!(ConfigManager.getInstance().getConfig().getHardwareConfig().hasPresetFOV()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class EstimatedRobotPose:
|
|||||||
timestampSeconds: float
|
timestampSeconds: float
|
||||||
"""The estimated time the frame used to derive the robot pose was taken"""
|
"""The estimated time the frame used to derive the robot pose was taken"""
|
||||||
|
|
||||||
targetsUsed: [PhotonTrackedTarget]
|
targetsUsed: list[PhotonTrackedTarget]
|
||||||
"""A list of the targets used to compute this pose"""
|
"""A list of the targets used to compute this pose"""
|
||||||
|
|
||||||
strategy: "PoseStrategy"
|
strategy: "PoseStrategy"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import wpilib
|
|||||||
|
|
||||||
|
|
||||||
class Packet:
|
class Packet:
|
||||||
def __init__(self, data: list[int]):
|
def __init__(self, data: bytes):
|
||||||
"""
|
"""
|
||||||
* Constructs an empty packet.
|
* Constructs an empty packet.
|
||||||
*
|
*
|
||||||
@@ -30,7 +30,7 @@ class Packet:
|
|||||||
matches the version of photonlib running in the robot code.
|
matches the version of photonlib running in the robot code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _getNextByte(self) -> int:
|
def _getNextByteAsInt(self) -> int:
|
||||||
retVal = 0x00
|
retVal = 0x00
|
||||||
|
|
||||||
if not self.outOfBytes:
|
if not self.outOfBytes:
|
||||||
@@ -43,7 +43,7 @@ class Packet:
|
|||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def getData(self) -> list[int]:
|
def getData(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
* Returns the packet data.
|
* Returns the packet data.
|
||||||
*
|
*
|
||||||
@@ -51,7 +51,7 @@ class Packet:
|
|||||||
"""
|
"""
|
||||||
return self.packetData
|
return self.packetData
|
||||||
|
|
||||||
def setData(self, data: list[int]):
|
def setData(self, data: bytes):
|
||||||
"""
|
"""
|
||||||
* Sets the packet data.
|
* Sets the packet data.
|
||||||
*
|
*
|
||||||
@@ -65,7 +65,7 @@ class Packet:
|
|||||||
# Read ints in from the data buffer
|
# Read ints in from the data buffer
|
||||||
intList = []
|
intList = []
|
||||||
for _ in range(numBytes):
|
for _ in range(numBytes):
|
||||||
intList.append(self._getNextByte())
|
intList.append(self._getNextByteAsInt())
|
||||||
|
|
||||||
# Interpret the bytes as a floating point number
|
# Interpret the bytes as a floating point number
|
||||||
value = struct.unpack(unpackFormat, bytes(intList))[0]
|
value = struct.unpack(unpackFormat, bytes(intList))[0]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from wpilib import Timer
|
|||||||
import wpilib
|
import wpilib
|
||||||
from photonlibpy.packet import Packet
|
from photonlibpy.packet import Packet
|
||||||
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
from photonlibpy.photonPipelineResult import PhotonPipelineResult
|
||||||
from photonlibpy.version import PHOTONVISION_VERSION, PHOTONLIB_VERSION
|
from photonlibpy.version import PHOTONVISION_VERSION, PHOTONLIB_VERSION # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
|
||||||
class VisionLEDMode(Enum):
|
class VisionLEDMode(Enum):
|
||||||
@@ -86,7 +86,8 @@ class PhotonCamera:
|
|||||||
if len(byteList) < 1:
|
if len(byteList) < 1:
|
||||||
return retVal
|
return retVal
|
||||||
else:
|
else:
|
||||||
retVal.populateFromPacket(Packet(byteList))
|
pkt = Packet(byteList)
|
||||||
|
retVal.populateFromPacket(pkt)
|
||||||
# NT4 allows us to correct the timestamp based on when the message was sent
|
# NT4 allows us to correct the timestamp based on when the message was sent
|
||||||
retVal.setTimestampSeconds(
|
retVal.setTimestampSeconds(
|
||||||
timestamp / 1e-6 - retVal.getLatencyMillis() / 1e-3
|
timestamp / 1e-6 - retVal.getLatencyMillis() / 1e-3
|
||||||
|
|||||||
@@ -38,3 +38,6 @@ class PhotonPipelineResult:
|
|||||||
|
|
||||||
def getTargets(self) -> list[PhotonTrackedTarget]:
|
def getTargets(self) -> list[PhotonTrackedTarget]:
|
||||||
return self.targets
|
return self.targets
|
||||||
|
|
||||||
|
def hasTargets(self) -> bool:
|
||||||
|
return len(self.targets) > 0
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class PhotonPoseEstimator:
|
|||||||
|
|
||||||
self._multiTagFallbackStrategy = PoseStrategy.LOWEST_AMBIGUITY
|
self._multiTagFallbackStrategy = PoseStrategy.LOWEST_AMBIGUITY
|
||||||
self._reportedErrors: set[int] = set()
|
self._reportedErrors: set[int] = set()
|
||||||
self._poseCacheTimestampSeconds = -1
|
self._poseCacheTimestampSeconds = -1.0
|
||||||
self._lastPose: Optional[Pose3d] = None
|
self._lastPose: Optional[Pose3d] = None
|
||||||
self._referencePose: Optional[Pose3d] = None
|
self._referencePose: Optional[Pose3d] = None
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class PhotonPoseEstimator:
|
|||||||
self._multiTagFallbackStrategy = strategy
|
self._multiTagFallbackStrategy = strategy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def referencePose(self) -> Pose3d:
|
def referencePose(self) -> Optional[Pose3d]:
|
||||||
"""Return the reference position that is being used by the estimator.
|
"""Return the reference position that is being used by the estimator.
|
||||||
|
|
||||||
:returns: the referencePose
|
:returns: the referencePose
|
||||||
@@ -163,7 +163,7 @@ class PhotonPoseEstimator:
|
|||||||
self._referencePose = referencePose
|
self._referencePose = referencePose
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lastPose(self) -> Pose3d:
|
def lastPose(self) -> Optional[Pose3d]:
|
||||||
return self._lastPose
|
return self._lastPose
|
||||||
|
|
||||||
@lastPose.setter
|
@lastPose.setter
|
||||||
@@ -178,10 +178,10 @@ class PhotonPoseEstimator:
|
|||||||
self._checkUpdate(self._lastPose, lastPose)
|
self._checkUpdate(self._lastPose, lastPose)
|
||||||
self._lastPose = lastPose
|
self._lastPose = lastPose
|
||||||
|
|
||||||
def _invalidatePoseCache(self):
|
def _invalidatePoseCache(self) -> None:
|
||||||
self._poseCacheTimestampSeconds = -1
|
self._poseCacheTimestampSeconds = -1.0
|
||||||
|
|
||||||
def _checkUpdate(self, oldObj, newObj):
|
def _checkUpdate(self, oldObj, newObj) -> None:
|
||||||
if oldObj != newObj and oldObj is not None and oldObj is not newObj:
|
if oldObj != newObj and oldObj is not None and oldObj is not newObj:
|
||||||
self._invalidatePoseCache()
|
self._invalidatePoseCache()
|
||||||
|
|
||||||
@@ -204,27 +204,27 @@ class PhotonPoseEstimator:
|
|||||||
if not cameraResult:
|
if not cameraResult:
|
||||||
if not self._camera:
|
if not self._camera:
|
||||||
wpilib.reportError("[PhotonPoseEstimator] Missing camera!", False)
|
wpilib.reportError("[PhotonPoseEstimator] Missing camera!", False)
|
||||||
return
|
return None
|
||||||
cameraResult = self._camera.getLatestResult()
|
cameraResult = self._camera.getLatestResult()
|
||||||
|
|
||||||
if cameraResult.timestampSec < 0:
|
if cameraResult.timestampSec < 0:
|
||||||
return
|
return None
|
||||||
|
|
||||||
# If the pose cache timestamp was set, and the result is from the same
|
# If the pose cache timestamp was set, and the result is from the same
|
||||||
# timestamp, return an
|
# timestamp, return an
|
||||||
# empty result
|
# empty result
|
||||||
if (
|
if (
|
||||||
self._poseCacheTimestampSeconds > 0
|
self._poseCacheTimestampSeconds > 0.0
|
||||||
and abs(self._poseCacheTimestampSeconds - cameraResult.timestampSec) < 1e-6
|
and abs(self._poseCacheTimestampSeconds - cameraResult.timestampSec) < 1e-6
|
||||||
):
|
):
|
||||||
return
|
return None
|
||||||
|
|
||||||
# Remember the timestamp of the current result used
|
# Remember the timestamp of the current result used
|
||||||
self._poseCacheTimestampSeconds = cameraResult.timestampSec
|
self._poseCacheTimestampSeconds = cameraResult.timestampSec
|
||||||
|
|
||||||
# If no targets seen, trivial case -- return empty result
|
# If no targets seen, trivial case -- return empty result
|
||||||
if not cameraResult.targets:
|
if not cameraResult.targets:
|
||||||
return
|
return None
|
||||||
|
|
||||||
return self._update(cameraResult, self._primaryStrategy)
|
return self._update(cameraResult, self._primaryStrategy)
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ class PhotonPoseEstimator:
|
|||||||
wpilib.reportError(
|
wpilib.reportError(
|
||||||
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", False
|
"[PhotonPoseEstimator] Unknown Position Estimation Strategy!", False
|
||||||
)
|
)
|
||||||
return
|
return None
|
||||||
|
|
||||||
if not estimatedPose:
|
if not estimatedPose:
|
||||||
self._lastPose = None
|
self._lastPose = None
|
||||||
@@ -280,7 +280,7 @@ class PhotonPoseEstimator:
|
|||||||
"""
|
"""
|
||||||
lowestAmbiguityTarget = None
|
lowestAmbiguityTarget = None
|
||||||
|
|
||||||
lowestAmbiguityScore = 10
|
lowestAmbiguityScore = 10.0
|
||||||
|
|
||||||
for target in result.targets:
|
for target in result.targets:
|
||||||
targetPoseAmbiguity = target.poseAmbiguity
|
targetPoseAmbiguity = target.poseAmbiguity
|
||||||
@@ -293,7 +293,7 @@ class PhotonPoseEstimator:
|
|||||||
# Although there are confirmed to be targets, none of them may be fiducial
|
# Although there are confirmed to be targets, none of them may be fiducial
|
||||||
# targets.
|
# targets.
|
||||||
if not lowestAmbiguityTarget:
|
if not lowestAmbiguityTarget:
|
||||||
return
|
return None
|
||||||
|
|
||||||
targetFiducialId = lowestAmbiguityTarget.fiducialId
|
targetFiducialId = lowestAmbiguityTarget.fiducialId
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ class PhotonPoseEstimator:
|
|||||||
|
|
||||||
if not targetPosition:
|
if not targetPosition:
|
||||||
self._reportFiducialPoseError(targetFiducialId)
|
self._reportFiducialPoseError(targetFiducialId)
|
||||||
return
|
return None
|
||||||
|
|
||||||
return EstimatedRobotPose(
|
return EstimatedRobotPose(
|
||||||
targetPosition.transformBy(
|
targetPosition.transformBy(
|
||||||
|
|||||||
@@ -350,8 +350,7 @@ public class DataSocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(Object message, WsContext user) throws JsonProcessingException {
|
private void sendMessage(ByteBuffer b, WsContext user) throws JsonProcessingException {
|
||||||
ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message));
|
|
||||||
if (user.session.isOpen()) {
|
if (user.session.isOpen()) {
|
||||||
user.send(b);
|
user.send(b);
|
||||||
}
|
}
|
||||||
@@ -359,16 +358,18 @@ public class DataSocketHandler {
|
|||||||
|
|
||||||
public void broadcastMessage(Object message, WsContext userToSkip)
|
public void broadcastMessage(Object message, WsContext userToSkip)
|
||||||
throws JsonProcessingException {
|
throws JsonProcessingException {
|
||||||
|
ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message));
|
||||||
|
|
||||||
if (userToSkip == null) {
|
if (userToSkip == null) {
|
||||||
for (WsContext user : users) {
|
for (WsContext user : users) {
|
||||||
sendMessage(message, user);
|
sendMessage(b, user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var skipUserPort = ((InetSocketAddress) userToSkip.session.getRemoteAddress()).getPort();
|
var skipUserPort = ((InetSocketAddress) userToSkip.session.getRemoteAddress()).getPort();
|
||||||
for (WsContext user : users) {
|
for (WsContext user : users) {
|
||||||
var userPort = ((InetSocketAddress) user.session.getRemoteAddress()).getPort();
|
var userPort = ((InetSocketAddress) user.session.getRemoteAddress()).getPort();
|
||||||
if (userPort != skipUserPort) {
|
if (userPort != skipUserPort) {
|
||||||
sendMessage(message, user);
|
sendMessage(b, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import java.util.HashMap;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.opencv.core.MatOfByte;
|
||||||
|
import org.opencv.core.MatOfInt;
|
||||||
|
import org.opencv.imgcodecs.Imgcodecs;
|
||||||
import org.photonvision.common.configuration.ConfigManager;
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
import org.photonvision.common.configuration.NetworkConfig;
|
import org.photonvision.common.configuration.NetworkConfig;
|
||||||
import org.photonvision.common.dataflow.DataChangeDestination;
|
import org.photonvision.common.dataflow.DataChangeDestination;
|
||||||
@@ -580,6 +583,77 @@ public class RequestHandler {
|
|||||||
ctx.status(204);
|
ctx.status(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onCalibrationSnapshotRequest(Context ctx) {
|
||||||
|
logger.info(ctx.queryString().toString());
|
||||||
|
|
||||||
|
int idx = Integer.parseInt(ctx.queryParam("cameraIdx"));
|
||||||
|
var width = Integer.parseInt(ctx.queryParam("width"));
|
||||||
|
var height = Integer.parseInt(ctx.queryParam("height"));
|
||||||
|
var observationIdx = Integer.parseInt(ctx.queryParam("snapshotIdx"));
|
||||||
|
|
||||||
|
CameraCalibrationCoefficients calList =
|
||||||
|
VisionModuleManager.getInstance()
|
||||||
|
.getModule(idx)
|
||||||
|
.getStateAsCameraConfig()
|
||||||
|
.calibrations
|
||||||
|
.stream()
|
||||||
|
.filter(
|
||||||
|
it ->
|
||||||
|
Math.abs(it.resolution.width - width) < 1e-4
|
||||||
|
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (calList == null || calList.observations.size() < observationIdx) {
|
||||||
|
ctx.status(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode as jpeg to save even more space. reduces size of a 1280p image from 300k to 25k
|
||||||
|
var jpegBytes = new MatOfByte();
|
||||||
|
Imgcodecs.imencode(
|
||||||
|
".jpg",
|
||||||
|
calList.observations.get(observationIdx).snapshotData.getAsMat(),
|
||||||
|
jpegBytes,
|
||||||
|
new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 60));
|
||||||
|
|
||||||
|
ctx.result(jpegBytes.toArray());
|
||||||
|
jpegBytes.release();
|
||||||
|
|
||||||
|
ctx.status(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onCalibrationExportRequest(Context ctx) {
|
||||||
|
logger.info(ctx.queryString().toString());
|
||||||
|
|
||||||
|
int idx = Integer.parseInt(ctx.queryParam("cameraIdx"));
|
||||||
|
var width = Integer.parseInt(ctx.queryParam("width"));
|
||||||
|
var height = Integer.parseInt(ctx.queryParam("height"));
|
||||||
|
|
||||||
|
var cc = VisionModuleManager.getInstance().getModule(idx).getStateAsCameraConfig();
|
||||||
|
|
||||||
|
CameraCalibrationCoefficients calList =
|
||||||
|
cc.calibrations.stream()
|
||||||
|
.filter(
|
||||||
|
it ->
|
||||||
|
Math.abs(it.resolution.width - width) < 1e-4
|
||||||
|
&& Math.abs(it.resolution.height - height) < 1e-4)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (calList == null) {
|
||||||
|
ctx.status(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filename = "photon_calibration_" + cc.uniqueName + "_" + width + "x" + height + ".json";
|
||||||
|
ctx.contentType("application/zip");
|
||||||
|
ctx.header("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||||
|
ctx.json(calList);
|
||||||
|
|
||||||
|
ctx.status(200);
|
||||||
|
}
|
||||||
|
|
||||||
public static void onImageSnapshotsRequest(Context ctx) {
|
public static void onImageSnapshotsRequest(Context ctx) {
|
||||||
var snapshots = new ArrayList<HashMap<String, Object>>();
|
var snapshots = new ArrayList<HashMap<String, Object>>();
|
||||||
var cameraDirs = ConfigManager.getInstance().getImageSavePath().toFile().listFiles();
|
var cameraDirs = ConfigManager.getInstance().getImageSavePath().toFile().listFiles();
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ public class Server {
|
|||||||
app.post("/api/utils/restartDevice", RequestHandler::onDeviceRestartRequest);
|
app.post("/api/utils/restartDevice", RequestHandler::onDeviceRestartRequest);
|
||||||
app.post("/api/utils/publishMetrics", RequestHandler::onMetricsPublishRequest);
|
app.post("/api/utils/publishMetrics", RequestHandler::onMetricsPublishRequest);
|
||||||
app.get("/api/utils/getImageSnapshots", RequestHandler::onImageSnapshotsRequest);
|
app.get("/api/utils/getImageSnapshots", RequestHandler::onImageSnapshotsRequest);
|
||||||
|
app.get("/api/utils/getCalSnapshot", RequestHandler::onCalibrationSnapshotRequest);
|
||||||
|
app.get("/api/utils/getCalibrationJSON", RequestHandler::onCalibrationExportRequest);
|
||||||
|
|
||||||
// Calibration
|
// Calibration
|
||||||
app.post("/api/calibration/end", RequestHandler::onCalibrationEndRequest);
|
app.post("/api/calibration/end", RequestHandler::onCalibrationEndRequest);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url = "https://maven.photonvision.org/repository/internal/" }
|
maven { url = "https://maven.photonvision.org/releases" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,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" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ fi
|
|||||||
echo "Installing additional math packages"
|
echo "Installing additional math packages"
|
||||||
apt-get install --yes libcholmod3 liblapack3 libsuitesparseconfig5
|
apt-get install --yes libcholmod3 liblapack3 libsuitesparseconfig5
|
||||||
|
|
||||||
|
echo "Installing v4l-utils..."
|
||||||
|
apt-get install --yes v4l-utils
|
||||||
|
echo "v4l-utils installation complete."
|
||||||
|
|
||||||
echo "Downloading latest stable release of PhotonVision..."
|
echo "Downloading latest stable release of PhotonVision..."
|
||||||
mkdir -p /opt/photonvision
|
mkdir -p /opt/photonvision
|
||||||
cd /opt/photonvision
|
cd /opt/photonvision
|
||||||
|
|||||||
@@ -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